|
|
|
// 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"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/gabriel-vasile/mimetype" // MIT License
|
|
|
|
"github.com/mattn/go-xmpp" // BSD-3-Clause
|
|
|
|
)
|
|
|
|
|
|
|
|
func httpUpload(client *xmpp.Client, jserver string, filePath string) string {
|
|
|
|
var iqDiscoItemsXML IQDiscoItemsType
|
|
|
|
var iqDiscoInfoXML IQDiscoInfoType
|
|
|
|
var iqHttpUploadSlotXML IQHttpUploadSlot
|
|
|
|
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, jserver, "get",
|
|
|
|
"<query xmlns='http://jabber.org/protocol/disco#items'/>")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
iqDiscoInfo, err := sendIQ(client, r.Jid, "get",
|
|
|
|
"<query xmlns='http://jabber.org/protocol/disco#info'/>")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if iqDiscoInfo.Type != "result" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
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.")
|
|
|
|
}
|
|
|
|
for _, r := range iqDiscoInfoXML.X {
|
|
|
|
for i, t := range r.Field {
|
|
|
|
if t.Var == "max-file-size" && r.Field[i-1].Value == nsHttpUpload {
|
|
|
|
maxFileSize, err = strconv.ParseInt(t.Value, 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).")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var request IQHttpUploadSlotRequest
|
|
|
|
request.Xmlns = nsHttpUpload
|
|
|
|
request.FileName = fileNameEscaped
|
|
|
|
request.FileSize = fileSize
|
|
|
|
request.FileType = mimeType
|
|
|
|
r, err := xml.Marshal(request)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Request http upload slot
|
|
|
|
uploadSlot, err := sendIQ(client, uploadComponent, "get", string(r))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if uploadSlot.Type != "result" {
|
|
|
|
log.Fatal("Error while requesting upload slot.")
|
|
|
|
}
|
|
|
|
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, buffer)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
req.Header.Set("Content-Type", mimeTypeEscaped.String())
|
|
|
|
for _, h := range iqHttpUploadSlotXML.Put.Headers {
|
|
|
|
switch h.Name {
|
|
|
|
case "Authorization", "Cookie", "Expires":
|
|
|
|
req.Header.Set(h.Name, h.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
return iqHttpUploadSlotXML.Get.URL
|
|
|
|
}
|