fasthttp
fasthttp copied to clipboard
FastHTTP incorrectly handles obs-fold on the first line
When FastHTTP receives a request in which the first header line begins with spaces, it allows the spaces to persist into the header name. This is invalid, because spaces are not permitted in header names.
This can be confirmed by
- running a FastHTTP server that echoes header names (like this one),
- sending it a request with a header name prefixed with spaces, and extracting the echoed header name:
printf 'GET / HTTP/1.1\r\n Test: whatever\r\n\r\n' \
| nc localhost 80 \
| grep "headers" \
| jq '.["headers"][0][0]' \
| xargs echo \
| base64 -d \
| xxd
00000000: 2020 5465 7374 Test
Note that the spaces are still there in the header name.
The correct behavior in this scenario is to reject the request with a 400.
package main
import (
"encoding/json"
"fmt"
"log"
"regexp"
"github.com/valyala/fasthttp"
)
// validateHeaderName checks if a header name is valid per RFC 7230.
// Returns true if valid, false if invalid (e.g., contains spaces or invalid chars).
func validateHeaderName(name []byte) bool {
// RFC 7230 token: ALPHA / DIGIT / ! # $ % & ' * + - . ^ _ ` | ~
// We can use a regex or character check. Regex is simpler for clarity.
// Invalid if contains spaces, tabs, or non-token chars.
validHeaderName := regexp.MustCompile(`^[!#$%&'*+\-.^_\`|~0-9A-Za-z]+$`)
return validHeaderName.Match(name)
}
// requestHandler validates headers and processes the request.
func requestHandler(ctx *fasthttp.RequestCtx) {
// Validate all header names
ctx.Request.Header.VisitAll(func(key, value []byte) {
if !validateHeaderName(key) {
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetContentType("text/plain")
fmt.Fprintf(ctx, "Invalid header name: %q contains spaces or invalid characters", key)
return
}
})
// If we reached here, headers are valid (or response was already set)
if ctx.Response.StatusCode() == fasthttp.StatusBadRequest {
return
}
// Echo headers as JSON for valid requests (mimicking user's test case)
headers := make([][]string, 0)
ctx.Request.Header.VisitAll(func(key, value []byte) {
headers = append(headers, []string{string(key), string(value)})
})
response := map[string][][]string{
"headers": headers,
}
ctx.SetContentType("application/json")
ctx.SetStatusCode(fasthttp.StatusOK)
if err := json.NewEncoder(ctx).Encode(response); err != nil {
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
fmt.Fprintf(ctx, "Failed to encode response: %v", err)
return
}
}
func main() {
// Create FastHTTP server
server := &fasthttp.Server{
Handler: requestHandler,
Name: "TestServer",
}
// Start server on port 80
if err := server.ListenAndServe(":80"); err != nil {
log.Fatalf("Error starting server: %v", err)
}
}
Of course you could always glue the validation on top. The issue is that this is not handled automatically, as it is in nearly all other HTTP libraries.