diff --git a/CHANGELOG.md b/CHANGELOG.md index c234880..e112e39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## 2024-08-23 v1.35.0 + +- Based on Comet 24.6.10 +- Fix multipart/form-data requests used in AdminMetaResourceNew. +- AdminMetaResourceNew now accepts a file path instead of the file contents. + ## 2024-08-23 v1.34.0 - Based on Comet 24.6.10 diff --git a/cometsdk.go b/cometsdk.go index 764af63..80fdd24 100644 --- a/cometsdk.go +++ b/cometsdk.go @@ -9,6 +9,8 @@ import ( "mime/multipart" "net/http" "net/url" + "os" + "path/filepath" "strings" ) @@ -4237,19 +4239,14 @@ func (c *CometAPIClient) Request(contentType, method, path string, data map[stri } u.Path = path - // req.Body must be set later on - req, err := http.NewRequest(strings.ToUpper(method), u.String(), nil) - if err != nil { - return nil, err - } - if c.CustomHeaders != nil { - for header, value := range c.CustomHeaders { - req.Header.Add(header, value) - } - } - + var req *http.Request switch contentType { case "application/x-www-form-urlencoded": + // req.Body must be set later on + req, err = http.NewRequest(strings.ToUpper(method), u.String(), nil) + if err != nil { + return nil, err + } body := url.Values{} if data != nil { body = url.Values(data) @@ -4283,36 +4280,52 @@ func (c *CometAPIClient) Request(contentType, method, path string, data map[stri m := multipart.NewWriter(&body) for key, values := range data { for _, value := range values { - m.WriteField(key, value) + file, err := os.Open(value) + if err != nil { + return nil, fmt.Errorf("error opening file: %w", err) + } + defer file.Close() + part, err := m.CreateFormFile(key, filepath.Base(value)) + if err != nil { + return nil, err + } + + if _, err := io.Copy(part, file); err != nil { + return nil, err + } + if err := m.Close(); err != nil { + return nil, err + } } } - - req.Body = io.NopCloser(&body) - + req, err = http.NewRequest(strings.ToUpper(method), u.String(), bytes.NewReader(body.Bytes())) + if err != nil { + return nil, err + } req.Header.Add("Content-Type", m.FormDataContentType()) req.Header.Add("X-Comet-Admin-Username", c.Username) if c.SessionKey != "" { req.Header.Add("X-Comet-Admin-AuthType", "SessionKey") req.Header.Add("X-Comet-Admin-SessionKey", c.SessionKey) - } else if c.TOTPKey != "" { req.Header.Add("X-Comet-Admin-AuthType", "PasswordTOTP") req.Header.Add("X-Comet-Admin-Password", c.Password) req.Header.Add("X-Comet-Admin-TOTP", c.TOTPKey) c.TOTPKey = "" // Once the TOTPKey is used, it is not usable again. - } else { req.Header.Add("X-Comet-Admin-AuthType", "Password") req.Header.Add("X-Comet-Admin-Password", c.Password) - } - default: return nil, fmt.Errorf("Unexpected content type: %s", contentType) } - + if c.CustomHeaders != nil { + for header, value := range c.CustomHeaders { + req.Header.Add(header, value) + } + } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err @@ -8143,7 +8156,7 @@ func (c *CometAPIClient) AdminMetaResourceGet(Hash string) ([]byte, error) { // You must supply administrator authentication credentials to use this API. // // - Params -// upload: The uploaded file contents, as a multipart/form-data part. +// upload: The path of the file to upload. func (c *CometAPIClient) AdminMetaResourceNew(upload string) (*AdminResourceResponse, error) { data := map[string][]string{} var err error diff --git a/examples/admin/main.go b/examples/admin/main.go index d13281a..e95e7d2 100644 --- a/examples/admin/main.go +++ b/examples/admin/main.go @@ -5,6 +5,7 @@ package main This quick commandline tool allows you to list and download any available CometBackup Clients hosted on the Comet Server you're sending requests to. + It also demonstrates how to upload a resource file using AdminMetaResourceNew. */ import ( @@ -28,12 +29,32 @@ func NewClient(url, username, password string) (*Client, error) { if err != nil { return nil, err } + if _, errIn := client.AdminAccountSessionStart(nil); errIn != nil { + return retryWithTOTP(client, errIn, url) + } return &Client{ client: client, }, nil } +func retryWithTOTP(c *sdk.CometAPIClient, errIn error, serverURL string) (client *Client, err error) { + if strings.Contains(errIn.Error(), "449") { + fmt.Printf("re-connecting to server: %s\n", serverURL) + totp, err := util.Totp() + if err != nil { + log.Fatal("Error reading TOTP: ", err) + } + client.client.TOTPKey = totp + // ReuseSessionKey is especially useful when using TOTP based authentication, otherwise you need to enter a + // new TOTPKey for every api call. + client.client.ReuseSessionKey = true + fmt.Println("Connection successful.") + return &Client{c}, nil + } + return nil, fmt.Errorf("error starting admin account session: %w", errIn) +} + func (c *Client) DownloadBrandedClient(platform int) ([]byte, error) { return c.client.AdminBrandingGenerateClientByPlatform(platform, nil) } @@ -68,6 +89,15 @@ func (c *Client) ListAndPrintPlatforms() error { return nil } +func (c *Client) UploadResource(filePath string) error { + response, err := c.client.AdminMetaResourceNew(filePath) + if err != nil { + return err + } + fmt.Printf("Resource uploaded successfully. response: %s\n", response) + return nil +} + func main() { url := flag.String("s", "http://localhost:8060", "The URL for the CometServer API") username := flag.String("u", "", "The username to authenticate with") @@ -75,10 +105,11 @@ func main() { list := flag.Bool("list", false, "List platforms available for download") download := flag.Int("download", 0, "The id of the platform to download") + upload := flag.String("upload", "README.md", "Path to the file to upload as a resource") flag.Parse() - if (*list && *download != 0) || (!*list && *download == 0) { - log.Fatal("Error: A command must be chosen. Choose one of either '--list' or '--download #'") + if (*list && *download != 0) || (!*list && *download == 0 && *upload == "") { + log.Fatal("Error: A command must be chosen. Choose one of '--list', '--download #', or '--upload '") } if *username == "" || *password == "" { var err error @@ -91,14 +122,6 @@ func main() { if err != nil { log.Fatal("Error creating client: ", err) } - totp, err := util.Totp() - if err != nil { - log.Fatal("Error reading TOTP: ", err) - } - client.client.TOTPKey = totp - // ReuseSessionKey is especially useful when using TOTP based authentication, otherwise you need to enter a - // new TOTPKey for every api call. - client.client.ReuseSessionKey = true if *list { err = client.ListAndPrintPlatforms() @@ -142,4 +165,11 @@ func main() { fmt.Println("Congratulations! You now have a branded Comet client: ", fileName) } + + if *upload != "" { + err := client.UploadResource(*upload) + if err != nil { + log.Fatal("Error uploading resource: ", err) + } + } }