diff --git a/go-sendxmpp.go b/go-sendxmpp.go
index c7a4f2a..cd8689a 100644
--- a/go-sendxmpp.go
+++ b/go-sendxmpp.go
@@ -167,6 +167,7 @@ func main() {
// Define command line flags.
flagHelp := getopt.BoolLong("help", 0, "Show help.")
+ flagHttpUpload := getopt.StringLong("http-upload", 0, "", "Send a file via http-upload.")
flagDebug := getopt.BoolLong("debug", 'd', "Show debugging info.")
flagServer := getopt.StringLong("jserver", 'j', "", "XMPP server address.")
flagUser := getopt.StringLong("username", 'u', "", "Username for XMPP account.")
@@ -274,6 +275,11 @@ func main() {
log.Fatal(err)
}
+ if *flagHttpUpload != "" {
+ message = httpUpload(client, strings.Split(server, ":")[0],
+ *flagHttpUpload)
+ }
+
// Skip reading message if '-i' or '--interactive' is set to work with e.g. 'tail -f'.
if !*flagInteractive {
if message == "" {
diff --git a/httpupload.go b/httpupload.go
new file mode 100644
index 0000000..3728ca7
--- /dev/null
+++ b/httpupload.go
@@ -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", "")
+ 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",
+ "")
+ 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",
+ "")
+ 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
+ }
+ }
+ }
+}