Add experimental http-upload
parent
e674de5df1
commit
9fd205f90f
@ -0,0 +1,208 @@
|
||||
// Copyright 2020 Martin Dosch.
|
||||
// Use of this source code is governed by the BSD-2-clause
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// TODO:
|
||||
// * Check filesize limit before trying to send
|
||||
// * Check IQ replies for type='error'
|
||||
// * Send link OOB
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/mattn/go-xmpp" // BSD-3-Clause"
|
||||
)
|
||||
|
||||
func getID() string {
|
||||
b := make([]byte, 12)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
id := fmt.Sprintf("%x-%x-%x", b[0:4], b[4:8], b[8:])
|
||||
return id
|
||||
}
|
||||
|
||||
func httpUpload(client *xmpp.Client, jserver string, filePath string) string {
|
||||
|
||||
// Created with https://github.com/miku/zek
|
||||
type IQDiscoItemsType struct {
|
||||
XMLName xml.Name `xml:"query"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Item []struct {
|
||||
Text string `xml:",chardata"`
|
||||
Jid string `xml:"jid,attr"`
|
||||
} `xml:"item"`
|
||||
}
|
||||
|
||||
// Created with https://github.com/miku/zek
|
||||
type IQDiscoInfoType struct {
|
||||
XMLName xml.Name `xml:"query"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Identity struct {
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
Category string `xml:"category,attr"`
|
||||
} `xml:"identity"`
|
||||
Feature []struct {
|
||||
Text string `xml:",chardata"`
|
||||
Var string `xml:"var,attr"`
|
||||
} `xml:"feature"`
|
||||
X []struct {
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Field []struct {
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
Var string `xml:"var,attr"`
|
||||
Value string `xml:"value"`
|
||||
} `xml:"field"`
|
||||
} `xml:"x"`
|
||||
}
|
||||
|
||||
// Created with https://github.com/miku/zek
|
||||
type IQHttpUploadSlot struct {
|
||||
XMLName xml.Name `xml:"slot"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Get struct {
|
||||
Text string `xml:",chardata"`
|
||||
URL string `xml:"url,attr"`
|
||||
} `xml:"get"`
|
||||
Put struct {
|
||||
Text string `xml:",chardata"`
|
||||
URL string `xml:"url,attr"`
|
||||
} `xml:"put"`
|
||||
}
|
||||
|
||||
var iqDiscoItemsXML IQDiscoItemsType
|
||||
var iqDiscoInfoXML IQDiscoInfoType
|
||||
var uploadComponent string
|
||||
var iqHttpUploadSlotXML IQHttpUploadSlot
|
||||
|
||||
// Get file size
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fileSize := fileInfo.Size()
|
||||
|
||||
// Open File
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Read file
|
||||
buffer := make([]byte, fileSize)
|
||||
_, err = f.Read(buffer)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Get mime type
|
||||
mimeType := http.DetectContentType(buffer)
|
||||
|
||||
// Get file name
|
||||
fileName := filepath.Base(filePath)
|
||||
|
||||
// IQDiscoItemsType server for disco#items
|
||||
id := getID()
|
||||
c := make(chan xmpp.IQ)
|
||||
go getIQ(client, id, c)
|
||||
_, err = client.RawInformation(client.JID(), jserver, id,
|
||||
"get", "<query xmlns='http://jabber.org/protocol/disco#items'/>")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
iqContent := <-c
|
||||
close(c)
|
||||
xml.Unmarshal(iqContent.Query, &iqDiscoItemsXML)
|
||||
|
||||
// Check the services reported by disco#items for the http upload service
|
||||
for _, r := range iqDiscoItemsXML.Item {
|
||||
id = getID()
|
||||
c := make(chan xmpp.IQ)
|
||||
go getIQ(client, id, c)
|
||||
_, err = client.RawInformation(client.JID(), r.Jid, id, "get",
|
||||
"<query xmlns='http://jabber.org/protocol/disco#info'/>")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
iqDiscoInfo := <-c
|
||||
close(c)
|
||||
xml.Unmarshal(iqDiscoInfo.Query, &iqDiscoInfoXML)
|
||||
|
||||
if iqDiscoInfoXML.Identity.Type == "file" && iqDiscoInfoXML.Identity.Category == "store" {
|
||||
uploadComponent = r.Jid
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if uploadComponent == "" {
|
||||
log.Fatal("No http upload component found.")
|
||||
}
|
||||
|
||||
// Request http upload slot
|
||||
id = getID()
|
||||
c = make(chan xmpp.IQ)
|
||||
go getIQ(client, id, c)
|
||||
_, err = client.RawInformation(client.JID(), uploadComponent, id, "get",
|
||||
"<request xmlns='urn:xmpp:http:upload:0' filename='"+
|
||||
fileName+"' size='"+strconv.FormatInt(fileSize, 10)+
|
||||
"' content-type='"+mimeType+"' />")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
uploadSlot := <-c
|
||||
close(c)
|
||||
xml.Unmarshal(uploadSlot.Query, &iqHttpUploadSlotXML)
|
||||
|
||||
// Upload file
|
||||
httpClient := &http.Client{}
|
||||
req, err := http.NewRequest(http.MethodPut, iqHttpUploadSlotXML.Put.URL, bytes.NewBuffer(buffer))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Content-Type", mimeType)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(resp.StatusCode)
|
||||
|
||||
// Return http link
|
||||
return iqHttpUploadSlotXML.Get.URL
|
||||
}
|
||||
|
||||
func getIQ(client *xmpp.Client, id string, c chan xmpp.IQ) {
|
||||
for {
|
||||
msg, err := client.Recv()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
switch v := msg.(type) {
|
||||
case xmpp.IQ:
|
||||
if v.ID == id {
|
||||
c <- v
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue