You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-sendxmpp/httpupload.go

217 lines
4.8 KiB
Go

// 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'
4 years ago
// * Update Manpages
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)
err = xml.Unmarshal(iqContent.Query, &iqDiscoItemsXML)
if err != nil {
log.Fatal(err)
}
// 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)
err = xml.Unmarshal(iqDiscoInfo.Query, &iqDiscoInfoXML)
if err != nil {
log.Fatal(err)
}
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)
err = xml.Unmarshal(uploadSlot.Query, &iqHttpUploadSlotXML)
if err != nil {
log.Fatal(err)
}
// 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)
_, err = httpClient.Do(req)
if err != nil {
log.Fatal(err)
}
// 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
}
}
}
}