// 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, 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, jserver, "get", "") 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, 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, 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 }