mirror of
https://salsa.debian.org/mdosch/go-sendxmpp
synced 2024-11-15 00:15:10 +00:00
2f2a2fd4fd
Calling client.Recv() multiple times caused messages not appear in `--listen` mode as calling Recv() for receiving IQs (e.g. for receiving Ox keys) also received the messages so they were no longer available for the Recv in the listening function.
190 lines
5.6 KiB
Go
190 lines
5.6 KiB
Go
// Copyright 2020 - 2021 Martin Dosch.
|
|
// Use of this source code is governed by the BSD-2-clause
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"github.com/beevik/etree" // BSD-2-clause
|
|
"github.com/gabriel-vasile/mimetype" // MIT License)
|
|
"github.com/mattn/go-xmpp" // BSD-3-Clause
|
|
)
|
|
|
|
func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ,
|
|
jserver string, filePath string) string {
|
|
var uploadComponent string
|
|
var maxFileSize int64
|
|
|
|
// Get file size
|
|
fileInfo, err := os.Stat(filePath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fileSize := fileInfo.Size()
|
|
|
|
// Read file
|
|
buffer, err := readFile(filePath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Get mime type
|
|
mimeType := mimetype.Detect(buffer.Bytes()).String()
|
|
var mimeTypeEscaped bytes.Buffer
|
|
xml.Escape(&mimeTypeEscaped, []byte(mimeType))
|
|
|
|
// Get file name
|
|
fileName := filepath.Base(filePath)
|
|
// Just use alphanumerical characters for now to work around
|
|
// https://github.com/mattn/go-xmpp/issues/132
|
|
reg, err := regexp.Compile(`[^a-zA-Z0-9\+\-\_\.]+`)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fileNameEscaped := reg.ReplaceAllString(fileName, "_")
|
|
|
|
// Query server for disco#items
|
|
iqContent, err := sendIQ(client, iqc, jserver, "get",
|
|
"<query xmlns='http://jabber.org/protocol/disco#items'/>")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
iqDiscoItemsXML := etree.NewDocument()
|
|
err = iqDiscoItemsXML.ReadFromBytes(iqContent.Query)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
iqDiscoItemsXMLQuery := iqDiscoItemsXML.SelectElement("query")
|
|
iqDiscoItemsXMLItems := iqDiscoItemsXMLQuery.SelectElements("item")
|
|
|
|
// Check the services reported by disco#items for the http upload service
|
|
for _, r := range iqDiscoItemsXMLItems {
|
|
jid := r.SelectAttr("jid")
|
|
iqDiscoInfoReqXML := etree.NewDocument()
|
|
iqDiscoInfoReqXMLQuery := iqDiscoInfoReqXML.CreateElement("query")
|
|
iqDiscoInfoReqXMLQuery.CreateAttr("xmlns", nsDiscoInfo)
|
|
iqdi, err := iqDiscoInfoReqXML.WriteToString()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
iqDiscoInfo, err := sendIQ(client, iqc, jid.Value, "get", iqdi)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if iqDiscoInfo.Type != "result" {
|
|
continue
|
|
}
|
|
iqDiscoInfoXML := etree.NewDocument()
|
|
err = iqDiscoInfoXML.ReadFromBytes(iqDiscoInfo.Query)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
iqDiscoInfoXMLQuery := iqDiscoInfoXML.SelectElement("query")
|
|
iqDiscoInfoXMLIdentity := iqDiscoInfoXMLQuery.SelectElement("identity")
|
|
iqDiscoInfoXMLType := iqDiscoInfoXMLIdentity.SelectAttr("type")
|
|
iqDiscoInfoXMLCategory := iqDiscoInfoXMLIdentity.SelectAttr("category")
|
|
|
|
if iqDiscoInfoXMLType.Value == "file" &&
|
|
iqDiscoInfoXMLCategory.Value == "store" {
|
|
uploadComponent = jid.Value
|
|
}
|
|
|
|
}
|
|
if uploadComponent == "" {
|
|
log.Fatal("No http upload component found.")
|
|
}
|
|
iqDiscoInfoXMLX := iqDiscoItemsXMLQuery.SelectElements("x")
|
|
for _, r := range iqDiscoInfoXMLX {
|
|
field := r.SelectElements("field")
|
|
for i, t := range field {
|
|
varAttr := t.SelectAttr("var")
|
|
prevFieldVal := field[i-1].SelectElement("value")
|
|
curFieldVal := t.SelectElement("value")
|
|
if varAttr.Value == "max-file-size" && prevFieldVal.Text() == nsHTTPUpload {
|
|
maxFileSize, err = strconv.ParseInt(curFieldVal.Text(), 10, 64)
|
|
if err != nil {
|
|
log.Fatal("Error while checking server maximum http upload file size.")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check if the file size doesn't exceed the maximum file size of the http upload
|
|
// component if a maximum file size is reported, if not just continue and hope for
|
|
// the best.
|
|
if maxFileSize != 0 {
|
|
if fileSize > maxFileSize {
|
|
log.Fatal("File size " + strconv.FormatInt(fileSize/1024/1024, 10) +
|
|
" MB is larger than the maximum file size allowed (" +
|
|
strconv.FormatInt(maxFileSize/1024/1024, 10) + " MB).")
|
|
}
|
|
}
|
|
|
|
request := etree.NewDocument()
|
|
requestReq := request.CreateElement("request")
|
|
requestReq.CreateAttr("xmlns", nsHTTPUpload)
|
|
requestReq.CreateAttr("filename", fileNameEscaped)
|
|
requestReq.CreateAttr("size", fmt.Sprint(fileSize))
|
|
requestReq.CreateAttr("content-type", mimeType)
|
|
r, err := request.WriteToString()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Request http upload slot
|
|
uploadSlot, err := sendIQ(client, iqc, uploadComponent, "get", r)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if uploadSlot.Type != "result" {
|
|
log.Fatal("Error while requesting upload slot.")
|
|
}
|
|
iqHTTPUploadSlotXML := etree.NewDocument()
|
|
err = iqHTTPUploadSlotXML.ReadFromBytes(uploadSlot.Query)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
iqHTTPUploadSlotXMLSlot := iqHTTPUploadSlotXML.SelectElement("slot")
|
|
iqHTTPUploadSlotXMLPut := iqHTTPUploadSlotXMLSlot.SelectElement("put")
|
|
iqHTTPUploadSlotXMLPutURL := iqHTTPUploadSlotXMLPut.SelectAttr("url")
|
|
|
|
// Upload file
|
|
httpClient := &http.Client{}
|
|
req, err := http.NewRequest(http.MethodPut, iqHTTPUploadSlotXMLPutURL.Value,
|
|
buffer)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
req.Header.Set("Content-Type", mimeTypeEscaped.String())
|
|
iqHTTPUploadSlotXMLPutHeaders := iqHTTPUploadSlotXMLPut.SelectElements("header")
|
|
for _, h := range iqHTTPUploadSlotXMLPutHeaders {
|
|
name := h.SelectAttr("name")
|
|
switch name.Value {
|
|
case "Authorization", "Cookie", "Expires":
|
|
req.Header.Set(name.Value, h.Text())
|
|
}
|
|
}
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// Test for http status code "200 OK" or "201 Created"
|
|
if resp.StatusCode != 200 && resp.StatusCode != 201 {
|
|
log.Fatal("Http upload failed.")
|
|
}
|
|
|
|
// Return http link
|
|
iqHTTPUploadSlotXMLGet := iqHTTPUploadSlotXMLSlot.SelectElement("get")
|
|
iqHTTPUploadSlotXMLGetURL := iqHTTPUploadSlotXMLGet.SelectAttr("url")
|
|
return iqHTTPUploadSlotXMLGetURL.Value
|
|
}
|