Add projects

This commit is contained in:
Alois 2026-08-24 00:10:41 +02:00
commit 8b607dd700
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
1802 changed files with 503346 additions and 2 deletions

View file

@ -0,0 +1,296 @@
// Package httpwire contains narrowly scoped HTTP/1.1 wire helpers.
package httpwire
import (
"bytes"
"fmt"
"io"
"net"
"strconv"
"strings"
"sync"
)
const maxBufferedRequestHeader = 1 << 20
// RequestHeaderOrder returns the desired header-name order for one HTTP/1.1
// request. Names are compared case-insensitively. Headers omitted from the
// returned list retain their original relative order after the listed headers.
type RequestHeaderOrder func(method, requestTarget string) []string
// NewOrderedRequestConn wraps conn and rewrites only HTTP/1.1 request-header
// order. Request lines, header casing and values, and body bytes remain intact.
func NewOrderedRequestConn(conn net.Conn, order RequestHeaderOrder) net.Conn {
if conn == nil || order == nil {
return conn
}
return &orderedRequestConn{Conn: conn, order: order}
}
type orderedRequestConn struct {
net.Conn
order RequestHeaderOrder
mu sync.Mutex
header []byte
bodyRemaining int64
chunked *chunkedRequestTracker
}
func (c *orderedRequestConn) Write(p []byte) (int, error) {
c.mu.Lock()
defer c.mu.Unlock()
originalLength := len(p)
consumed := 0
remaining := p
for len(remaining) > 0 {
if c.bodyRemaining > 0 {
bodyBytes := min(int64(len(remaining)), c.bodyRemaining)
written, errWrite := writeAll(c.Conn, remaining[:bodyBytes])
consumed += written
c.bodyRemaining -= int64(written)
if errWrite != nil {
return consumed, errWrite
}
remaining = remaining[bodyBytes:]
continue
}
if c.chunked != nil {
preview := c.chunked.clone()
chunkBytes, _, errChunk := preview.consume(remaining)
if errChunk != nil {
return consumed, errChunk
}
written, errWrite := writeAll(c.Conn, remaining[:chunkBytes])
consumed += written
_, completed, errConsume := c.chunked.consume(remaining[:written])
if errConsume != nil {
return consumed, errConsume
}
if completed {
c.chunked = nil
}
if errWrite != nil {
return consumed, errWrite
}
remaining = remaining[chunkBytes:]
continue
}
previousHeaderLength := len(c.header)
c.header = append(c.header, remaining...)
headerEnd := bytes.Index(c.header, []byte("\r\n\r\n"))
if headerEnd < 0 {
if len(c.header) > maxBufferedRequestHeader {
return consumed, fmt.Errorf("httpwire: request header exceeds %d bytes", maxBufferedRequestHeader)
}
return originalLength, nil
}
headerEnd += len("\r\n\r\n")
header := c.header[:headerEnd]
body := c.header[headerEnd:]
c.header = nil
currentHeaderBytes := min(len(remaining), max(0, headerEnd-previousHeaderLength))
ordered, contentLength, chunked := orderRequestHeader(header, c.order)
if _, errWrite := writeAll(c.Conn, ordered); errWrite != nil {
// All caller bytes were accepted into the wrapper before the transformed
// header write failed. Return the full input count with the terminal
// connection error so callers do not replay an ambiguous partial header.
return originalLength, errWrite
}
consumed += currentHeaderBytes
remaining = body
if chunked {
c.chunked = newChunkedRequestTracker()
continue
}
c.bodyRemaining = contentLength
}
return originalLength, nil
}
func orderRequestHeader(header []byte, order RequestHeaderOrder) ([]byte, int64, bool) {
lines := bytes.Split(header[:len(header)-len("\r\n\r\n")], []byte("\r\n"))
if len(lines) == 0 {
return header, 0, false
}
requestParts := strings.SplitN(string(lines[0]), " ", 3)
if len(requestParts) != 3 {
return header, requestContentLength(lines[1:]), requestUsesChunkedEncoding(lines[1:])
}
desired := order(requestParts[0], requestParts[1])
if len(desired) == 0 {
return header, requestContentLength(lines[1:]), requestUsesChunkedEncoding(lines[1:])
}
headerLines := lines[1:]
used := make([]bool, len(headerLines))
orderedLines := make([][]byte, 0, len(lines))
orderedLines = append(orderedLines, lines[0])
for _, name := range desired {
for index, line := range headerLines {
if used[index] || !headerLineNamed(line, name) {
continue
}
orderedLines = append(orderedLines, line)
used[index] = true
}
}
for index, line := range headerLines {
if !used[index] {
orderedLines = append(orderedLines, line)
}
}
var output bytes.Buffer
for _, line := range orderedLines {
output.Write(line)
output.WriteString("\r\n")
}
output.WriteString("\r\n")
return output.Bytes(), requestContentLength(headerLines), requestUsesChunkedEncoding(headerLines)
}
func headerLineNamed(line []byte, name string) bool {
colon := bytes.IndexByte(line, ':')
return colon > 0 && strings.EqualFold(string(line[:colon]), name)
}
func requestContentLength(lines [][]byte) int64 {
for _, line := range lines {
if !headerLineNamed(line, "Content-Length") {
continue
}
colon := bytes.IndexByte(line, ':')
value := strings.TrimSpace(string(line[colon+1:]))
length, errParse := strconv.ParseInt(value, 10, 64)
if errParse == nil && length > 0 {
return length
}
return 0
}
return 0
}
func requestUsesChunkedEncoding(lines [][]byte) bool {
for _, line := range lines {
if !headerLineNamed(line, "Transfer-Encoding") {
continue
}
colon := bytes.IndexByte(line, ':')
for _, encoding := range strings.Split(string(line[colon+1:]), ",") {
if strings.EqualFold(strings.TrimSpace(encoding), "chunked") {
return true
}
}
}
return false
}
type chunkedRequestTracker struct {
state uint8
line []byte
dataRemaining int64
crlfPosition int
trailers []byte
}
const (
chunkedReadingSize uint8 = iota
chunkedReadingData
chunkedReadingDataCRLF
chunkedReadingTrailers
)
func newChunkedRequestTracker() *chunkedRequestTracker {
return &chunkedRequestTracker{state: chunkedReadingSize}
}
func (tracker *chunkedRequestTracker) clone() *chunkedRequestTracker {
cloned := *tracker
cloned.line = append([]byte(nil), tracker.line...)
cloned.trailers = append([]byte(nil), tracker.trailers...)
return &cloned
}
func (tracker *chunkedRequestTracker) consume(data []byte) (consumed int, completed bool, err error) {
for consumed < len(data) {
switch tracker.state {
case chunkedReadingSize:
tracker.line = append(tracker.line, data[consumed])
consumed++
if len(tracker.line) > maxBufferedRequestHeader {
return consumed, false, fmt.Errorf("httpwire: chunk size line exceeds %d bytes", maxBufferedRequestHeader)
}
if len(tracker.line) < 2 || !bytes.Equal(tracker.line[len(tracker.line)-2:], []byte("\r\n")) {
continue
}
sizeText := strings.TrimSpace(string(tracker.line[:len(tracker.line)-2]))
if extension := strings.IndexByte(sizeText, ';'); extension >= 0 {
sizeText = strings.TrimSpace(sizeText[:extension])
}
size, errParse := strconv.ParseInt(sizeText, 16, 64)
if errParse != nil || size < 0 {
return consumed, false, fmt.Errorf("httpwire: invalid chunk size %q", sizeText)
}
tracker.line = tracker.line[:0]
if size == 0 {
tracker.state = chunkedReadingTrailers
continue
}
tracker.dataRemaining = size
tracker.state = chunkedReadingData
case chunkedReadingData:
chunkBytes := min(int64(len(data)-consumed), tracker.dataRemaining)
consumed += int(chunkBytes)
tracker.dataRemaining -= chunkBytes
if tracker.dataRemaining == 0 {
tracker.crlfPosition = 0
tracker.state = chunkedReadingDataCRLF
}
case chunkedReadingDataCRLF:
want := []byte("\r\n")
if data[consumed] != want[tracker.crlfPosition] {
return consumed, false, fmt.Errorf("httpwire: chunk data is missing CRLF terminator")
}
consumed++
tracker.crlfPosition++
if tracker.crlfPosition == len(want) {
tracker.state = chunkedReadingSize
}
case chunkedReadingTrailers:
tracker.trailers = append(tracker.trailers, data[consumed])
consumed++
if len(tracker.trailers) > maxBufferedRequestHeader {
return consumed, false, fmt.Errorf("httpwire: chunk trailers exceed %d bytes", maxBufferedRequestHeader)
}
if bytes.Equal(tracker.trailers, []byte("\r\n")) ||
(len(tracker.trailers) >= 4 && bytes.Equal(tracker.trailers[len(tracker.trailers)-4:], []byte("\r\n\r\n"))) {
return consumed, true, nil
}
default:
return consumed, false, fmt.Errorf("httpwire: invalid chunk parser state %d", tracker.state)
}
}
return consumed, false, nil
}
func writeAll(writer io.Writer, data []byte) (int, error) {
total := 0
for len(data) > 0 {
written, errWrite := writer.Write(data)
total += written
if errWrite != nil {
return total, errWrite
}
if written <= 0 {
return total, io.ErrShortWrite
}
data = data[written:]
}
return total, nil
}

View file

@ -0,0 +1,200 @@
package httpwire
import (
"bytes"
"errors"
"io"
"net"
"testing"
"time"
)
func TestOrderedRequestConnReordersKeepAliveRequestsWithoutChangingBodies(t *testing.T) {
t.Parallel()
client, server := net.Pipe()
t.Cleanup(func() {
if errClose := client.Close(); errClose != nil && !errors.Is(errClose, net.ErrClosed) {
t.Errorf("close client connection: %v", errClose)
}
if errClose := server.Close(); errClose != nil && !errors.Is(errClose, net.ErrClosed) {
t.Errorf("close server connection: %v", errClose)
}
})
conn := NewOrderedRequestConn(client, func(method, target string) []string {
if method == "POST" && target == "/v1/messages?beta=true" {
return []string{"Accept", "Authorization", "Content-Type", "User-Agent", "Connection", "Host", "Accept-Encoding", "Content-Length"}
}
return []string{"Accept", "Host", "Connection"}
})
firstInput := "POST /v1/messages?beta=true HTTP/1.1\r\nHost: api.anthropic.com\r\nUser-Agent: claude-cli/2.1.220 (external, cli)\r\nContent-Length: 7\r\nAccept: application/json\r\nX-Unknown: keep\r\nAuthorization: Bearer placeholder\r\nContent-Type: application/json\r\nConnection: keep-alive\r\nAccept-Encoding: gzip, deflate, br, zstd\r\n\r\n{\"a\":1}"
secondInput := "GET /api/oauth/profile HTTP/1.1\r\nConnection: close\r\nHost: api.anthropic.com\r\nAccept: application/json\r\n\r\n"
want := "POST /v1/messages?beta=true HTTP/1.1\r\nAccept: application/json\r\nAuthorization: Bearer placeholder\r\nContent-Type: application/json\r\nUser-Agent: claude-cli/2.1.220 (external, cli)\r\nConnection: keep-alive\r\nHost: api.anthropic.com\r\nAccept-Encoding: gzip, deflate, br, zstd\r\nContent-Length: 7\r\nX-Unknown: keep\r\n\r\n{\"a\":1}GET /api/oauth/profile HTTP/1.1\r\nAccept: application/json\r\nHost: api.anthropic.com\r\nConnection: close\r\n\r\n"
readDone := make(chan []byte, 1)
go func() {
if errDeadline := server.SetReadDeadline(time.Now().Add(5 * time.Second)); errDeadline != nil {
readDone <- nil
return
}
got := make([]byte, len(want))
if _, errRead := io.ReadFull(server, got); errRead != nil {
readDone <- nil
return
}
readDone <- got
}()
parts := [][]byte{
[]byte(firstInput[:29]),
[]byte(firstInput[29 : len(firstInput)-3]),
[]byte(firstInput[len(firstInput)-3:] + secondInput[:17]),
[]byte(secondInput[17:]),
}
for _, part := range parts {
written, errWrite := conn.Write(part)
if errWrite != nil {
t.Fatalf("write request bytes: %v", errWrite)
}
if written != len(part) {
t.Fatalf("write length = %d, want %d", written, len(part))
}
}
select {
case got := <-readDone:
if !bytes.Equal(got, []byte(want)) {
t.Fatalf("wire bytes differ\n got: %q\nwant: %q", got, want)
}
case <-time.After(5 * time.Second):
t.Fatal("timed out reading ordered request bytes")
}
}
func TestOrderedRequestConnPreservesChunkedBodyAndReordersNextRequest(t *testing.T) {
t.Parallel()
client, server := net.Pipe()
t.Cleanup(func() {
_ = client.Close()
_ = server.Close()
})
conn := NewOrderedRequestConn(client, func(_, _ string) []string { return []string{"Host", "Transfer-Encoding"} })
first := "POST /upload HTTP/1.1\r\nTransfer-Encoding: chunked\r\nHost: example.com\r\n\r\n4\r\ntest\r\n0\r\nX-Trailer: done\r\n\r\n"
second := "GET /next HTTP/1.1\r\nTransfer-Encoding: identity\r\nHost: example.com\r\n\r\n"
input := []byte(first + second)
want := []byte("POST /upload HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n4\r\ntest\r\n0\r\nX-Trailer: done\r\n\r\nGET /next HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: identity\r\n\r\n")
readDone := make(chan []byte, 1)
go func() {
got := make([]byte, len(want))
_, _ = io.ReadFull(server, got)
readDone <- got
}()
for index := range input {
part := input[index : index+1]
written, errWrite := conn.Write(part)
if errWrite != nil {
t.Fatal(errWrite)
}
if written != len(part) {
t.Fatalf("write length = %d, want %d", written, len(part))
}
}
if got := <-readDone; !bytes.Equal(got, want) {
t.Fatalf("chunked wire bytes differ\n got: %q\nwant: %q", got, want)
}
}
type partialErrorConn struct {
bytes.Buffer
failLimit int
failErr error
}
func (conn *partialErrorConn) Write(data []byte) (int, error) {
if conn.failErr == nil {
return conn.Buffer.Write(data)
}
written := min(conn.failLimit, len(data))
_, _ = conn.Buffer.Write(data[:written])
return written, conn.failErr
}
func (*partialErrorConn) Read([]byte) (int, error) { return 0, io.EOF }
func (*partialErrorConn) Close() error { return nil }
func (*partialErrorConn) LocalAddr() net.Addr { return nil }
func (*partialErrorConn) RemoteAddr() net.Addr { return nil }
func (*partialErrorConn) SetDeadline(time.Time) error { return nil }
func (*partialErrorConn) SetReadDeadline(time.Time) error { return nil }
func (*partialErrorConn) SetWriteDeadline(time.Time) error { return nil }
func TestOrderedRequestConnReportsPartialBodyWrite(t *testing.T) {
underlying := &partialErrorConn{}
conn := NewOrderedRequestConn(underlying, func(_, _ string) []string { return []string{"Host", "Content-Length"} })
header := []byte("POST /upload HTTP/1.1\r\nContent-Length: 5\r\nHost: example.com\r\n\r\n")
if written, errWrite := conn.Write(header); errWrite != nil || written != len(header) {
t.Fatalf("header write = %d, %v", written, errWrite)
}
underlying.failLimit = 2
injectedErr := errors.New("injected partial write")
underlying.failErr = injectedErr
written, errWrite := conn.Write([]byte("hello"))
if !errors.Is(errWrite, injectedErr) {
t.Fatalf("body write error = %v, want injected error", errWrite)
}
if written != 2 {
t.Fatalf("body write length = %d, want underlying partial count 2", written)
}
if remaining := conn.(*orderedRequestConn).bodyRemaining; remaining != 3 {
t.Fatalf("bodyRemaining = %d, want 3 after confirmed partial write", remaining)
}
underlying.failErr = nil
if written, errWrite = conn.Write([]byte("llo")); errWrite != nil || written != 3 {
t.Fatalf("retried body write = %d, %v", written, errWrite)
}
second := []byte("GET /next HTTP/1.1\r\nContent-Length: 0\r\nHost: example.com\r\n\r\n")
if written, errWrite = conn.Write(second); errWrite != nil || written != len(second) {
t.Fatalf("next request write = %d, %v", written, errWrite)
}
want := "POST /upload HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\n\r\nhelloGET /next HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n"
if got := underlying.String(); got != want {
t.Fatalf("wire bytes differ after retry\n got: %q\nwant: %q", got, want)
}
}
func TestOrderedRequestConnTracksOnlyWrittenChunkBytesAfterPartialError(t *testing.T) {
underlying := &partialErrorConn{}
conn := NewOrderedRequestConn(underlying, func(_, _ string) []string { return []string{"Host", "Transfer-Encoding"} })
header := []byte("POST /upload HTTP/1.1\r\nTransfer-Encoding: chunked\r\nHost: example.com\r\n\r\n")
if written, errWrite := conn.Write(header); errWrite != nil || written != len(header) {
t.Fatalf("header write = %d, %v", written, errWrite)
}
chunkedBody := []byte("4\r\ntest\r\n0\r\nX-Trailer: done\r\n\r\n")
underlying.failLimit = 6
injectedErr := errors.New("injected chunk partial write")
underlying.failErr = injectedErr
written, errWrite := conn.Write(chunkedBody)
if !errors.Is(errWrite, injectedErr) || written != 6 {
t.Fatalf("chunk write = %d, %v; want 6 and injected error", written, errWrite)
}
underlying.failErr = nil
if retried, errRetry := conn.Write(chunkedBody[written:]); errRetry != nil || retried != len(chunkedBody)-written {
t.Fatalf("retried chunk write = %d, %v", retried, errRetry)
}
second := []byte("GET /next HTTP/1.1\r\nTransfer-Encoding: identity\r\nHost: example.com\r\n\r\n")
if written, errWrite = conn.Write(second); errWrite != nil || written != len(second) {
t.Fatalf("next request write = %d, %v", written, errWrite)
}
want := "POST /upload HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n" + string(chunkedBody) +
"GET /next HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: identity\r\n\r\n"
if got := underlying.String(); got != want {
t.Fatalf("wire bytes differ after chunk retry\n got: %q\nwant: %q", got, want)
}
}