-
Notifications
You must be signed in to change notification settings - Fork 18k
net/http: should not reuse body following redirects after method is changed to GET #70180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
please include a reproducer with server code |
ok, will try to build snippet for the server side |
Hi, Here is the server like (this is not the origin but this how it behave) package main
import (
"fmt"
"net/http"
)
const paramName = "token"
const baseRedirectionUrlTemplate = "http://%v/location?token=%v"
func getHostnameFromHttpRequest(request *http.Request) string {
host := request.Host
//request.Host is empty on redirected requests
if host == "" {
host = request.URL.Host
}
return host
}
// Handler for the initial POST request
func handleLocation(w http.ResponseWriter, r *http.Request) {
host := getHostnameFromHttpRequest(r)
if r.ContentLength > 0 {
w.Header().Set("Location", fmt.Sprintf(baseRedirectionUrlTemplate, host, "1"))
w.WriteHeader(http.StatusFound)
return
}
value := r.URL.Query().Get(paramName)
switch value {
case "1":
w.Header().Set("Location", fmt.Sprintf(baseRedirectionUrlTemplate, host, "2"))
w.WriteHeader(http.StatusMovedPermanently)
case "2":
w.Header().Set("Location", fmt.Sprintf(baseRedirectionUrlTemplate, host, "3"))
w.WriteHeader(http.StatusPermanentRedirect)
case "3":
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(w, "Finally you complete the redirections")
default:
w.WriteHeader(http.StatusNotFound)
}
}
func main() {
// Set up the routes
http.HandleFunc("/location", handleLocation)
// Start the server
fmt.Println("Server is running on port 8081...")
if err := http.ListenAndServe(":8081", nil); err != nil {
fmt.Println("Error starting server:", err)
}
} It worked with curl (I used the -x http://localhost:8080 to see the conversation on local proxy like Fiddler or Burp)
Or you can use Python (also here I've used proxy to see the conversation) import requests
# URL and data
url = "http://localhost:8081/location"
data = {
"emplid": 333333333,
"userpassword": "Trinet@123"
}
# Proxy configuration
proxies = {
"http": "http://localhost:8080",
"https": "http://localhost:8080"
}
# Sending the POST request
response = requests.post(url, json=data, proxies=proxies)
# Printing the response
print("Status Code:", response.status_code)
print("Response Body:", response.text) |
But if you use golang you will fail with this error package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
)
func main() {
// Define the URL and JSON body
targetURL := "http://localhost:8081/location"
jsonBody := map[string]interface{}{
"emplid": 333333333,
"userpassword": "Trinet@123",
}
// Convert JSON body to bytes
jsonData, err := json.Marshal(jsonBody)
if err != nil {
fmt.Println("Error marshalling JSON:", err)
return
}
// Set up the proxy
proxyURL, err := url.Parse("http://localhost:8080")
if err != nil {
fmt.Println("Error parsing proxy URL:", err)
return
}
// Create transport with the proxy
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
// Create an HTTP client with the transport
jar, _ := cookiejar.New(nil)
client := &http.Client{
Transport: transport,
Jar: jar,
}
// Create a new POST request
req, err := http.NewRequest(http.MethodPost, targetURL, bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
// Set the content type to application/json
req.Header.Set("Content-Type", "application/json")
// Send the request
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer func() {
if err = resp.Body.Close(); err != nil {
fmt.Println(err)
}
}()
// Print the response status
fmt.Println("Response status:", resp.Status)
}
`Error sending request: Post "http://localhost:8081/location?token=1": stopped after 10 redirects` |
cc @neild I've modified the client invocations so they should be doing the same thing. curl:
curl request log
go go clientpackage main
import (
"context"
"fmt"
"net/http"
"strings"
)
func main() {
// Define the URL and JSON body
targetURL := "http://localhost:8081/location"
// Create an HTTP client with the transport
client := &http.Client{}
ctx := context.Background()
// Create a new POST request
req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, strings.NewReader("request body"))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
// Set the content type to application/json
req.Header.Set("Content-Type", "application/json")
// Send the request
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer func() {
if err = resp.Body.Close(); err != nil {
fmt.Println(err)
}
}()
// Print the response status
fmt.Println("Response status:", resp.Status)
} go request log
|
To summarize:
This behavior is specified at: https://fetch.spec.whatwg.org/#http-redirect-fetch When the net/http client follows a chain of redirects (for example, a 301 leading to a 308), it handles a 308 by sending the original request rather than the modified one:
Seems like a bug. Edit: I described the current behavior incorrectly; after the 308, we send a GET, not a POST. The problem is that the GET includes a body. |
It looks like right now it's:
|
Change https://go.dev/cl/626055 mentions this issue: |
@seankhliao do you know when this fix will be shipped? |
1.24 ~ next February |
Should this be treated as a security issue? Consider e.g. the following scenario:
|
Go version
1.23.2
Output of
go env
in your module/workspace:What did you do?
Using simple http client to execute POST with body the server response with series of redirect and enter loop (till reached to 10th redirects).
I used Burp (Fiddler like) to see the communication b/w the client and server.
What did you see happen?
The go client received several redirect responses
302 -> 301 -> 308 -> 302 -> 301 -> 308... until it reached to the 10th redirects.
What did you expect to see?
It was supposed to stop after the 308.
The reason is simple it should send the last one w/o body and Content Length.
Expected behavior of handling HTTP 308 redirection is that the client must repeat the same type of request (either POST or GET) on the target location. And it works properly when server responds with HTTP 308 on POST request (the redirected request will be also POST with the same body and only different URL following value of Location header).

In our case the flow is different - after several "normal" redirections (following HTTP 302 and HTTP 301) client receives HTTP 308 response and following the logic should respond with the same method as in previous request (that was GET without body) to the new location.
This way it works in browsers.
Thus the handling of HTTP 308 response in our (and GO) code should refer the request that responded with HTTP 308 and not the initial one ("first") in the redirections sequence.
I've checked with curl...
Curl
Go
Also tested with Python and Java and they (Curl, Java and Python received 200 OK)
So you should check that.. there is something wrong with the redirections...
The text was updated successfully, but these errors were encountered: