gomultipartform-datamultipartcontent-lengthhttp-content-length

How to compute content length of multipart file request with formdata in go


Currently trying to create a multipart upload request to upload a png along with some extra body params. The server is returning the response:

The request body did not contain the specified number of bytes. Got 142,827, expected 146,836

So obviously something is going wrong with the calculation. I can't for the life of me work out how to actually manually count the content length of the body and file correctly for the request. See below my code for sending request:

func signUp(email, username, password, profilePath string) account {
    session := createClient()

    capToke := captchaToken(session)

    solved := solveCaptcha(capToke, "example.com")

    if solved == failed {
        panic("Solve Failed")
    }
    extraParams := map[string]string{
        "Username":             username,
        "Age":                  string(random(18, 21)),
        "Gender":               "1",
        "EmailAddress":         email,
        "Password":             password,
        "ConfirmPassword":      password,
        "g-recaptcha-response": solved,
        "Recaptcha":            "",
        "ReceiveNewsletter":    "false",
    }

    req, err := makeFileUploadRequest("https://example.com", extraParams, "ProfileImage", profilePath)
    if err != nil {
        panic(err)
    }

    req.Header.Set("Host", "example.com")
    req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0")
    req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    req.Header.Set("Accept-Language", "en-US,en;q=0.5")
    req.Header.Set("Accept-Encoding", "gzip, deflate, br")
    req.Header.Set("Referer", "https://example.com")
    req.Header.Set("Connection", "keep-alive")
    req.Header.Set("Upgrade-Insecure-Requests", "1")

    client := createClient()
    resp, err := client.Do(req)
    defer resp.Body.Close()
    if err != nil {
        panic(err)
    } else {
        var bodyContent []byte
        fmt.Println(resp.StatusCode)
        fmt.Println(resp.Header)
        resp.Body.Read(bodyContent)
        resp.Body.Close()
        fmt.Println(bodyContent)
    }
    var acc account

    acc.email = email
    acc.password = password
    acc.username = username

    return acc
}

func makeFileUploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    fileContents, err := ioutil.ReadAll(file)
    if err != nil {
        return nil, err
    }
    fi, err := file.Stat()
    if err != nil {
        return nil, err
    }
    file.Close()

    body := new(bytes.Buffer)
    writer := multipart.NewWriter(body)

    for key, val := range params {
        _ = writer.WriteField(key, val)
    }
    err = writer.Close()
    if err != nil {
        return nil, err
    }

    part, err := writer.CreateFormFile(paramName, fi.Name())
    if err != nil {
        return nil, err
    }
    ht, _ := http.NewRequest("POST", uri, body)
    ht.Header.Set("Content-Type", writer.FormDataContentType())
    writer.Close()
    return ht, err
}           

Solution

  • The error is on these lines:

    ht, _ := http.NewRequest("POST", uri, body)
    ht.Header.Set("Content-Type", writer.FormDataContentType())
    writer.Close()
    

    The call to NewRequest recognizes that the body is a bytes.Buffer and sets the content length header using the current size of the buffer.

    The final multipart boundary is written to the request body in the call to writer.Close(). This boundary is not included in the content length set previously in the call to NewRequest.

    To fix the problem, close the writer before creating the request:

    writer.Close()
    ht, _ := http.NewRequest("POST", uri, body)
    ht.Header.Set("Content-Type", writer.FormDataContentType())