package main import ( "bytes" "context" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/url" "strconv" "strings" ) type gokapiClient struct { httpClient *http.Client host *url.URL apiKey string } func newGokapiClient(host *url.URL, apiKey string) *gokapiClient { return &gokapiClient{ httpClient: &http.Client{}, host: host, apiKey: apiKey, } } func (gc *gokapiClient) uploadChunk(ctx context.Context, fi uploadInfo, offset int64, data io.Reader) error { var body bytes.Buffer boundary, err := gc.prepUploadChunkBody(&body, fi, offset, data) if err != nil { return err } actionURL := gc.host.ResolveReference(&url.URL{Path: "/api/chunk/add"}) req, err := http.NewRequestWithContext(ctx, "POST", actionURL.String(), &body) if err != nil { return err } req.Header.Set("Accept", "application/json") req.Header.Set("Apikey", gc.apiKey) req.Header.Set("Content-Type", "multipart/form-data; boundary="+boundary) resp, err := gc.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("upload chunk failed with status code %d", resp.StatusCode) } return nil } func (gc *gokapiClient) finalizeChunk(ctx context.Context, fi uploadInfo) (UploadResponse, error) { actionURL := gc.host.ResolveReference(&url.URL{Path: "/api/chunk/complete"}) formData := url.Values{} formData.Set("uuid", fi.chunkID) formData.Set("filename", fi.filename) formData.Set("filesize", strconv.FormatInt(fi.totalSize, 10)) formData.Set("contenttype", fi.contentType) formData.Set("allowedDownloads", strconv.Itoa(fi.allowedDownloads)) formData.Set("expiryDays", strconv.Itoa(fi.expiryDays)) formData.Set("password", fi.password) req, err := http.NewRequestWithContext(ctx, "POST", actionURL.String(), strings.NewReader(formData.Encode())) if err != nil { return UploadResponse{}, err } req.Header.Set("Accept", "application/json") req.Header.Set("Apikey", gc.apiKey) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := gc.httpClient.Do(req) if err != nil { return UploadResponse{}, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return UploadResponse{}, fmt.Errorf("upload chunk finalization with status code %d", resp.StatusCode) } var r UploadResponse if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { return UploadResponse{}, err } return r, nil } func (gc *gokapiClient) prepUploadChunkBody(w io.Writer, fi uploadInfo, offset int64, data io.Reader) (string, error) { mw := multipart.NewWriter(w) defer mw.Close() mw.WriteField("uuid", fi.chunkID) mw.WriteField("filesize", strconv.FormatInt(fi.totalSize, 10)) mw.WriteField("offset", strconv.FormatInt(offset, 10)) fileWriter, err := mw.CreateFormFile("file", fi.filename) if err != nil { return "", err } _, err = io.Copy(fileWriter, data) if err != nil { return "", err } return mw.Boundary(), nil }