Skip to content

Commit a161af2

Browse files
fix: allow headers without whitespace after colon (#1741)
* Allows headers without whitespace after colon. * Makes headers faster. * Optimizes header splitting. * Formatting.
1 parent 23073b6 commit a161af2

3 files changed

Lines changed: 36 additions & 5 deletions

File tree

frankenphp.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -481,14 +481,43 @@ func go_apache_request_headers(threadIndex C.uintptr_t) (*C.go_string, C.size_t)
481481
}
482482

483483
func addHeader(fc *frankenPHPContext, cString *C.char, length C.int) {
484-
parts := strings.SplitN(C.GoStringN(cString, length), ": ", 2)
485-
if len(parts) != 2 {
486-
fc.logger.LogAttrs(context.Background(), slog.LevelDebug, "invalid header", slog.String("header", parts[0]))
487-
484+
key, val := splitRawHeader(cString, int(length))
485+
if key == "" {
486+
fc.logger.LogAttrs(context.Background(), slog.LevelDebug, "invalid header", slog.String("header", C.GoStringN(cString, length)))
488487
return
489488
}
489+
fc.responseWriter.Header().Add(key, val)
490+
}
491+
492+
// split the raw header coming from C with minimal allocations
493+
func splitRawHeader(rawHeader *C.char, length int) (string, string) {
494+
buf := unsafe.Slice((*byte)(unsafe.Pointer(rawHeader)), length)
495+
496+
// Search for the colon in 'Header-Key: value'
497+
var i int
498+
for i = 0; i < length; i++ {
499+
if buf[i] == ':' {
500+
break
501+
}
502+
}
503+
504+
if i == length {
505+
return "", "" // No colon found, invalid header
506+
}
507+
508+
headerKey := C.GoStringN(rawHeader, C.int(i))
509+
510+
// skip whitespaces after the colon
511+
j := i + 1
512+
for j < length && buf[j] == ' ' {
513+
j++
514+
}
515+
516+
// anything left is the header value
517+
valuePtr := (*C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(rawHeader)) + uintptr(j)))
518+
headerValue := C.GoStringN(valuePtr, C.int(length-j))
490519

491-
fc.responseWriter.Header().Add(parts[0], parts[1])
520+
return headerKey, headerValue
492521
}
493522

494523
//export go_write_headers

frankenphp_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ func testHeaders(t *testing.T, opts *testOptions) {
242242
assert.Equal(t, 201, resp.StatusCode)
243243
assert.Equal(t, "bar", resp.Header.Get("Foo"))
244244
assert.Equal(t, "bar2", resp.Header.Get("Foo2"))
245+
assert.Equal(t, "bar3", resp.Header.Get("Foo3"), "header without whitespace after colon")
245246
assert.Empty(t, resp.Header.Get("Invalid"))
246247
assert.Equal(t, fmt.Sprintf("%d", i), resp.Header.Get("I"))
247248
}, opts)

testdata/headers.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
return function () {
66
header('Foo: bar');
77
header('Foo2: bar2');
8+
header('Foo3:bar3'); // no space after colon (also valid, not recommended)
89
header('Invalid');
910
header('I: ' . ($_GET['i'] ?? 'i not set'));
1011
http_response_code(201);

0 commit comments

Comments
 (0)