go-sendxmpp/httpupload.go

233 lines
6.9 KiB
Go
Raw Normal View History

2023-05-11 18:05:31 +00:00
// Copyright Martin Dosch.
2020-04-09 12:58:03 +00:00
// 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"
2023-06-07 20:28:01 +00:00
"errors"
2022-04-23 23:45:20 +00:00
"fmt"
2020-04-09 12:58:03 +00:00
"log"
"net/http"
"os"
"path/filepath"
"regexp"
2020-04-09 12:58:03 +00:00
"strconv"
2022-04-23 23:45:20 +00:00
"github.com/beevik/etree" // BSD-2-clause
2023-01-19 19:05:40 +00:00
"github.com/gabriel-vasile/mimetype" // MIT License
2022-04-09 02:54:12 +00:00
"github.com/mattn/go-xmpp" // BSD-3-Clause
2020-04-09 12:58:03 +00:00
)
2023-06-07 20:33:43 +00:00
func httpUpload(client *xmpp.Client, iqc chan xmpp.IQ, jserver string, filePath string) string {
var uploadComponent string
var maxFileSize int64
2020-04-09 12:58:03 +00:00
// Get file size
fileInfo, err := os.Stat(filePath)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2020-04-09 12:58:03 +00:00
log.Fatal(err)
}
fileSize := fileInfo.Size()
// Read file
buffer, err := readFile(filePath)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2020-04-09 12:58:03 +00:00
log.Fatal(err)
}
// Get mime type
mimeType := mimetype.Detect(buffer.Bytes()).String()
var mimeTypeEscaped bytes.Buffer
xml.Escape(&mimeTypeEscaped, []byte(mimeType))
2020-04-09 12:58:03 +00:00
// Get file name
fileName := filepath.Base(filePath)
2022-11-26 11:33:12 +00:00
// Just use alphanumerical and some special characters for now
// to work around https://github.com/mattn/go-xmpp/issues/132
reg := regexp.MustCompile(`[^a-zA-Z0-9\+\-\_\.]+`)
fileNameEscaped := reg.ReplaceAllString(fileName, "_")
2020-04-09 12:58:03 +00:00
2020-04-09 18:52:07 +00:00
// Query server for disco#items
iqContent, err := sendIQ(client, iqc, jserver, "get",
2022-04-09 02:46:57 +00:00
"<query xmlns='http://jabber.org/protocol/disco#items'/>")
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2020-04-09 12:58:03 +00:00
log.Fatal(err)
}
2022-04-25 09:59:26 +00:00
iqDiscoItemsXML := etree.NewDocument()
err = iqDiscoItemsXML.ReadFromBytes(iqContent.Query)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
log.Fatal(err)
2020-04-09 13:08:52 +00:00
}
2022-04-25 09:59:26 +00:00
iqDiscoItemsXMLQuery := iqDiscoItemsXML.SelectElement("query")
2022-09-01 20:13:13 +00:00
if iqDiscoItemsXMLQuery == nil {
log.Fatal("no query element in disco items reply")
}
2022-04-25 09:59:26 +00:00
iqDiscoItemsXMLItems := iqDiscoItemsXMLQuery.SelectElements("item")
2020-04-09 12:58:03 +00:00
// Check the services reported by disco#items for the http upload service
2022-04-25 09:59:26 +00:00
for _, r := range iqDiscoItemsXMLItems {
jid := r.SelectAttr("jid")
iqDiscoInfoReqXML := etree.NewDocument()
iqDiscoInfoReqXML.WriteSettings.AttrSingleQuote = true
iqDiscoInfoReqXMLQuery := iqDiscoInfoReqXML.CreateElement("query")
iqDiscoInfoReqXMLQuery.CreateAttr("xmlns", nsDiscoInfo)
iqdi, err := iqDiscoInfoReqXML.WriteToString()
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
log.Fatal(err)
}
iqDiscoInfo, err := sendIQ(client, iqc, jid.Value, "get", iqdi)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2020-04-09 12:58:03 +00:00
log.Fatal(err)
}
if iqDiscoInfo.Type != strResult {
continue
}
2022-04-25 09:59:26 +00:00
iqDiscoInfoXML := etree.NewDocument()
err = iqDiscoInfoXML.ReadFromBytes(iqDiscoInfo.Query)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
log.Fatal(err)
2020-04-09 13:08:52 +00:00
}
2022-04-25 09:59:26 +00:00
iqDiscoInfoXMLQuery := iqDiscoInfoXML.SelectElement("query")
2022-09-01 20:13:13 +00:00
if iqDiscoInfoXMLQuery == nil {
continue
}
2022-04-25 09:59:26 +00:00
iqDiscoInfoXMLIdentity := iqDiscoInfoXMLQuery.SelectElement("identity")
2022-09-01 20:13:13 +00:00
if iqDiscoInfoXMLIdentity == nil {
continue
}
2022-04-25 09:59:26 +00:00
iqDiscoInfoXMLType := iqDiscoInfoXMLIdentity.SelectAttr("type")
2022-09-01 20:13:13 +00:00
if iqDiscoInfoXMLType == nil {
continue
}
2022-04-25 09:59:26 +00:00
iqDiscoInfoXMLCategory := iqDiscoInfoXMLIdentity.SelectAttr("category")
2022-09-01 20:13:13 +00:00
if iqDiscoInfoXMLCategory == nil {
continue
}
2022-04-25 09:59:26 +00:00
if iqDiscoInfoXMLType.Value == "file" &&
iqDiscoInfoXMLCategory.Value == "store" {
uploadComponent = jid.Value
2020-04-09 12:58:03 +00:00
}
}
if uploadComponent == "" {
log.Fatal("No http upload component found.")
}
2022-04-25 09:59:26 +00:00
iqDiscoInfoXMLX := iqDiscoItemsXMLQuery.SelectElements("x")
for _, r := range iqDiscoInfoXMLX {
field := r.SelectElements("field")
for i, t := range field {
varAttr := t.SelectAttr("var")
2022-09-01 20:13:13 +00:00
if varAttr == nil {
continue
}
2022-04-25 09:59:26 +00:00
prevFieldVal := field[i-1].SelectElement("value")
2022-09-01 20:13:13 +00:00
if prevFieldVal == nil {
continue
}
2022-04-25 09:59:26 +00:00
curFieldVal := t.SelectElement("value")
2022-09-01 20:13:13 +00:00
if curFieldVal == nil {
continue
}
if varAttr.Value == "max-file-size" && prevFieldVal.Text() == nsHTTPUpload {
2022-04-25 09:59:26 +00:00
maxFileSize, err = strconv.ParseInt(curFieldVal.Text(), 10, 64)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2023-04-09 12:56:22 +00:00
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).")
}
}
2020-04-09 12:58:03 +00:00
2022-04-23 23:45:20 +00:00
request := etree.NewDocument()
request.WriteSettings.AttrSingleQuote = true
2022-04-23 23:45:20 +00:00
requestReq := request.CreateElement("request")
requestReq.CreateAttr("xmlns", nsHTTPUpload)
2022-04-23 23:45:20 +00:00
requestReq.CreateAttr("filename", fileNameEscaped)
requestReq.CreateAttr("size", fmt.Sprint(fileSize))
requestReq.CreateAttr("content-type", mimeType)
r, err := request.WriteToString()
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
log.Fatal(err)
}
2020-04-09 12:58:03 +00:00
// Request http upload slot
uploadSlot, err := sendIQ(client, iqc, uploadComponent, "get", r)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2020-04-09 12:58:03 +00:00
log.Fatal(err)
}
if uploadSlot.Type != strResult {
2023-04-09 12:56:22 +00:00
log.Fatal("error while requesting upload slot.")
}
iqHTTPUploadSlotXML := etree.NewDocument()
err = iqHTTPUploadSlotXML.ReadFromBytes(uploadSlot.Query)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
log.Fatal(err)
2020-04-09 13:08:52 +00:00
}
iqHTTPUploadSlotXMLSlot := iqHTTPUploadSlotXML.SelectElement("slot")
2022-09-01 20:13:13 +00:00
if iqHTTPUploadSlotXMLSlot == nil {
log.Fatal("http-upload: no slot element")
}
iqHTTPUploadSlotXMLPut := iqHTTPUploadSlotXMLSlot.SelectElement("put")
2022-09-01 20:13:13 +00:00
if iqHTTPUploadSlotXMLPut == nil {
log.Fatal("http-upload: no put element")
}
iqHTTPUploadSlotXMLPutURL := iqHTTPUploadSlotXMLPut.SelectAttr("url")
2022-09-01 20:13:13 +00:00
if iqHTTPUploadSlotXMLPutURL == nil {
log.Fatal("http-upload: no url attribute")
}
2020-04-09 12:58:03 +00:00
// Upload file
httpClient := &http.Client{}
req, err := http.NewRequest(http.MethodPut, iqHTTPUploadSlotXMLPutURL.Value,
2022-04-25 09:59:26 +00:00
buffer)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2020-04-09 12:58:03 +00:00
log.Fatal(err)
}
req.Header.Set("Content-Type", mimeTypeEscaped.String())
iqHTTPUploadSlotXMLPutHeaders := iqHTTPUploadSlotXMLPut.SelectElements("header")
for _, h := range iqHTTPUploadSlotXMLPutHeaders {
2022-04-25 09:59:26 +00:00
name := h.SelectAttr("name")
2022-09-01 20:13:13 +00:00
if name == nil {
continue
}
2022-04-25 09:59:26 +00:00
switch name.Value {
2021-01-29 15:06:30 +00:00
case "Authorization", "Cookie", "Expires":
2022-04-25 09:59:26 +00:00
req.Header.Set(name.Value, h.Text())
2021-01-29 15:06:30 +00:00
}
}
2020-04-11 14:08:15 +00:00
resp, err := httpClient.Do(req)
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2020-04-09 12:58:03 +00:00
log.Fatal(err)
}
2020-04-11 14:08:15 +00:00
// Test for http status code "200 OK" or "201 Created"
if resp.StatusCode != 200 && resp.StatusCode != 201 {
log.Fatal("Http upload failed.")
}
2020-04-09 12:58:03 +00:00
// Return http link
iqHTTPUploadSlotXMLGet := iqHTTPUploadSlotXMLSlot.SelectElement("get")
2022-09-01 20:13:13 +00:00
if iqHTTPUploadSlotXMLGet == nil {
log.Fatal("http-upload: no get element")
}
iqHTTPUploadSlotXMLGetURL := iqHTTPUploadSlotXMLGet.SelectAttr("url")
2022-09-01 20:13:13 +00:00
if iqHTTPUploadSlotXMLGetURL == nil {
log.Fatal("http-upload: no url attribute")
}
2023-06-04 14:24:46 +00:00
err = resp.Body.Close()
2023-06-07 20:28:01 +00:00
if errors.Unwrap(err) != nil {
2023-06-04 14:24:46 +00:00
fmt.Println("error while closing http request body:", err)
}
return iqHTTPUploadSlotXMLGetURL.Value
2020-04-09 12:58:03 +00:00
}