httpmsg

package
v0.5.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package httpmsg is the HTTP request/response model that flows through the scan. It parses and serializes raw requests and responses, models the associated service (host/port/scheme), and extracts insertion points (query/path/header/cookie/body, including nested JSON and form fields) that active modules mutate to inject payloads. It is foundational: most other packages depend on it.

Example (AnalyzeRequest_Combined)

Example demonstrating analyzing a complex request with multiple parameter types

package main

import (
	"fmt"
	"sort"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("POST /api/search?category=books&limit=10 HTTP/1.1\r\n" +
		"Host: api.example.com\r\n" +
		"Cookie: session=xyz789; lang=en\r\n" +
		"Content-Type: application/json\r\n" +
		"\r\n" +
		`{"query":"golang","sort":"relevance"}`)

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Method: %s\n", info.Method)
	fmt.Printf("URL: %s\n", info.URL)
	fmt.Printf("Total Parameters: %d\n", len(info.Parameters))

	// Extract different parameter types
	urlParams := info.ParametersByType(httpmsg.ParamURL)
	cookieParams := info.ParametersByType(httpmsg.ParamCookie)
	jsonParams := info.ParametersByType(httpmsg.ParamJSON)

	fmt.Printf("\nURL Parameters (%d):\n", len(urlParams))
	for _, param := range urlParams {
		fmt.Printf("  %s=%s\n", param.Name(), param.Value())
	}

	fmt.Printf("\nCookie Parameters (%d):\n", len(cookieParams))
	for _, param := range cookieParams {
		fmt.Printf("  %s=%s\n", param.Name(), param.Value())
	}

	// Sort JSON parameters by name for consistent output
	sort.Slice(jsonParams, func(i, j int) bool {
		return jsonParams[i].Name() < jsonParams[j].Name()
	})

	fmt.Printf("\nJSON Parameters (%d):\n", len(jsonParams))
	for _, param := range jsonParams {
		fmt.Printf("  %s=%s\n", param.Name(), param.Value())
	}

}
Output:
Method: POST
URL: /api/search?category=books&limit=10
Total Parameters: 8

URL Parameters (2):
  category=books
  limit=10

Cookie Parameters (2):
  session=xyz789
  lang=en

JSON Parameters (2):
  query=golang
  sort=relevance
Example (AnalyzeRequest_EmptyBody)

Example demonstrating handling requests without body

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET /api/status HTTP/1.1\r\n" +
		"Host: api.example.com\r\n" +
		"\r\n")

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Method: %s\n", info.Method)
	fmt.Printf("Has Body: %t\n", info.HasBody)
	fmt.Printf("Body Offset: %d\n", info.BodyOffset)
	fmt.Printf("Request Length: %d\n", len(request))
	fmt.Printf("Parameters: %d\n", len(info.Parameters))

}
Output:
Method: GET
Has Body: false
Body Offset: 51
Request Length: 51
Parameters: 2
Example (AnalyzeRequest_GET)

Example demonstrating analyzing a simple GET request

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET /api/users?id=123&name=john HTTP/1.1\r\n" +
		"Host: api.example.com\r\n" +
		"User-Agent: TestClient/1.0\r\n" +
		"\r\n")

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Method: %s\n", info.Method)
	fmt.Printf("URL: %s\n", info.URL)
	fmt.Printf("HTTP Version: %d\n", info.HTTPVersion)
	fmt.Printf("Headers: %d\n", len(info.Headers))

	// Extract URL parameters
	urlParams := info.ParametersByType(httpmsg.ParamURL)
	fmt.Printf("URL Parameters: %d\n", len(urlParams))
	for _, param := range urlParams {
		fmt.Printf("  %s=%s\n", param.Name(), param.Value())
	}

}
Output:
Method: GET
URL: /api/users?id=123&name=john
HTTP Version: 11
Headers: 3
URL Parameters: 2
  id=123
  name=john
Example (AnalyzeRequest_HTTPVersions)

Example demonstrating HTTP version detection

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	requests := [][]byte{
		[]byte("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"),
		[]byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"),
		[]byte("GET / HTTP/2.0\r\nHost: example.com\r\n\r\n"),
		[]byte("GET / HTTP/2\r\nHost: example.com\r\n\r\n"),
	}

	for _, req := range requests {
		info, err := httpmsg.AnalyzeRequest(req)
		if err != nil {
			continue
		}
		fmt.Printf("Request line: %s → HTTP Version: %d\n", info.Headers[0], info.HTTPVersion)
	}

}
Output:
Request line: GET / HTTP/1.0 → HTTP Version: 10
Request line: GET / HTTP/1.1 → HTTP Version: 11
Request line: GET / HTTP/2.0 → HTTP Version: 20
Request line: GET / HTTP/2 → HTTP Version: 20
Example (AnalyzeRequest_HelperMethods)

Example demonstrating using RequestInfo helper methods

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("POST /api/update?id=123 HTTP/1.1\r\n" +
		"Host: api.example.com\r\n" +
		"Cookie: session=abc123\r\n" +
		"Content-Type: application/json\r\n" +
		"\r\n" +
		`{"status":"active"}`)

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	// Using GetHeader method
	host := info.Header("Host")
	fmt.Printf("Host: %s\n", host)

	contentType := info.Header("Content-Type")
	fmt.Printf("Content-Type: %s\n", contentType)

	// Using GetParameter method
	idParam := info.Parameter("id")
	if idParam != nil {
		fmt.Printf("ID Parameter: %s=%s (type: %d)\n", idParam.Name(), idParam.Value(), idParam.Type())
	}

	// Using HasParameter method
	hasSession := info.HasParameter("session")
	fmt.Printf("Has session cookie: %t\n", hasSession)

	hasPassword := info.HasParameter("password")
	fmt.Printf("Has password param: %t\n", hasPassword)

}
Output:
Host: api.example.com
Content-Type: application/json
ID Parameter: id=123 (type: 0)
Has session cookie: true
Has password param: false
Example (AnalyzeRequest_JSON)

Example demonstrating analyzing a POST request with JSON body

package main

import (
	"fmt"
	"sort"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("POST /api/users HTTP/1.1\r\n" +
		"Host: api.example.com\r\n" +
		"Content-Type: application/json\r\n" +
		"\r\n" +
		`{"name":"Alice","age":30,"active":true}`)

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Method: %s\n", info.Method)
	fmt.Printf("Content-Type: %d\n", info.ContentType)

	// Extract JSON parameters
	jsonParams := info.ParametersByType(httpmsg.ParamJSON)
	fmt.Printf("JSON Parameters: %d\n", len(jsonParams))

	// Sort parameters by name for consistent output
	sort.Slice(jsonParams, func(i, j int) bool {
		return jsonParams[i].Name() < jsonParams[j].Name()
	})

	for _, param := range jsonParams {
		fmt.Printf("  %s=%s\n", param.Name(), param.Value())
	}

}
Output:
Method: POST
Content-Type: 4
JSON Parameters: 3
  active=true
  age=30
  name=Alice
Example (AnalyzeRequest_POST)

Example demonstrating analyzing a POST request with URL-encoded body

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("POST /api/login HTTP/1.1\r\n" +
		"Host: api.example.com\r\n" +
		"Content-Type: application/x-www-form-urlencoded\r\n" +
		"\r\n" +
		"username=admin&password=secret123")

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Method: %s\n", info.Method)
	fmt.Printf("URL: %s\n", info.URL)
	fmt.Printf("Content-Type: %d\n", info.ContentType)
	fmt.Printf("Has Body: %t\n", info.HasBody)

	// Extract body parameters
	bodyParams := info.ParametersByType(httpmsg.ParamBody)
	fmt.Printf("Body Parameters: %d\n", len(bodyParams))
	for _, param := range bodyParams {
		fmt.Printf("  %s=%s\n", param.Name(), param.Value())
	}

}
Output:
Method: POST
URL: /api/login
Content-Type: 1
Has Body: true
Body Parameters: 2
  username=admin
  password=secret123
Example (AnalyzeRequest_ParameterOffsets)

Example demonstrating accessing parameter byte offsets

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("POST /api HTTP/1.1\r\n" +
		"Content-Type: application/x-www-form-urlencoded\r\n" +
		"\r\n" +
		"username=alice&password=secret")

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	bodyParams := info.ParametersByType(httpmsg.ParamBody)
	for _, param := range bodyParams {
		fmt.Printf("Parameter: %s\n", param.Name())
		fmt.Printf("  Name offsets: [%d:%d]\n", param.NameStart(), param.NameEnd())
		fmt.Printf("  Value offsets: [%d:%d]\n", param.ValueStart(), param.ValueEnd())
		fmt.Printf("  Actual name bytes: %s\n", string(request[param.NameStart():param.NameEnd()]))
		fmt.Printf("  Actual value bytes: %s\n", string(request[param.ValueStart():param.ValueEnd()]))
	}

}
Output:
Parameter: username
  Name offsets: [71:79]
  Value offsets: [80:85]
  Actual name bytes: username
  Actual value bytes: alice
Parameter: password
  Name offsets: [86:94]
  Value offsets: [95:101]
  Actual name bytes: password
  Actual value bytes: secret
Example (AnalyzeRequest_WithCookies)

Example demonstrating analyzing a request with cookies

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET /api/profile HTTP/1.1\r\n" +
		"Host: api.example.com\r\n" +
		"Cookie: session_id=abc123xyz; user_id=456; preferences=dark_mode\r\n" +
		"\r\n")

	info, err := httpmsg.AnalyzeRequest(request)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Method: %s\n", info.Method)
	fmt.Printf("URL: %s\n", info.URL)

	// Extract cookie parameters
	cookieParams := info.ParametersByType(httpmsg.ParamCookie)
	fmt.Printf("Cookie Parameters: %d\n", len(cookieParams))
	for _, param := range cookieParams {
		fmt.Printf("  %s=%s\n", param.Name(), param.Value())
	}

}
Output:
Method: GET
URL: /api/profile
Cookie Parameters: 3
  session_id=abc123xyz
  user_id=456
  preferences=dark_mode
Example (CaseInsensitiveComparison)

Example_caseInsensitiveComparison demonstrates case-insensitive string comparison

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	result1 := httpmsg.EqualsCaseInsensitive("Content-Type", "content-type")
	result2 := httpmsg.EqualsCaseInsensitive("Host", "HOST")
	result3 := httpmsg.EqualsCaseInsensitive("Accept", "Reject")

	fmt.Println("Content-Type == content-type:", result1)
	fmt.Println("Host == HOST:", result2)
	fmt.Println("Accept == Reject:", result3)

}
Output:
Content-Type == content-type: true
Host == HOST: true
Accept == Reject: false
Example (ExtractAllHeaders)

Example_extractAllHeaders demonstrates the convenience function

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET /api/users HTTP/1.1\r\nHost: api.example.com\r\nContent-Type: application/json\r\n\r\n{\"user\":\"test\"}")

	headers, offsets, bodyStart, _ := httpmsg.ExtractAllHeaders(request)

	fmt.Println("Request line:", headers[0])
	fmt.Println("Header count:", len(headers))
	fmt.Println("Body starts at:", bodyStart)
	fmt.Println("First header offset:", offsets[0])

}
Output:
Request line: GET /api/users HTTP/1.1
Header count: 3
Body starts at: 82
First header offset: 0
Example (ExtractHeaders)

Example_extractHeaders demonstrates extracting HTTP headers with byte offsets

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0\r\n\r\nBODY")

	// Extract headers manually
	bodyStart := httpmsg.FindHeaderBodySeparator(request, 0)
	headers, offsets, _ := httpmsg.ExtractHeaders(request, 0, bodyStart)

	fmt.Println("Headers:")
	for i, header := range headers {
		fmt.Printf("  [%d] Offset %d: %s\n", i, offsets[i], header)
	}

}
Output:
Headers:
  [0] Offset 0: GET / HTTP/1.1
  [1] Offset 16: Host: example.com
  [2] Offset 35: User-Agent: Mozilla/5.0
Example (FindHeaderBodySeparator)

Example_findHeaderBodySeparator demonstrates finding where body begins

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\nBODY DATA HERE")

	bodyStart := httpmsg.FindHeaderBodySeparator(request, 0)

	if bodyStart != -1 {
		body := string(request[bodyStart:])
		fmt.Println("Body:", body)
	}

}
Output:
Body: BODY DATA HERE
Example (GetHeader)

Example_getHeader demonstrates case-insensitive header lookup

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	headers := []string{
		"POST /upload HTTP/1.1",
		"Host: example.com",
		"Content-Type: multipart/form-data; boundary=----WebKit",
		"Content-Length: 1234",
	}

	// Case-insensitive lookup
	host := httpmsg.Header(headers, "host")
	contentType := httpmsg.Header(headers, "CONTENT-TYPE")
	missing := httpmsg.Header(headers, "Authorization")

	fmt.Println("Host:", host)
	fmt.Println("Content-Type:", contentType)
	fmt.Println("Missing header:", missing)

}
Output:
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKit
Missing header:
Example (HeaderOffsets)

Example_headerOffsets demonstrates using header offsets for modification

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n")

	headers, offsets, _ := httpmsg.ExtractHeaders(request, 0, len(request))

	// Find Content-Length header
	for i, header := range headers {
		if httpmsg.EqualsCaseInsensitive(header[:len("Content-Length")], "Content-Length") {
			fmt.Printf("Found at offset %d: %s\n", offsets[i], header)
		}
	}

}
Output:
Found at offset 35: Content-Length: 0
Example (LfLineEndings)

Example_lfLineEndings demonstrates handling LF-only line endings

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET / HTTP/1.1\nHost: example.com\n\nBODY")

	bodyStart := httpmsg.FindHeaderBodySeparator(request, 0)
	headers, _, _ := httpmsg.ExtractHeaders(request, 0, bodyStart)

	fmt.Println("Headers extracted:", len(headers))

}
Output:
Headers extracted: 2
Example (MultipleHeaders)

Example_multipleHeaders demonstrates working with multiple headers

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	headers := []string{
		"GET /api HTTP/1.1",
		"Host: api.example.com",
		"Accept: application/json",
		"Accept-Language: en-US,en;q=0.9",
		"User-Agent: CustomClient/1.0",
	}

	// Extract specific headers
	host := httpmsg.Header(headers, "Host")
	accept := httpmsg.Header(headers, "Accept")
	userAgent := httpmsg.Header(headers, "User-Agent")

	fmt.Println("Host:", host)
	fmt.Println("Accept:", accept)
	fmt.Println("User-Agent:", userAgent)

}
Output:
Host: api.example.com
Accept: application/json
User-Agent: CustomClient/1.0
Example (ParseContentType)

Example_parseContentType demonstrates parsing Content-Type header

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	headers := []string{
		"POST /upload HTTP/1.1",
		"Host: example.com",
		"Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
	}

	mimeType, boundary := httpmsg.ParseContentType(headers)

	fmt.Println("MIME type:", mimeType)
	fmt.Println("Boundary:", boundary)

}
Output:
MIME type: multipart/form-data
Boundary: ----WebKitFormBoundary7MA4YWxkTrZu0gW
Example (ParseContentTypeSimple)

Example_parseContentTypeSimple demonstrates simple Content-Type without parameters

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	headers := []string{
		"POST /api HTTP/1.1",
		"Content-Type: application/json",
	}

	mimeType, boundary := httpmsg.ParseContentType(headers)

	fmt.Println("MIME type:", mimeType)
	fmt.Println("Has boundary:", boundary != "")

}
Output:
MIME type: application/json
Has boundary: false

Index

Examples

Constants

View Source
const (
	// HTTP line ending bytes
	CR = byte(13) // \r (0x0D)
	LF = byte(10) // \n (0x0A)
)
View Source
const (
	EncoderNoop       = iota // Passthrough encoder (no encoding)
	EncoderJSONString        // Escapes only double quotes for JSON string values
	EncoderJSONEscape        // Full JSON escaping including control characters
)

Encoder type constants

View Source
const (
	COLON = 0x3A // Colon :
	SPACE = 0x20 // Space
	TAB   = 0x09 // Tab
	SEMI  = 0x3B // Semicolon ;
	EQ    = 0x3D // Equals =
)

Byte constants for parsing Note: CR and LF are defined in byte_utils.go

View Source
const (
	// UserAgentPreset selects the self-identifying Vigolium string. Default.
	UserAgentPreset = "preset"
	// UserAgentRandom rotates a realistic browser string per request. A blank
	// value ("") behaves identically.
	UserAgentRandom = "random"
)

User-Agent selector keywords accepted by the scanning_strategy.http.user_agent config value and the VIGOLIUM_DEFAULT_UA environment variable. Matched case-insensitively.

View Source
const (
	XMLElementTypeOpen      byte = 0 // Opening tag: <tag>
	XMLElementTypeClose     byte = 1 // Closing tag: </tag>
	XMLElementTypeComment   byte = 2 // Comment: <!-- -->
	XMLElementTypeText      byte = 3 // Text content between tags
	XMLElementTypeSelfClose byte = 4 // Self-closing tag: <tag/> or <?xml?>
)

XMLElementType represents the type of XML element during parsing.

View Source
const (
	XMLQuoteDouble   byte = 0 // Double quote: "
	XMLQuoteSingle   byte = 1 // Single quote: '
	XMLQuoteBacktick byte = 2 // Backtick: `
	XMLQuoteNone     byte = 3 // No quote (unquoted value)
)

XMLQuoteType represents the type of quote used for attribute values.

View Source
const BuiltinUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"

BuiltinUserAgent is a realistic Chrome User-Agent string. It is one entry in the random-rotation pool and a concrete fallback for callers that need a browser string directly. It is no longer the default — see PresetUserAgent.

View Source
const DefaultTargetScheme = "http"

DefaultTargetScheme is the scheme a schemeless scan target is read as. http keeps the normalized form byte-identical to the request GetRawRequestFromURL already built for a bare host, so nothing that works today changes shape.

View Source
const DefaultUserAgentEnvVar = "VIGOLIUM_DEFAULT_UA"

DefaultUserAgentEnvVar overrides the configured User-Agent from the environment. When present it wins over scanning_strategy.http.user_agent and accepts the same values: "preset", "random", "" (blank == random), or any literal ({version} still expands).

View Source
const PresetUserAgent = "Mozilla/5.0 (compatible; Vigolium/{version}; +https://github.com/vigolium/vigolium)"

PresetUserAgent is the honest, self-identifying User-Agent Vigolium sends by default. The {version} placeholder is replaced with the running binary version at request time so the value stays correct across upgrades. Selected via the "preset" keyword (the default) or any literal containing {version}.

Variables

View Source
var DefaultBrowserHeaders = map[string]string{
	"Cache-Control":      "max-age=0",
	"Sec-Ch-Ua":          `"Google Chrome";v="143", "Not=A?Brand";v="8", "Chromium";v="143"`,
	"Sec-Ch-Ua-Mobile":   "?0",
	"Sec-Ch-Ua-Platform": `"macOS"`,
	"Accept-Language":    "en-US;q=0.9,en;q=0.8",
	"User-Agent":         BuiltinUserAgent,
	"Accept":             "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
	"Sec-Fetch-Site":     "none",
	"Sec-Fetch-Mode":     "navigate",
	"Sec-Fetch-User":     "?1",
	"Sec-Fetch-Dest":     "document",
	"Accept-Encoding":    "gzip, deflate, br",
}

DefaultBrowserHeaders are headers that mimic a real Chrome browser. Applied if not already present in the request.

View Source
var DefaultBrowserHeadersOrder = []string{
	"Cache-Control",
	"Sec-Ch-Ua",
	"Sec-Ch-Ua-Mobile",
	"Sec-Ch-Ua-Platform",
	"Accept-Language",
	"User-Agent",
	"Accept",
	"Sec-Fetch-Site",
	"Sec-Fetch-Mode",
	"Sec-Fetch-User",
	"Sec-Fetch-Dest",
	"Accept-Encoding",
}

DefaultBrowserHeadersOrder defines the canonical order for browser headers. This ensures requests look natural/realistic.

View Source
var ErrNilService = &nilServiceError{}

Sentinel error

Functions

func AddHeader

func AddHeader(message []byte, name, value string) ([]byte, error)

AddHeader adds a new header to an HTTP message.

Algorithm:

  1. Find header end position (before separator)
  2. Write message up to that position
  3. Write new header bytes
  4. Write CRLF
  5. Write rest of message from that position onward
  6. Return new message

Parameters:

  • message: Original HTTP message
  • name: Header name
  • value: Header value

Returns:

  • Modified HTTP message with new header
  • Error if message is malformed

Example:

request := []byte("GET / HTTP/1.1\r\n\r\n")
modified, _ := AddHeader(request, "Host", "example.com")
// Returns: "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"

func AddHeaderIfNotExists

func AddHeaderIfNotExists(request []byte, name, value string) ([]byte, error)

AddHeaderIfNotExists adds a header only if it doesn't already exist.

Algorithm:

  1. Check if header exists
  2. If not exists, add header
  3. If exists, return unchanged

Parameters:

  • request: HTTP request bytes
  • name: Header name
  • value: Header value

Returns:

  • Modified request (unchanged if header exists)
  • Error if request malformed

Example:

modified, _ := AddHeaderIfNotExists(request, "Accept", "application/json")

func AddMultipleParameters

func AddMultipleParameters(request []byte, params []*Param) ([]byte, error)

AddMultipleParameters adds multiple parameters at once. Provides batch add operation.

Parameters:

  • request: HTTP request bytes
  • params: Parameters to add

Returns:

  • Modified request with all parameters added
  • Error if request malformed

Example:

params := []*Param{BuildParameter("a", "1", ParamURL), BuildParameter("b", "2", ParamURL)}
modified, _ := AddMultipleParameters(request, params)

func AddOrReplaceHeader

func AddOrReplaceHeader(request []byte, name, value string) ([]byte, error)

AddOrReplaceHeader adds header if not exists, replaces if exists.

Algorithm:

  1. Check if header exists
  2. If exists, replace; if not, add

Parameters:

  • request: HTTP request bytes
  • name: Header name
  • value: Header value

Returns:

  • Modified request with header set
  • Error if request malformed

Example:

modified, _ := AddOrReplaceHeader(request, "Authorization", "Bearer token")

func AddParameter

func AddParameter(request []byte, param *Param) ([]byte, error)

AddParameter adds a parameter to an HTTP request.

Algorithm:

  1. Analyze request to get current parameters and structure
  2. Determine where to add parameter based on type
  3. For ParamURL: Add to query string
  4. For ParamBody: Add to request body (URL-encoded or multipart)
  5. For ParamCookie: Add to Cookie header
  6. Rebuild request with new parameter
  7. Update Content-Length if body changed

Parameters:

  • request: Original HTTP request
  • param: Parameter to add (with Name(), Value(), Type())

Returns:

  • Modified request with parameter added
  • Error if request is malformed or parameter type unsupported

Example:

param := BuildParameter("token", "xyz", ParamURL)
modified, _ := AddParameter(request, param)

func AppendBody

func AppendBody(request []byte, data []byte) ([]byte, error)

AppendBody appends data to the existing request body. Provides body append operation.

Algorithm:

  1. Get current body
  2. Append new data
  3. Set combined body

Parameters:

  • request: HTTP request bytes
  • data: Data to append

Returns:

  • Modified request with appended body
  • Error if request malformed

Example:

modified, _ := AppendBody(request, []byte("&extra=data"))

func AppendBodyParameter

func AppendBodyParameter(request []byte, name, value string) ([]byte, error)

AppendBodyParameter adds a body parameter without removing existing ones. Provides append operation.

Parameters:

  • request: HTTP request bytes
  • name: Parameter name
  • value: Parameter value

Returns:

  • Modified request with appended parameter
  • Error if request malformed

Example:

modified, _ := AppendBodyParameter(request, "debug", "true")

func AppendToHeader

func AppendToHeader(request []byte, name, appendValue string) ([]byte, error)

AppendToHeader appends a value to an existing header. If header doesn't exist, returns request unchanged.

Algorithm:

  1. Find header offsets using GetHeaderOffsets
  2. If header not found, return unchanged
  3. Insert append value at the end of header value
  4. Return modified request

Parameters:

  • request: HTTP request bytes
  • name: Header name to append to
  • appendValue: Value to append (e.g., ", text/html")

Returns:

  • Modified request with appended header value
  • Error if request malformed

Example:

req, _ := AppendToHeader(request, "Accept", ", text/html")
// "Accept: text/plain" → "Accept: text/plain, text/html"

func AppendToPath

func AppendToPath(request []byte, segment string) ([]byte, error)

AppendToPath appends a segment to path (before query string).

Algorithm:

  1. Get current path without query
  2. Get query string if present
  3. Append segment to path
  4. Reattach query string
  5. Return modified request

Parameters:

  • request: HTTP request bytes
  • segment: Path segment to append (e.g., "/extra")

Returns:

  • Modified request with appended path
  • Error if request malformed

Example:

req, _ := AppendToPath(request, "/extra")
// "/api?id=1" → "/api/extra?id=1"

func AppendURLParameter

func AppendURLParameter(request []byte, name, value string) ([]byte, error)

AppendURLParameter adds a URL parameter without removing existing ones. Provides append operation.

Algorithm:

  1. Get current query string
  2. URL-encode new parameter
  3. Append with '&' separator if query exists
  4. Call SetQueryString

Parameters:

  • request: HTTP request bytes
  • name: Parameter name
  • value: Parameter value

Returns:

  • Modified request with appended parameter
  • Error if request malformed

Example:

modified, _ := AppendURLParameter(request, "debug", "true")
// For "?id=123" creates "?id=123&debug=true"

func Base64Decode

func Base64Decode(s string) ([]byte, error)

Base64Decode decodes a Base64-encoded string.

Example:

Base64Decode("aGVsbG8=")         // Returns []byte("hello"), nil
Base64Decode("aGVsbG8gd29ybGQ=") // Returns []byte("hello world"), nil
Base64Decode("invalid!")         // Returns nil, error

Parameters:

  • s: Base64-encoded string to decode

Returns:

  • Decoded bytes
  • Error if decoding fails

func Base64DecodeBytes

func Base64DecodeBytes(data []byte) ([]byte, error)

Base64DecodeBytes decodes Base64-encoded bytes.

Example:

Base64DecodeBytes([]byte("aGVsbG8="))         // Returns []byte("hello"), nil
Base64DecodeBytes([]byte("aGVsbG8gd29ybGQ=")) // Returns []byte("hello world"), nil

Parameters:

  • data: Base64-encoded byte array to decode

Returns:

  • Decoded bytes
  • Error if decoding fails

func Base64Encode

func Base64Encode(s string) string

Base64Encode encodes a string to Base64.

Example:

Base64Encode("hello")      // Returns "aGVsbG8="
Base64Encode("hello world") // Returns "aGVsbG8gd29ybGQ="

Parameters:

  • s: String to encode

Returns:

  • Base64-encoded string

func Base64EncodeBytes

func Base64EncodeBytes(data []byte) string

Base64EncodeBytes encodes bytes to Base64 string.

Example:

Base64EncodeBytes([]byte("hello"))      // Returns "aGVsbG8="
Base64EncodeBytes([]byte{0x01, 0x02})   // Returns "AQI="

Parameters:

  • data: Byte array to encode

Returns:

  • Base64-encoded string

func BuildHttpMessage

func BuildHttpMessage(headers []string, body []byte) []byte

BuildHttpMessage builds an HTTP message from headers and body.

Algorithm:

  1. Write each header line followed by CRLF
  2. Write additional CRLF to separate headers from body
  3. Append body bytes if present
  4. Return complete message as byte array

Parameters:

  • headers: List of HTTP headers (including request/status line as first element)
  • body: Message body bytes (can be nil for no body)

Returns:

  • Complete HTTP message as byte array

Example:

headers := []string{"GET / HTTP/1.1", "Host: example.com"}
body := []byte("test data")
message := BuildHttpMessage(headers, body)
// Returns: "GET / HTTP/1.1\r\nHost: example.com\r\n\r\ntest data"

func BuildHttpRequest

func BuildHttpRequest(urlStr string) ([]byte, error)

BuildHttpRequest builds a basic HTTP GET request for a URL.

Parameters:

Returns:

  • HTTP GET request bytes
  • Error if URL parsing fails

Example:

request, _ := BuildHttpRequest("http://example.com/api")
// Returns: "GET /api HTTP/1.1\r\nHost: example.com\r\n\r\n"

func BuildRawResponse

func BuildRawResponse(statusCode int, headers map[string]string, body string) []byte

BuildRawResponse creates raw HTTP response bytes from components. This is useful for building responses from parsed data (e.g., parquet records).

func BuildRequestWithPayloads

func BuildRequestWithPayloads(request []byte, payloads PayloadMap) ([][]byte, error)

BuildRequestWithPayloads injects custom payloads into multiple params simultaneously. Each insertion point applies its own encoding automatically based on parameter type.

If conflicting IPs exist (e.g., nested IP and its parent both in payloads), automatically splits into multiple requests - one per non-conflicting group.

func BuildRequestWithSamePayload

func BuildRequestWithSamePayload(request []byte, insertionPoints []InsertionPoint, payload []byte) ([][]byte, error)

BuildRequestWithSamePayload injects the same payload into all insertion points.

func ByteArrayEquals

func ByteArrayEquals(a, b []byte) bool

ByteArrayEquals compares two byte arrays for equality.

Example:

a := []byte("hello")
b := []byte("hello")
c := []byte("world")
ByteArrayEquals(a, b) // true
ByteArrayEquals(a, c) // false

Parameters:

  • a: First byte array
  • b: Second byte array

Returns:

  • true if arrays are equal, false otherwise

func ByteArrayEqualsCaseInsensitive

func ByteArrayEqualsCaseInsensitive(a, b []byte) bool

ByteArrayEqualsCaseInsensitive compares two byte arrays (case-insensitive).

Algorithm: 1. Check if both arrays are null 2. Check if lengths differ 3. Loop through arrays comparing each byte (lowercased) 4. Return true if all bytes match, false otherwise

Example:

a := []byte("Hello")
b := []byte("HELLO")
c := []byte("world")
ByteArrayEqualsCaseInsensitive(a, b) // true
ByteArrayEqualsCaseInsensitive(a, c) // false

Parameters:

  • a: First byte array
  • b: Second byte array

Returns:

  • true if arrays are equal (ignoring case), false otherwise

func ByteArrayStartsWith

func ByteArrayStartsWith(haystack, needle []byte, caseSensitive bool, offset int) bool

ByteArrayStartsWith checks if haystack starts with needle at given offset.

Example:

data := []byte("Hello World")
ByteArrayStartsWith(data, []byte("Hello"), true, 0)  // true
ByteArrayStartsWith(data, []byte("World"), true, 6)  // true
ByteArrayStartsWith(data, []byte("world"), false, 6) // true (case-insensitive)

Parameters:

  • haystack: Data to search in
  • needle: Pattern to match
  • caseSensitive: Whether comparison is case-sensitive
  • offset: Position in haystack to start comparison

Returns:

  • true if haystack starts with needle at offset

func BytesToHexString

func BytesToHexString(data []byte) string

BytesToHexString converts bytes to hex string (space-separated).

Algorithm:

  1. Loop through bytes
  2. Convert each byte to 2-digit hex
  3. Join with spaces

Parameters:

  • data: Byte array to convert

Returns:

  • Hex string with space separators (e.g., "48 65 6c 6c 6f")

Example:

hex := BytesToHexString([]byte("Hello"))  // "48 65 6c 6c 6f"

func BytesToString

func BytesToString(b []byte) string

BytesToString converts byte array to string.

Note: For proper UTF-8 handling, use string(b) directly in production code.

Example:

BytesToString([]byte{104, 101, 108, 108, 111})   // Returns "hello"
BytesToString([]byte{})                           // Returns ""

Parameters:

  • b: Byte array to convert

Returns:

  • String

func ClearBody

func ClearBody(request []byte) ([]byte, error)

ClearBody removes the body from an HTTP request. Provides body removal.

Algorithm:

  1. Set body to empty byte array
  2. Remove Content-Length and Content-Type headers

Parameters:

  • request: HTTP request bytes

Returns:

  • Modified request without body
  • Error if request malformed

Example:

modified, _ := ClearBody(request)

func ClearQueryString

func ClearQueryString(request []byte) ([]byte, error)

ClearQueryString removes all query parameters from a request. Provides query removal.

Algorithm:

  1. Get path without query using GetPathOnly
  2. Set path to path-only value

Parameters:

  • request: HTTP request bytes

Returns:

  • Modified request without query string
  • Error if request malformed

Example:

modified, _ := ClearQueryString(request)
// Changes "/api?id=123&name=test" to "/api"

func CloneRequest

func CloneRequest(request []byte) []byte

CloneRequest creates a deep copy of a request.

Parameters:

  • request: HTTP request bytes

Returns:

  • Cloned request

Example:

clone := CloneRequest(request)

func CompressBytes

func CompressBytes(input []byte) []byte

CompressBytes is a convenience function for simple gzip compression.

func ContainsBytes

func ContainsBytes(data, pattern []byte) bool

ContainsBytes checks if data contains the pattern.

Algorithm:

  1. Use IndexOfBytes to find first occurrence
  2. Return true if found (index != -1)

Parameters:

  • data: Byte array to search in
  • pattern: Byte pattern to find

Returns:

  • true if pattern is found in data
  • false if pattern is nil or empty

Example:

found := ContainsBytes(data, []byte("test"))  // true if "test" found

func ConvertToHTTP1

func ConvertToHTTP1(request []byte) []byte

ConvertToHTTP1 converts HTTP/2 request to HTTP/1.1.

Algorithm:

  1. Find "HTTP/2" in request line
  2. Replace with "HTTP/1.1"
  3. Return modified request

Parameters:

  • request: HTTP request bytes (may be HTTP/2)

Returns:

  • Request converted to HTTP/1.1

Example:

req := ConvertToHTTP1(request)  // "HTTP/2" → "HTTP/1.1"

func CountMatches

func CountMatches(data, pattern []byte) int

CountMatches counts occurrences of pattern in data.

Algorithm:

  1. Start from position 0
  2. Find each occurrence using IndexOfBytes
  3. Move past each match and continue searching
  4. Return total count

Parameters:

  • data: Byte array to search in
  • pattern: Byte pattern to count

Returns:

  • Number of occurrences of pattern in data

Example:

count := CountMatches(data, []byte("ab"))  // counts all "ab" occurrences

func DecodePathValue

func DecodePathValue(encoded string) string

DecodePathValue decodes URL-encoded path segments (RFC 3986). Unlike DecodeQueryValue, this does NOT convert '+' to space. Use for path segments; use DecodeQueryValue for query/body/cookie params.

RFC 3986 path encoding differs from query string encoding:

  • Path: '+' is a literal character (no space conversion)
  • Query: '+' represents space (form-encoding convention)

Algorithm:

  1. Loop through string character by character
  2. When '%' found, decode next 2 hex characters
  3. '+' is kept as literal (NOT converted to space)
  4. Otherwise, copy character as-is

Example:

DecodePathValue("api/v1%2B2")     // Returns "api/v1+2" (+ stays literal)
DecodePathValue("hello%20world") // Returns "hello world"
DecodePathValue("user%2Fadmin")  // Returns "user/admin"

Parameters:

  • encoded: URL-encoded path segment

Returns:

  • Decoded string

func DecodeQueryValue

func DecodeQueryValue(encoded string) string

DecodeQueryValue decodes URL-encoded value using loop-based parsing. Handles %XX hex sequences and '+' to space conversion. NO REGEX, NO url.QueryUnescape per requirements.

Algorithm:

  1. Loop through string character by character
  2. When '%' found, decode next 2 hex characters
  3. When '+' found, convert to space
  4. Otherwise, copy character as-is

Example:

encoded := "John%20Doe+Smith"
decoded := DecodeQueryValue(encoded)
// Returns "John Doe Smith"

encoded := "hello%2Bworld"
decoded := DecodeQueryValue(encoded)
// Returns "hello+world"

Parameters:

  • encoded: URL-encoded string

Returns:

  • Decoded string
Example

ExampleDecodeQueryValue demonstrates URL decoding

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	// Example 1: Percent encoding
	encoded1 := "John%20Doe"
	decoded1 := httpmsg.DecodeQueryValue(encoded1)
	fmt.Printf("Decoded: %s\n", decoded1)

	// Example 2: Plus encoding
	encoded2 := "hello+world"
	decoded2 := httpmsg.DecodeQueryValue(encoded2)
	fmt.Printf("Decoded: %s\n", decoded2)

	// Example 3: Mixed encoding
	encoded3 := "search+term%3A+golang%2Bpython"
	decoded3 := httpmsg.DecodeQueryValue(encoded3)
	fmt.Printf("Decoded: %s\n", decoded3)

}
Output:
Decoded: John Doe
Decoded: hello world
Decoded: search term: golang+python
Example (SpecialCharacters)

ExampleDecodeQueryValue_specialCharacters demonstrates decoding special characters

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	examples := []string{
		"hello%2Bworld",   // Plus sign
		"a%3Db%26c%3Dd",   // Equals and ampersand
		"100%25+complete", // Percent sign
	}

	for _, encoded := range examples {
		decoded := httpmsg.DecodeQueryValue(encoded)
		fmt.Printf("%s -> %s\n", encoded, decoded)
	}

}
Output:
hello%2Bworld -> hello+world
a%3Db%26c%3Dd -> a=b&c=d
100%25+complete -> 100% complete

func DecompressBytes

func DecompressBytes(input []byte) []byte

DecompressBytes is a convenience function for simple gzip decompression.

func DefaultUserAgent

func DefaultUserAgent() string

DefaultUserAgent returns the effective User-Agent for the next outgoing request. The VIGOLIUM_DEFAULT_UA environment variable (when present) takes precedence over the configured selector; the selector is then resolved: "preset" -> the self-identifying Vigolium string, "random"/"" -> a rotating browser string (a fresh pick per call), any other value -> that literal. {version} is expanded throughout.

func EncodeBytesAbove7F

func EncodeBytesAbove7F(input string) string

EncodeBytesAbove7F URL-encodes bytes with value > 0x7F.

Algorithm:

  1. Loop through input bytes
  2. If byte > 0x7F, encode as %XX
  3. Otherwise keep as-is

Parameters:

  • input: String that may contain high bytes

Returns:

  • String with high bytes URL-encoded

Example:

encoded := EncodeBytesAbove7F("hello\x80world")
// Returns "hello%80world"

func EncodePathValue

func EncodePathValue(decoded string) string

EncodePathValue encodes a string for safe use in URL path segments (RFC 3986). Unlike EncodeQueryValue, this encodes space as '%20' (not '+'). Use for path segments; use EncodeQueryValue for query/body/cookie params.

RFC 3986 path encoding differs from query string encoding:

  • Path: space → %20 (percent encoding only)
  • Query: space → + (form-encoding convention)

Algorithm:

  1. Loop through string byte by byte
  2. Encode unreserved characters as-is: A-Z, a-z, 0-9, -, _, ., ~
  3. Encode everything else (including space and +) as %XX

Example:

EncodePathValue("hello world")   // Returns "hello%20world"
EncodePathValue("v1+2")          // Returns "v1%2B2"
EncodePathValue("user/admin")    // Returns "user%2Fadmin"

Parameters:

  • decoded: String to encode

Returns:

  • URL-encoded string for path segment

func EncodePathValueBytes

func EncodePathValueBytes(src []byte) []byte

EncodePathValueBytes is the []byte-native form of EncodePathValue, avoiding the []byte→string→[]byte round trips on the path-parameter fuzzing hot path. Returns nil for empty input.

func EncodeQueryValue

func EncodeQueryValue(decoded string) string

EncodeQueryValue encodes a string for safe use in URL query parameters. Uses loop-based encoding (NO REGEX, NO url.QueryEscape per requirements).

Algorithm:

  1. Loop through string byte by byte
  2. Encode unreserved characters as-is: A-Z, a-z, 0-9, -, _, ., ~
  3. Encode space as '+'
  4. Encode everything else as %XX hex sequence

Example:

EncodeQueryValue("John Doe")      // Returns "John+Doe"
EncodeQueryValue("hello+world")   // Returns "hello%2Bworld"
EncodeQueryValue("a=b&c=d")       // Returns "a%3Db%26c%3Dd"

Parameters:

  • decoded: String to encode

Returns:

  • URL-encoded string
Example

ExampleEncodeQueryValue demonstrates URL encoding

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	// Example 1: Space to plus
	decoded1 := "John Doe"
	encoded1 := httpmsg.EncodeQueryValue(decoded1)
	fmt.Printf("Encoded: %s\n", encoded1)

	// Example 2: Special characters
	decoded2 := "hello+world"
	encoded2 := httpmsg.EncodeQueryValue(decoded2)
	fmt.Printf("Encoded: %s\n", encoded2)

	// Example 3: Equals and ampersand
	decoded3 := "a=b&c=d"
	encoded3 := httpmsg.EncodeQueryValue(decoded3)
	fmt.Printf("Encoded: %s\n", encoded3)

}
Output:
Encoded: John+Doe
Encoded: hello%2Bworld
Encoded: a%3Db%26c%3Dd
Example (RoundTrip)

ExampleEncodeQueryValue_roundTrip demonstrates encode/decode round trip

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	original := "Hello World & Friends!"

	// Encode
	encoded := httpmsg.EncodeQueryValue(original)
	fmt.Printf("Original: %s\n", original)
	fmt.Printf("Encoded:  %s\n", encoded)

	// Decode
	decoded := httpmsg.DecodeQueryValue(encoded)
	fmt.Printf("Decoded:  %s\n", decoded)
	fmt.Printf("Match:    %t\n", original == decoded)

}
Output:
Original: Hello World & Friends!
Encoded:  Hello+World+%26+Friends%21
Decoded:  Hello World & Friends!
Match:    true

func EncodeQueryValueBytes

func EncodeQueryValueBytes(src []byte) []byte

EncodeQueryValueBytes is the []byte-native form of EncodeQueryValue. It avoids the []byte→string→[]byte round trips the string form forces on the insertion-point fuzzing hot path (encodePayload). Returns nil for empty input.

func EnsureURLScheme added in v0.4.8

func EnsureURLScheme(target, defaultScheme string) string

EnsureURLScheme prefixes a schemeless target with defaultScheme so callers all hand the rest of the pipeline the same absolute URL. A target that already names a scheme is returned untouched, whatever that scheme is.

It exists because "does this target have a scheme" has three different answers scattered around the tree, and only one of them is right. The HTTP phases never had to ask: urlutil.ParseAbsoluteURL accepts a bare host and GetRawRequestFromURL defaults the service protocol to http, so `-t example.com` or a -T file of bare hostnames sweeps fine under probe, discovery and known-issue-scan. The browser phase does have to ask -- spitolas parses its seed with net/url, where "example.com" is a path with no host ("target URL must have a host") and "example.com:8443" is worse still ("first path segment in URL cannot contain colon") -- so spidering hard-failed per target on the exact host lists the other phases scanned, and because it reports a failed crawl as a completed one with 0 records, `scan --only spidering --soft-fail` exited 0 with an empty export.

The default scheme is the caller's to choose because it is not the same everywhere: scan targets take http, so the normalized form stays byte-identical to the request the HTTP path already built for them (a host that only answers on 443 redirects there on the first hop), while a shim translating a tool that documents bare domains takes https.

func EnsureURLSchemeTracked added in v0.4.9

func EnsureURLSchemeTracked(target, defaultScheme string) (string, bool)

EnsureURLSchemeTracked is EnsureURLScheme plus whether the scheme was SUPPLIED by this call rather than typed by the operator.

The distinction is not cosmetic. "http" is a guess for a schemeless target, and a phase that can test the guess (the probe sweep, which connects before it requests) must be able to tell a guess from an instruction: an operator who typed `http://host` means http even if only 443 answers, while one who typed `host` meant "whatever this host speaks". Normalization happens early and in more than one place, so without recording the answer here the information is gone by the time any phase could act on it.

func EqualsCaseInsensitive

func EqualsCaseInsensitive(a, b string) bool

EqualsCaseInsensitive compares two strings case-insensitively. Loop-based implementation (NO regex or library functions).

Parameters:

  • a, b: Strings to compare

Returns:

  • true if equal (case-insensitive), false otherwise

Logic:

  1. Check length first (fast path)
  2. Loop through each character
  3. Convert to lowercase and compare

func EqualsIgnoreCase

func EqualsIgnoreCase(a, b string) bool

EqualsIgnoreCase compares two strings case-insensitively.

func EstimateRetainedBytes

func EstimateRetainedBytes(points []InsertionPoint) int

EstimateRetainedBytes approximates the heap retained by a set of insertion points produced by one CreateAllInsertionPoints call.

Byte buffers are counted once per distinct backing array. That is the whole point of the function: insertion points from a single call deliberately share their base request and parent-value buffers (see sharedBaseRequest and newEncodedInsertionPointShared), so summing len(baseRequest) per point would overstate retention by the child count — reproducing, in the estimate, exactly the quadratic blowup the sharing removed.

The result is an estimate for budgeting, not an exact measurement: it attributes shared Params per-point and ignores allocator overhead. It is intended to be stable and cheap (one pass, no allocation beyond the seen-set), not precise.

func ExtractAllHeaders

func ExtractAllHeaders(data []byte) ([]string, []int, int, error)

ExtractAllHeaders is a convenience function that extracts headers from an HTTP request/response, automatically finding the header section.

Parameters:

  • data: Complete HTTP request/response bytes

Returns:

  • headers: List of header lines including request/status line
  • headerOffsets: Byte offset where each header starts
  • bodyStart: Position where body begins (after headers)
  • error: Any parsing error

Logic:

  1. Find header/body separator
  2. Extract headers from start to separator
  3. Return headers and body position

Example:

request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\nBODY")
headers, offsets, bodyStart, _ := ExtractAllHeaders(request)
// headers = ["GET / HTTP/1.1", "Host: example.com"]
// offsets = [0, 16]
// bodyStart = 38

func ExtractBoundary

func ExtractBoundary(contentType string) string

ExtractBoundary extracts the boundary string from a Content-Type header value.

Algorithm:

  1. Search for "boundary=" in Content-Type header
  2. Extract everything after "boundary="
  3. Trim whitespace
  4. Return boundary string

Example:

contentType := "multipart/form-data; boundary=----WebKitFormBoundary1234"
boundary := ExtractBoundary(contentType)
// Returns: "----WebKitFormBoundary1234"

Parameters:

  • contentType: Content-Type header value

Returns:

  • Boundary string, or empty string if not found
Example

ExampleExtractBoundary demonstrates extracting boundary from Content-Type header.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	contentType := "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"
	boundary := httpmsg.ExtractBoundary(contentType)

	fmt.Println(boundary)
}
Output:
----WebKitFormBoundary7MA4YWxkTrZu0gW
Example (WithWhitespace)

ExampleExtractBoundary_withWhitespace demonstrates trimming whitespace.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	contentType := "multipart/form-data; boundary=  ----WebKit  "
	boundary := httpmsg.ExtractBoundary(contentType)

	fmt.Printf("Boundary: '%s'\n", boundary)
}
Output:
Boundary: '----WebKit'

func ExtractHeaders

func ExtractHeaders(request []byte, startOffset, endOffset int) ([]string, []int, error)

ExtractHeaders extracts HTTP headers from byte slice.

Parameters:

  • request: HTTP request/response bytes
  • startOffset: Starting position to parse from
  • endOffset: Ending position (typically body start or end of data)

Returns:

  • headers: List of header lines including request/status line
  • headerOffsets: Byte offset where each header starts
  • error: Any parsing error

Logic:

  1. Loop through bytes from startOffset to endOffset
  2. Find CRLF (\r\n) line terminators
  3. Extract line between terminators
  4. Skip empty lines
  5. Track offset of each non-empty line
  6. Return headers and their offsets

Example:

request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
headers, offsets, _ := ExtractHeaders(request, 0, 36)
// headers = ["GET / HTTP/1.1", "Host: example.com"]
// offsets = [0, 16]

func ExtractURLFromRequest

func ExtractURLFromRequest(request []byte) ([]byte, int, int, error)

ExtractURLFromRequest extracts URL from HTTP request line. Returns URL bytes and their start/end positions.

Parameters:

  • request: HTTP request bytes

Returns:

  • url: URL bytes (between first and second space)
  • urlStart: Position where URL starts
  • urlEnd: Position where URL ends
  • error: Any parsing error

Algorithm:

  1. Skip leading whitespace
  2. Skip HTTP method (until first space)
  3. Skip whitespace after method
  4. Extract URL (until next space)
  5. Return URL bytes and positions

Example:

request := []byte("GET /api/users?id=123 HTTP/1.1\r\n...")
url, start, end, _ := ExtractURLFromRequest(request)
// url = []byte("/api/users?id=123")
// start = 4, end = 21
Example

ExampleExtractURLFromRequest demonstrates extracting URL from HTTP request

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET /api/users?id=123 HTTP/1.1\r\nHost: example.com\r\n\r\n")

	urlBytes, start, end, err := httpmsg.ExtractURLFromRequest(request)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("URL: %s\n", string(urlBytes))
	fmt.Printf("Start: %d\n", start)
	fmt.Printf("End: %d\n", end)

}
Output:
URL: /api/users?id=123
Start: 4
End: 21
Example (Post)

ExampleExtractURLFromRequest_post demonstrates POST request URL extraction

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("POST /api/create HTTP/1.1\r\nHost: api.example.com\r\nContent-Type: application/json\r\n\r\n{}")

	urlBytes, _, _, err := httpmsg.ExtractURLFromRequest(request)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("URL: %s\n", string(urlBytes))

}
Output:
URL: /api/create

func FilterString

func FilterString(input, safeChars string) string

FilterString keeps only characters in safeChars.

Algorithm:

  1. Loop through input characters
  2. Check if character is in safeChars
  3. Keep only matching characters

Parameters:

  • input: String to filter
  • safeChars: String containing allowed characters

Returns:

  • Filtered string containing only safe characters

Example:

filtered := FilterString("abc123!@#", "abcdefghijklmnopqrstuvwxyz")
// Returns "abc"

func FindBodyEnd

func FindBodyEnd(request []byte, startOffset int) int

FindBodyEnd finds the offset where the HTTP body ends (before trailing separators). This searches backwards from the end to find trailing CRLF CRLF or LF LF sequences.

Algorithm:

  1. Search backwards for CRLF CRLF or LF LF
  2. Return offset BEFORE the separator (end of actual body content)
  3. If not found, return startOffset

Example:

response := []byte("HTTP/1.1 200 OK\r\n\r\nBODY\r\n\r\n")
bodyStart := FindBodyOffset(response)  // Returns 19
bodyEnd := FindBodyEnd(response, bodyStart)  // Returns 23 (before trailing \r\n\r\n)

Parameters:

  • request: HTTP request or response bytes
  • startOffset: Position to start searching from (typically body start)

Returns:

  • Offset where body ends (before trailing separator)
  • If no trailing separator, returns len(request)
Example

Example demonstrating FindBodyEnd with trailing separators

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	response := []byte("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nBODY DATA\r\n\r\n")

	bodyStart := httpmsg.FindBodyOffset(response)
	bodyEnd := httpmsg.FindBodyEnd(response, bodyStart)

	fmt.Printf("Body starts at: %d\n", bodyStart)
	fmt.Printf("Body ends at: %d\n", bodyEnd)
	fmt.Printf("Body content: %s\n", string(response[bodyStart:bodyEnd]))

}
Output:
Body starts at: 44
Body ends at: 53
Body content: BODY DATA

func FindBodyOffset

func FindBodyOffset(request []byte) int

FindBodyOffset finds the offset where the HTTP body starts in a request or response. This is the position immediately after the header/body separator.

Algorithm:

  1. Search for CRLF CRLF sequence (0x0D 0x0A 0x0D 0x0A)
  2. If not found, search for LF LF sequence (0x0A 0x0A)
  3. If found, return offset AFTER the separator (body start position)
  4. Handle edge case: check last 2 bytes for LF LF if main loop didn't find it
  5. If no separator found, return length of data (no body)

Example:

request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\nBODY")
offset := FindBodyOffset(request)
// Returns 38 (position where "BODY" starts)

Parameters:

  • request: HTTP request or response bytes

Returns:

  • Offset where body starts (after the separator)
  • If no separator found, returns len(request)
Example

Example demonstrating FindBodyOffset with CRLF line endings

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\nBODY DATA")

	offset := httpmsg.FindBodyOffset(request)
	fmt.Printf("Body starts at offset: %d\n", offset)
	fmt.Printf("Body content: %s\n", string(request[offset:]))

}
Output:
Body starts at offset: 55
Body content: BODY DATA
Example (HttpWorkflow)

Example showing complete HTTP request parsing workflow

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	// HTTP request with body
	request := []byte("POST /api/data HTTP/1.1\r\n" +
		"Host: example.com\r\n" +
		"Content-Type: application/json\r\n" +
		"Content-Length: 18\r\n" +
		"\r\n" +
		"{\"key\":\"value\"}")

	// Find where body starts
	bodyOffset := httpmsg.FindBodyOffset(request)

	// Extract headers (everything before body)
	headers := httpmsg.SliceBytes(request, 0, bodyOffset-4) // -4 to exclude \r\n\r\n
	fmt.Printf("Headers length: %d bytes\n", len(headers))

	// Extract body
	body := httpmsg.SliceBytes(request, bodyOffset, len(request))
	fmt.Printf("Body: %s\n", string(body))

	// Find Content-Type in headers
	ctIdx := httpmsg.IndexOfBytes(headers, []byte("Content-Type:"), 0)
	if ctIdx != -1 {
		fmt.Println("Content-Type header found")
	}

}
Output:
Headers length: 94 bytes
Body: {"key":"value"}
Content-Type header found
Example (Lf)

Example demonstrating FindBodyOffset with LF line endings

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET / HTTP/1.1\nHost: example.com\nUser-Agent: test\n\nBODY DATA")

	offset := httpmsg.FindBodyOffset(request)
	fmt.Printf("Body starts at offset: %d\n", offset)
	fmt.Printf("Body content: %s\n", string(request[offset:]))

}
Output:
Body starts at offset: 51
Body content: BODY DATA

func FindCharIndex

func FindCharIndex(s string, c byte) int

FindCharIndex finds first occurrence of character in string. Loop-based helper (no strings.Index).

Parameters:

  • s: String to search
  • c: Character byte to find

Returns:

  • Index of first occurrence, or -1 if not found

func FindColonIndex

func FindColonIndex(header string) int

FindColonIndex finds the first colon in a header line. Helper function for header parsing.

Parameters:

  • header: Header line (e.g., "Content-Type: text/html")

Returns:

  • Index of first colon, or -1 if not found

Logic:

  • Loop through string looking for ':' character
  • Return index when found
  • Return -1 if not found

func FindHeaderBodySeparator

func FindHeaderBodySeparator(data []byte, startOffset int) int

FindHeaderBodySeparator finds the position where headers end and body begins. Looks for double line terminator: \r\n\r\n or \n\n

Parameters:

  • data: HTTP request/response bytes
  • startOffset: Position to start searching from

Returns:

  • Position after the double line terminator (start of body)
  • Returns -1 if not found

Logic:

  1. Search for CRLFCRLF (\r\n\r\n) sequence
  2. Also search for LFLF (\n\n) sequence
  3. Return position after separator
  4. Handle edge case near end of data

Example:

request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\nBODY")
bodyStart := FindHeaderBodySeparator(request, 0)
// bodyStart = 38 (position after \r\n\r\n)

func FindHostEnd

func FindHostEnd(url []byte, hostStart int) int

FindHostEnd finds the end position of the host:port portion. Loop until '/', '?', '#', or end of string.

Parameters:

  • url: URL bytes
  • hostStart: Position where host begins

Returns:

  • Position where host:port ends

Algorithm:

  1. Loop from hostStart until path/query/fragment delimiter
  2. Check for '/', '?', '#'
  3. Return position of delimiter or end of string

Example:

url := []byte("example.com:8080/path")
end := FindHostEnd(url, 0)
// Returns 16 (position of '/')

func FindHttpHeader

func FindHttpHeader(headers []HttpHeader, name string) (string, bool)

FindHttpHeader searches for a header by name (case-insensitive) in a slice of headers. Returns the value and true if found, empty string and false otherwise.

func FindLineEnd

func FindLineEnd(data []byte, start, end int) int

FindLineEnd finds the position of the next line terminator. Helper function for header parsing.

Parameters:

  • data: Byte slice to search
  • start: Starting position
  • end: Ending position

Returns:

  • Position of CR or LF, or end if not found

Logic:

  • Loop through bytes looking for 0x0D (CR) or 0x0A (LF)
  • Return position when found
  • Return end if no terminator found

func FindPathEnd

func FindPathEnd(url []byte, pathStart int) int

FindPathEnd finds the end position of the path (before '?' or '#'). Loop until query or fragment delimiter.

Parameters:

  • url: URL bytes
  • pathStart: Position where path begins

Returns:

  • Position where path ends (before '?' or '#')

Algorithm:

  1. Loop from pathStart looking for '?' or '#'
  2. Return position of delimiter
  3. Return length if no delimiter found

Example:

url := []byte("/api/users?id=123#section")
end := FindPathEnd(url, 0)
// Returns 10 (position of '?')

func FindProtocolEnd

func FindProtocolEnd(url []byte) int

FindProtocolEnd finds the end position of the protocol (before ://). Loop-based implementation (no regex).

Parameters:

  • url: URL bytes to search

Returns:

  • Position of ':' in "://", or -1 if not found

Algorithm:

  1. Loop through bytes looking for "://" sequence
  2. Return position of ':' when found
  3. Return -1 if not found

Example:

url := []byte("http://example.com")
pos := FindProtocolEnd(url)
// Returns 4 (position of ':' in "http:")

func FindQueryEnd

func FindQueryEnd(url []byte, queryStart int) int

FindQueryEnd finds the end position of the query string (before '#'). Loop until fragment delimiter.

Parameters:

  • url: URL bytes
  • queryStart: Position where query begins (after '?')

Returns:

  • Position where query ends (before '#')

Algorithm:

  1. Loop from queryStart looking for '#'
  2. Also check for newline (byte 10) or space as terminators
  3. Return position of delimiter
  4. Return length if no delimiter found

Example:

url := []byte("id=123&name=test#section")
end := FindQueryEnd(url, 0)
// Returns 16 (position of '#')
Example

ExampleFindQueryEnd demonstrates finding the query string end position Note: FindQueryEnd is from url_parser.go

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com/path?foo=bar#anchor")

	start := httpmsg.FindQueryStart(url)
	// FindQueryEnd expects queryStart to be AFTER the '?', so we add 1
	end := httpmsg.FindQueryEnd(url, start+1)

	if start != -1 && end != -1 {
		fmt.Printf("Query string: %s\n", url[start+1:end])
	}

}
Output:
Query string: foo=bar

func FindQueryStart

func FindQueryStart(url []byte) int

FindQueryStart finds the position of the '?' character in a URL. Uses simple loop-based search (NO REGEX per requirements). Note: For full query string bounds detection, use findQueryBounds instead.

Algorithm:

  1. Loop through byte array
  2. Return position when '?' (63) found
  3. Return -1 if not found

Example:

url := []byte("http://example.com/path?foo=bar")
pos := FindQueryStart(url)
// Returns 23 (position of '?')

Parameters:

  • url: URL byte array

Returns:

  • Position of '?' character, or -1 if not found
Example

ExampleFindQueryStart demonstrates finding the query string start position

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com/path?foo=bar&name=value")

	pos := httpmsg.FindQueryStart(url)
	if pos != -1 {
		fmt.Printf("Query string starts at position: %d\n", pos)
		fmt.Printf("Query string: %s\n", url[pos:])
	}

}
Output:
Query string starts at position: 23
Query string: ?foo=bar&name=value

func FindQueryStringBounds

func FindQueryStringBounds(urlPath []byte) (queryStart int, queryEnd int)

FindQueryStringBounds finds the start and end of query string in URL path. Returns indices of '?' and '#' delimiters.

Parameters:

  • urlPath: URL path bytes (may contain query)

Returns:

  • queryStart: Position of '?' (or -1 if not found)
  • queryEnd: Position of '#' or end of string

Algorithm:

  1. Loop through bytes looking for '?'
  2. When found, mark start position
  3. Continue looking for '#' or whitespace
  4. Return [start, end] or nil if no query

Example:

path := []byte("/api/users?id=123#section")
start, end := FindQueryStringBounds(path)
// start = 10, end = 17
Example

ExampleFindQueryStringBounds demonstrates finding query string boundaries

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	path := []byte("/api/users?id=123&status=active#results")

	start, end := httpmsg.FindQueryStringBounds(path)

	fmt.Printf("Query starts at: %d\n", start)
	fmt.Printf("Query ends at: %d\n", end)
	fmt.Printf("Query string: %s\n", string(path[start+1:end]))

}
Output:
Query starts at: 10
Query ends at: 31
Query string: id=123&status=active

func FlattenCookiesForHost

func FlattenCookiesForHost(host string, cookies []*http.Cookie) string

FlattenCookiesForHost filters browser-harvested cookies down to those that apply to host and joins them into a Cookie header value ("a=1; b=2"). Cookies for unrelated domains are dropped so a session stays scoped to the host it was harvested from, as are expired ones. For a duplicated name the BROADEST (shortest) path wins: the result is one static header for the whole host, so the cookie most likely to apply to an arbitrary path is the right one to keep — previously it was whichever the browser happened to list first.

This is the attribute-losing flattening; it stays because a flat header is what several callers still want (and what CarriedSession.CookieHeader is). Per-request evaluation belongs in CarriedSession.CookieHeaderFor.

func GetAllCookies

func GetAllCookies(request []byte) (map[string]string, error)

GetAllCookies returns all cookies as a map.

Parameters:

  • request: HTTP request bytes

Returns:

  • Map of cookie name → value
  • Error if request malformed

Example:

cookies, _ := GetAllCookies(request)

func GetAllHeaderValues

func GetAllHeaderValues(request []byte, name string) ([]string, error)

GetAllHeaderValues returns all values for a header (for multi-value headers).

Algorithm:

  1. Extract all headers
  2. Loop through and collect all matching header values
  3. Return as slice

Parameters:

  • request: HTTP request bytes
  • name: Header name (case-insensitive)

Returns:

  • Slice of all values for the header
  • Error if request malformed

Example:

setCookies, _ := GetAllHeaderValues(request, "Set-Cookie")

func GetBody

func GetBody(request []byte) ([]byte, error)

GetBody extracts the body portion from an HTTP request. Provides direct body access.

Algorithm:

  1. Find body offset using FindBodyOffset
  2. Return bytes from offset to end

Parameters:

  • request: HTTP request bytes

Returns:

  • Body bytes (empty if no body)
  • Error if request malformed

Example:

body, _ := GetBody(request)
// Returns body portion as []byte

func GetBodyBytes

func GetBodyBytes(request []byte, bodyOffset int) []byte

GetBodyBytes extracts the body portion of an HTTP request as a byte slice. Helper function for working with request bodies.

Parameters:

  • request: Complete HTTP request bytes
  • bodyOffset: Byte offset where body starts

Returns:

  • Byte slice containing the body data
  • Empty slice if bodyOffset is invalid

Example:

request := []byte("POST / HTTP/1.1\r\n\r\nname=value")
bodyOffset := FindBodyOffset(request)
body := GetBodyBytes(request, bodyOffset)
// body = []byte("name=value")

func GetBodyOffset

func GetBodyOffset(request []byte) (int, error)

GetBodyOffset returns the offset where the body starts.

Parameters:

  • request: HTTP request bytes

Returns:

  • Body offset (-1 if no body)
  • Error if request malformed

Example:

offset, _ := GetBodyOffset(request)

func GetBodyParametersMap

func GetBodyParametersMap(request []byte) (map[string]string, error)

GetBodyParametersMap returns all body parameters as a map. Provides map-based body parameter access.

Parameters:

  • request: HTTP request bytes

Returns:

  • Map of parameter name → value
  • Error if request malformed

Example:

params, _ := GetBodyParametersMap(request)

func GetBodySize

func GetBodySize(request []byte) (int, error)

GetBodySize returns the size of the request body. Provides body size calculation.

Algorithm:

  1. Find body offset
  2. Calculate total length - offset

Parameters:

  • request: HTTP request bytes

Returns:

  • Body size in bytes
  • Error if request malformed

Example:

size, _ := GetBodySize(request)

func GetBodyString

func GetBodyString(request []byte) (string, error)

GetBodyString extracts the body as a string. Provides string body access.

Algorithm:

  1. Get body bytes using GetBody
  2. Convert to string

Parameters:

  • request: HTTP request bytes

Returns:

  • Body as string
  • Error if request malformed

Example:

bodyStr, _ := GetBodyString(request)

func GetContentType

func GetContentType(request []byte) (string, error)

GetContentType extracts the Content-Type header value.

Algorithm:

  1. Call GetHeaderValue for "Content-Type"

Parameters:

  • request: HTTP request bytes

Returns:

  • Content-Type value
  • Error if request malformed

Example:

contentType, _ := GetContentType(request)

func GetCookie

func GetCookie(request []byte, name string) (string, error)

GetCookie extracts a single cookie value by name.

Parameters:

  • request: HTTP request bytes
  • name: Cookie name

Returns:

  • Cookie value
  • Error if request malformed

Example:

sessionId, _ := GetCookie(request, "JSESSIONID")

func GetDefaultPort

func GetDefaultPort(protocol string) int

GetDefaultPort returns the default port for a protocol.

Parameters:

  • protocol: Protocol string ("http", "https", etc.)

Returns:

  • Default port (80 for http, 443 for https)
  • -1 if protocol is unknown

Algorithm:

  1. Convert protocol to lowercase using loop
  2. Check for known protocols
  3. Return corresponding default port

Example:

port := GetDefaultPort("HTTP")
// Returns 80

port := GetDefaultPort("https")
// Returns 443
Example

ExampleGetDefaultPort demonstrates getting default port for protocol

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	fmt.Printf("HTTP default port: %d\n", httpmsg.GetDefaultPort("http"))
	fmt.Printf("HTTPS default port: %d\n", httpmsg.GetDefaultPort("https"))
	fmt.Printf("WS default port: %d\n", httpmsg.GetDefaultPort("ws"))
	fmt.Printf("WSS default port: %d\n", httpmsg.GetDefaultPort("wss"))

}
Output:
HTTP default port: 80
HTTPS default port: 443
WS default port: 80
WSS default port: 443

func GetExtension

func GetExtension(request []byte) (string, error)

GetExtension extracts file extension from request path.

Algorithm:

  1. Get path without query string
  2. Find last '.' in path
  3. Find last '/' in path
  4. If '.' exists and is after last '/', return extension (including '.')
  5. Return empty string if no extension

Parameters:

  • request: HTTP request bytes

Returns:

  • File extension including dot (e.g., ".json", ".html")
  • Empty string if no extension found

Example:

ext, _ := GetExtension(request)  // "/api/file.json?x=1" → ".json"
ext, _ := GetExtension(request)  // "/api/users" → ""

func GetHTTPVersion

func GetHTTPVersion(request []byte) (string, error)

GetHTTPVersion extracts the HTTP version from a request. Provides direct version access.

Algorithm:

  1. Extract first header line (request line)
  2. Parse using parseRequestLineString
  3. Return version portion

Parameters:

  • request: HTTP request bytes

Returns:

  • HTTP version (e.g., "HTTP/1.1")
  • Error if request malformed

Example:

version, _ := GetHTTPVersion(request)
// Returns: "HTTP/1.1"

func GetHeaderOffsets

func GetHeaderOffsets(request []byte, name string) []int

GetHeaderOffsets returns [lineStart, valueStart, valueEnd] offsets for a header.

Algorithm:

  1. Search through headers line by line
  2. Find header with matching name (case-insensitive)
  3. Return offsets: [lineStart, valueStart, valueEnd] - lineStart: Position where header line begins - valueStart: Position where header value begins (after ": ") - valueEnd: Position where header value ends (before CRLF)
  4. Return nil if header not found

Parameters:

  • request: HTTP request bytes
  • name: Header name to find (case-insensitive)

Returns:

  • []int{lineStart, valueStart, valueEnd} or nil if not found

Example:

offsets := GetHeaderOffsets(request, "Host")
// For "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
// offsets = [16, 22, 33] (line at 16, value "example.com" from 22 to 33)

func GetHeaderValue

func GetHeaderValue(request []byte, name string) (string, error)

GetHeaderValue extracts a header value directly from request bytes.

Algorithm:

  1. Extract headers using ExtractAllHeaders
  2. Use GetHeader to find value

Parameters:

  • request: HTTP request bytes
  • name: Header name (case-insensitive)

Returns:

  • Header value (empty string if not found)
  • Error if request malformed

Example:

contentType, _ := GetHeaderValue(request, "Content-Type")

func GetHeaders

func GetHeaders(message []byte) string

GetHeaders extracts all headers as string (excluding request/status line).

Algorithm:

  1. Find end of first line (request/status line)
  2. Find body offset
  3. Extract headers between these positions

Parameters:

  • message: HTTP request or response bytes

Returns:

  • Headers as string (e.g., "Host: example.com\r\nAccept: */*\r\n")

Example:

headers := GetHeaders(request)  // "Host: example.com\r\nAccept: */*\r\n"

func GetHeadersByPrefix

func GetHeadersByPrefix(request []byte, prefix string) ([]string, error)

GetHeadersByPrefix returns all headers matching a prefix. Useful for getting all X-* headers, etc.

Algorithm:

  1. Extract all headers
  2. Loop through and collect headers starting with prefix
  3. Return as slice (full "Name: Value" format)

Parameters:

  • request: HTTP request bytes
  • prefix: Header name prefix (case-insensitive)

Returns:

  • Slice of matching headers in "Name: Value" format
  • Error if request malformed

Example:

customHeaders, _ := GetHeadersByPrefix(request, "X-")

func GetHeadersBytes

func GetHeadersBytes(message []byte) []byte

GetHeadersBytes extracts all headers as bytes (excluding request/status line). Returns the raw header bytes between first line and body.

Algorithm:

  1. Find end of first line (request/status line)
  2. Find body offset (header/body separator)
  3. Extract bytes between these positions (excluding separator)

Parameters:

  • message: HTTP request or response bytes

Returns:

  • Header bytes (empty if no headers)

Example:

headers := GetHeadersBytes(request)

func GetHeadersSize

func GetHeadersSize(request []byte) (int, error)

GetHeadersSize returns the size of headers (including request line).

Parameters:

  • request: HTTP request bytes

Returns:

  • Headers size in bytes
  • Error if request malformed

Example:

size, _ := GetHeadersSize(request)

func GetHost

func GetHost(request []byte) (string, error)

GetHost extracts the Host header value.

Algorithm:

  1. Call GetHeaderValue for "Host"

Parameters:

  • request: HTTP request bytes

Returns:

  • Host value
  • Error if request malformed

Example:

host, _ := GetHost(request)

func GetMatches

func GetMatches(data, pattern []byte, limit int) [][2]int

GetMatches returns all [start, end] positions of pattern in data.

Algorithm:

  1. Start from position 0
  2. Find each occurrence using IndexOfBytes
  3. Record [start, end] for each match
  4. Continue until no more matches or limit reached
  5. Return all positions

Parameters:

  • data: Byte array to search in
  • pattern: Byte pattern to find
  • limit: Maximum matches to find (-1 for unlimited)

Returns:

  • Slice of [2]int{start, end} positions for each match

Example:

matches := GetMatches(data, []byte("ab"), -1)
// For "abcab", returns [][2]int{{0, 2}, {3, 5}}

func GetMethod

func GetMethod(request []byte) (string, error)

GetMethod extracts the HTTP method from a request. Provides direct method access.

Algorithm:

  1. Extract first header line (request line)
  2. Parse using parseRequestLineString
  3. Return method portion

Parameters:

  • request: HTTP request bytes

Returns:

  • HTTP method (e.g., "GET", "POST")
  • Error if request malformed

Example:

method, _ := GetMethod(request)
// Returns: "GET"

func GetNestedResponse

func GetNestedResponse(response []byte) []byte

GetNestedResponse extracts nested HTTP response from body. Useful for HTTP request smuggling detection.

Algorithm:

  1. Find body offset of outer response
  2. Search for "HTTP/" in body (start of nested response)
  3. Return nested response bytes

Parameters:

  • response: HTTP response bytes

Returns:

  • Nested response bytes, or nil if not found

Example:

nested := GetNestedResponse(response)
// If body contains "HTTP/1.1 200 OK...", returns that response

func GetParameter

func GetParameter(request []byte, name string, paramType ParamType) (string, error)

GetParameter extracts a single parameter value by name and type. Provides direct parameter access without AnalyzeRequest.

Algorithm:

  1. Call AnalyzeRequest to get all parameters
  2. Find parameter by name and type
  3. Return value

Parameters:

  • request: HTTP request bytes
  • name: Parameter name
  • paramType: Parameter type (ParamURL, ParamBody, ParamCookie)

Returns:

  • Parameter value (decoded)
  • Error if request malformed

Example:

value, _ := GetParameter(request, "id", ParamURL)

func GetParameterOrDefault

func GetParameterOrDefault(request []byte, name string, paramType ParamType, defaultVal string) string

GetParameterOrDefault returns parameter value or default if not found. Provides parameter access with fallback.

Parameters:

  • request: HTTP request bytes
  • name: Parameter name
  • paramType: Parameter type
  • defaultVal: Default value if parameter not found

Returns:

  • Parameter value or default

Example:

limit := GetParameterOrDefault(request, "limit", ParamURL, "10")

func GetPath

func GetPath(request []byte) (string, error)

GetPath extracts the full path (including query string) from a request. Provides direct path access.

Algorithm:

  1. Extract first header line (request line)
  2. Parse using parseRequestLineString
  3. Return path portion (includes query if present)

Parameters:

  • request: HTTP request bytes

Returns:

  • Full path with query (e.g., "/api/users?id=123")
  • Error if request malformed

Example:

path, _ := GetPath(request)
// Returns: "/api/users?id=123"

func GetPathOnly

func GetPathOnly(request []byte) (string, error)

GetPathOnly extracts the path without query string from a request. Provides path-only access.

Algorithm:

  1. Get full path using GetPath
  2. Find '?' character
  3. Return substring before '?'

Parameters:

  • request: HTTP request bytes

Returns:

  • Path without query (e.g., "/api/users")
  • Error if request malformed

Example:

pathOnly, _ := GetPathOnly(request)
// For "/api/users?id=123" returns: "/api/users"

func GetQueryString

func GetQueryString(request []byte) (string, error)

GetQueryString extracts the query string portion from a request path. Provides direct query access.

Algorithm:

  1. Get full path using GetPath
  2. Find '?' character
  3. Return substring after '?'

Parameters:

  • request: HTTP request bytes

Returns:

  • Query string without '?' (e.g., "id=123&name=test")
  • Error if request malformed

Example:

query, _ := GetQueryString(request)
// For "/api?id=123" returns: "id=123"

func GetRequestBuffer

func GetRequestBuffer(n int) []byte

GetRequestBuffer returns a pooled buffer of at least size n. Caller must call PutRequestBuffer when done.

func GetRequestSize

func GetRequestSize(request []byte) int

GetRequestSize returns the total size of the request.

Parameters:

  • request: HTTP request bytes

Returns:

  • Request size in bytes

Example:

size := GetRequestSize(request)

func GetStartType

func GetStartType(response []byte) string

GetStartType detects content type from response body start.

Algorithm:

  1. Find body offset
  2. Skip leading whitespace
  3. Check for known content patterns
  4. Return type identifier

Parameters:

  • response: HTTP response bytes

Returns:

  • Content type string: "[blank]", "<html", "<head", "text", etc.

Example:

startType := GetStartType(response)  // "<html>..." → "<html"
startType := GetStartType(response)  // "{..." → "json"

func GetStatusCode

func GetStatusCode(response []byte) int16

GetStatusCode extracts status code from response (shortcut for AnalyzeResponse).

Algorithm:

  1. Find first space (after HTTP version)
  2. Find second space (after status code)
  3. Parse status code between the two spaces

Parameters:

  • response: HTTP response bytes

Returns:

  • Status code as int16 (e.g., 200, 404, 500)
  • Returns 0 if not a valid response

Example:

code := GetStatusCode(response)  // "HTTP/1.1 404 Not Found" → 404

func GetURLFromRequest

func GetURLFromRequest(scheme string, request []byte) string

GetURLFromRequest constructs a full URL string from a raw HTTP request. Requires the scheme (http/https) since it's not present in raw HTTP request.

Algorithm:

  1. Extract path with query string from request line
  2. Extract Host header value
  3. Combine scheme + host + path

Parameters:

  • scheme: URL scheme ("http" or "https")
  • request: Raw HTTP request bytes

Returns:

Example:

url := GetURLFromRequest("https", []byte("GET /api?id=123 HTTP/1.1\r\nHost: example.com\r\n\r\n"))
// Returns: "https://example.com/api?id=123"

func GetURLFromService

func GetURLFromService(request []byte, httpService *Service) (*urlutil.URL, error)

GetURLFromService constructs a complete URL from raw request bytes and Service. Extracts path from raw bytes using GetPath(), then combines with Service data.

Algorithm:

  1. Extract path with query string from raw request
  2. Get scheme, host, port from Service
  3. Construct URL string: scheme://host:port/path
  4. Parse into urlutil.URL to populate all fields

Parameters:

  • request: Raw HTTP request bytes
  • httpService: HTTP service containing host, port, protocol

Returns:

  • Constructed urlutil.URL with scheme, host, port, path, and query params
  • Error if request malformed or httpService nil

Example:

url, err := GetURLFromService(rawRequest, httpService)
// For raw request "GET /api/users?id=123 HTTP/1.1" and httpService with https://example.com:8443
// Returns: https://example.com:8443/api/users?id=123

func GetURLParameter

func GetURLParameter(request []byte, name string) (string, error)

GetURLParameter extracts a single URL parameter value by name. Provides direct parameter access without AnalyzeRequest.

Algorithm:

  1. Get query string
  2. Use ParseQueryString to parse into parameters
  3. Find parameter by name
  4. Return decoded value

Parameters:

  • request: HTTP request bytes
  • name: Parameter name to find

Returns:

  • Parameter value (decoded)
  • Error if request malformed

Example:

value, _ := GetURLParameter(request, "id")
// For "?id=123&name=test" returns: "123"

func GetURLParametersMap

func GetURLParametersMap(request []byte) (map[string]string, error)

GetURLParametersMap returns all URL parameters as a map. Provides map-based parameter access.

Algorithm:

  1. Get query string
  2. Parse using ParseQueryString
  3. Convert to map (last value wins if duplicates)

Parameters:

  • request: HTTP request bytes

Returns:

  • Map of parameter name → value
  • Error if request malformed

Example:

params, _ := GetURLParametersMap(request)
// For "?id=123&name=test" returns: {"id": "123", "name": "test"}

func HasBody

func HasBody(request []byte) (bool, error)

HasBody checks if the request contains a body. Provides body existence check.

Algorithm:

  1. Find body offset
  2. Check if offset is valid and body size > 0

Parameters:

  • request: HTTP request bytes

Returns:

  • true if body exists and has content
  • Error if request malformed

Example:

hasBody, _ := HasBody(request)

func HasCookie

func HasCookie(request []byte, name string) (bool, error)

HasCookie checks if a cookie exists.

Parameters:

  • request: HTTP request bytes
  • name: Cookie name

Returns:

  • true if cookie exists
  • Error if request malformed

Example:

hasSess, _ := HasCookie(request, "session")

func HasHeader

func HasHeader(request []byte, name string) (bool, error)

HasHeader checks if a header exists in the request.

Algorithm:

  1. Get header value
  2. Return true if not empty

Parameters:

  • request: HTTP request bytes
  • name: Header name (case-insensitive)

Returns:

  • true if header exists
  • Error if request malformed

Example:

hasAuth, _ := HasHeader(request, "Authorization")

func HasParameter

func HasParameter(request []byte, name string, paramType ParamType) (bool, error)

HasParameter checks if a parameter exists. Provides parameter existence check.

Parameters:

  • request: HTTP request bytes
  • name: Parameter name
  • paramType: Parameter type

Returns:

  • true if parameter exists
  • Error if request malformed

Example:

exists, _ := HasParameter(request, "debug", ParamURL)

func HasURLEncodedBody

func HasURLEncodedBody(headers []string) bool

HasURLEncodedBody checks if an HTTP request has a URL-encoded body. Examines Content-Type header for application/x-www-form-urlencoded.

Algorithm:

  1. Extract Content-Type header from headers list
  2. Parse MIME type (before semicolon)
  3. Compare against "application/x-www-form-urlencoded" (case-insensitive)

Example:

headers := []string{
	"POST / HTTP/1.1",
	"Content-Type: application/x-www-form-urlencoded",
	"Content-Length: 10",
}
hasURLEncoded := HasURLEncodedBody(headers)
// hasURLEncoded = true

Parameters:

  • headers: List of HTTP header lines (from ExtractHeaders or ExtractAllHeaders)

Returns:

  • true if Content-Type is application/x-www-form-urlencoded
  • false otherwise

func HasURLParameter

func HasURLParameter(request []byte, name string) (bool, error)

HasURLParameter checks if a URL parameter exists. Provides parameter existence check.

Algorithm:

  1. Get URL parameter value
  2. Return true if found (even if empty value)

Parameters:

  • request: HTTP request bytes
  • name: Parameter name to check

Returns:

  • true if parameter exists
  • Error if request malformed

Example:

exists, _ := HasURLParameter(request, "id")
func Header(headers []string, name string) string

GetHeader retrieves header value by name (case-insensitive).

Parameters:

  • headers: List of header lines from ExtractHeaders
  • name: Header name to search for (e.g., "Content-Type")

Returns:

  • Header value (trimmed), or empty string if not found

Logic:

  1. Loop through headers, skipping request/status line
  2. Parse each header as "Name: Value"
  3. Compare name case-insensitively
  4. Return trimmed value

Example:

headers := []string{"GET / HTTP/1.1", "Host: example.com", "Content-Type: text/html"}
value := Header(headers, "content-type")
// value = "text/html"

func HeadersToStrings

func HeadersToStrings(headers []HttpHeader) []string

HeadersToStrings converts HttpHeader slice to string slice. Useful for rebuilding raw HTTP messages.

func HexCharToValue

func HexCharToValue(ch byte) int

HexCharToValue converts a hexadecimal character to its numeric value. Uses switch/case logic (NO REGEX per requirements).

Algorithm:

  1. Check if character is '0'-'9': return value 0-9
  2. Check if character is 'a'-'f': return value 10-15
  3. Check if character is 'A'-'F': return value 10-15
  4. Otherwise return -1 (invalid hex)

Example:

HexCharToValue('3')  // Returns 3
HexCharToValue('A')  // Returns 10
HexCharToValue('f')  // Returns 15
HexCharToValue('G')  // Returns -1 (invalid)

Parameters:

  • ch: Character to convert

Returns:

  • Numeric value (0-15), or -1 if invalid hex character
Example

ExampleHexCharToValue demonstrates hex character conversion

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	chars := []byte{'0', '9', 'A', 'F', 'a', 'f', 'G'}

	for _, ch := range chars {
		value := httpmsg.HexCharToValue(ch)
		if value != -1 {
			fmt.Printf("'%c' = %d\n", ch, value)
		} else {
			fmt.Printf("'%c' = invalid\n", ch)
		}
	}

}
Output:
'0' = 0
'9' = 9
'A' = 10
'F' = 15
'a' = 10
'f' = 15
'G' = invalid

func HostnameFromURL

func HostnameFromURL(raw string) string

HostnameFromURL parses a URL string and returns its lowercased, port-stripped hostname, or "" when it has no host. Used to scope carried sessions to the exact host they were harvested from.

func HttpHeadersContain

func HttpHeadersContain(headers []HttpHeader, name string) bool

HttpHeadersContain checks if headers contain a header with the given name (case-insensitive).

func IndexOf

func IndexOf(haystack, needle []byte, caseSensitive bool, start, end int) int

IndexOf finds the first occurrence of a pattern in data. Uses loop-based search with optional case-insensitive comparison.

Example:

data := []byte("Hello World")
pos := IndexOf(data, []byte("World"), true, 0, len(data))
// Returns 6

pos := IndexOf(data, []byte("world"), false, 0, len(data))
// Returns 6 (case-insensitive)

Parameters:

  • haystack: Data to search in (cannot be null)
  • needle: Pattern to find (cannot be null)
  • caseSensitive: Whether search is case-sensitive
  • start: Start position (must be >= 0)
  • end: End position (must be >= start and <= len(haystack))

Returns:

  • Position of first match, or -1 if not found
  • Returns -1 if inputs are invalid

func IndexOfByte

func IndexOfByte(data []byte, target byte, startOffset int) int

IndexOfByte finds the first occurrence of a target byte starting from startOffset.

Algorithm:

  1. Loop from startOffset to end of array
  2. Compare each byte with target
  3. Return index of first match
  4. Return -1 if not found

Example:

data := []byte("hello")
idx := IndexOfByte(data, 'l', 0)  // Returns 2 (first 'l')
idx := IndexOfByte(data, 'l', 3)  // Returns 3 (second 'l')
idx := IndexOfByte(data, 'x', 0)  // Returns -1 (not found)

Parameters:

  • data: Byte array to search in
  • target: Byte to find
  • startOffset: Position to start searching from

Returns:

  • Index of first occurrence, or -1 if not found
Example

Example demonstrating IndexOfByte

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	data := []byte("hello world")

	// Find first 'o'
	idx1 := httpmsg.IndexOfByte(data, 'o', 0)
	fmt.Printf("First 'o' at index: %d\n", idx1)

	// Find second 'o' (start searching after first)
	idx2 := httpmsg.IndexOfByte(data, 'o', idx1+1)
	fmt.Printf("Second 'o' at index: %d\n", idx2)

	// Not found
	idx3 := httpmsg.IndexOfByte(data, 'x', 0)
	fmt.Printf("'x' found at: %d\n", idx3)

}
Output:
First 'o' at index: 4
Second 'o' at index: 7
'x' found at: -1

func IndexOfByteInRange

func IndexOfByteInRange(haystack []byte, target byte, caseSensitive bool, start, end int) int

IndexOfByteInRange finds a byte within a range.

Example:

data := []byte("Hello World")
IndexOfByteInRange(data, 'W', true, 0, len(data))  // 6
IndexOfByteInRange(data, 'w', false, 0, len(data)) // 6 (case-insensitive)

Parameters:

  • haystack: Data to search in
  • target: Byte to find
  • caseSensitive: Whether search is case-sensitive
  • start: Start position
  • end: End position

Returns:

  • Index of first match, or -1 if not found

func IndexOfBytes

func IndexOfBytes(data []byte, target []byte, startOffset int) int

IndexOfBytes finds the first occurrence of a target byte sequence starting from startOffset.

Algorithm:

  1. Loop from startOffset to end
  2. For each position, check if target sequence matches
  3. Use nested loop for multi-byte matching
  4. Return index of first match
  5. Return -1 if not found

Example:

data := []byte("hello world")
idx := IndexOfBytes(data, []byte("wor"), 0)  // Returns 6
idx := IndexOfBytes(data, []byte("xyz"), 0)  // Returns -1

Parameters:

  • data: Byte array to search in
  • target: Byte sequence to find
  • startOffset: Position to start searching from

Returns:

  • Index of first occurrence, or -1 if not found
Example

Example demonstrating IndexOfBytes for finding byte sequences

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	data := []byte("The quick brown fox jumps over the lazy dog")

	// Find "quick"
	idx1 := httpmsg.IndexOfBytes(data, []byte("quick"), 0)
	fmt.Printf("'quick' found at: %d\n", idx1)

	// Find "the" (case sensitive)
	idx2 := httpmsg.IndexOfBytes(data, []byte("the"), 0)
	fmt.Printf("'the' found at: %d\n", idx2)

	// Not found
	idx3 := httpmsg.IndexOfBytes(data, []byte("cat"), 0)
	fmt.Printf("'cat' found at: %d\n", idx3)

}
Output:
'quick' found at: 4
'the' found at: 31
'cat' found at: -1

func InsertionPointRequiresContentLengthUpdate

func InsertionPointRequiresContentLengthUpdate(ipType InsertionPointType) bool

InsertionPointRequiresContentLengthUpdate returns whether an insertion point type requires Content-Length header updates when modifying request body.

Body parameters need updates because they modify request body length:

  • INS_PARAM_BODY: POST body parameters
  • INS_PARAM_XML: XML elements
  • INS_PARAM_XML_ATTR: XML attributes
  • INS_PARAM_MULTIPART_ATTR: Multipart attributes
  • INS_PARAM_JSON: JSON values
  • INS_ENTIRE_BODY: Entire body

URL/Cookie/Path parameters don't need updates because they don't affect body length.

func IntToString

func IntToString(n int) string

IntToString converts integer to string using loops (NO strconv.Itoa).

Parameters:

  • n: Integer to convert

Returns:

  • String representation

func IsAbsoluteURL

func IsAbsoluteURL(urlBytes []byte) bool

IsAbsoluteURL checks if URL has a protocol.

Parameters:

  • urlBytes: URL bytes to check

Returns:

  • true if URL starts with "http://" or "https://"

Example:

isAbs := IsAbsoluteURL([]byte("http://example.com"))
// Returns true

isAbs := IsAbsoluteURL([]byte("/path"))
// Returns false
Example

ExampleIsAbsoluteURL demonstrates checking if URL is absolute

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	absoluteURL := []byte("http://example.com/path")
	relativeURL := []byte("/path")

	fmt.Printf("http://example.com/path is absolute: %v\n", httpmsg.IsAbsoluteURL(absoluteURL))
	fmt.Printf("/path is absolute: %v\n", httpmsg.IsAbsoluteURL(relativeURL))

}
Output:
http://example.com/path is absolute: true
/path is absolute: false

func IsHTTP2

func IsHTTP2(request []byte) bool

IsHTTP2 checks if request uses HTTP/2 protocol.

Algorithm:

  1. Find end of first line
  2. Check if line ends with "HTTP/2" or starts with pseudo-headers
  3. Return true if HTTP/2 indicators found

Parameters:

  • request: HTTP request bytes

Returns:

  • true if request uses HTTP/2 protocol

Example:

isH2 := IsHTTP2(request)  // "GET / HTTP/2\r\n..." → true

func IsMethodType

func IsMethodType(request []byte, method string) (bool, error)

IsMethodType checks if the request uses a specific HTTP method. Provides method checking.

Algorithm:

  1. Extract method using GetMethod
  2. Compare case-insensitively

Parameters:

  • request: HTTP request bytes
  • method: Method to check (e.g., "GET", "POST")

Returns:

  • true if method matches
  • Error if request malformed

Example:

isPost, _ := IsMethodType(request, "POST")

func IsRedirectStatus added in v0.4.9

func IsRedirectStatus(code int) bool

IsRedirectStatus reports whether a status code is one that carries a Location, i.e. names a destination.

304 is excluded: it is a cache validator, not a redirect, and has no destination to show. 300/305/306 are included because they are 3xx with a Location; none is seen in practice, and excluding them by hand is how a second, subtly different spelling of "redirect" gets written.

It lives here because every layer that asks needs the same answer: the transport's chain handling, the executor's chain_truncated write, discovery's redirect detector, and the stored-record read path that renders chains. Four packages previously carried four spellings of this switch, which is the mechanism by which a write path and the read path rendering it come to disagree — a disagreement whose symptom is a blank destination, not an error.

func IsRelativeURL

func IsRelativeURL(urlBytes []byte) bool

IsRelativeURL checks if URL is relative (no protocol).

Parameters:

  • urlBytes: URL bytes to check

Returns:

  • true if URL has no protocol

Example:

isRel := IsRelativeURL([]byte("/path"))
// Returns true

isRel := IsRelativeURL([]byte("http://example.com"))
// Returns false
Example

ExampleIsRelativeURL demonstrates checking if URL is relative

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	absoluteURL := []byte("https://example.com/path")
	relativeURL := []byte("/api/users")

	fmt.Printf("https://example.com/path is relative: %v\n", httpmsg.IsRelativeURL(absoluteURL))
	fmt.Printf("/api/users is relative: %v\n", httpmsg.IsRelativeURL(relativeURL))

}
Output:
https://example.com/path is relative: false
/api/users is relative: true

func IsRequest

func IsRequest(message []byte) bool

IsRequest checks if message is an HTTP request (not a response). Complement of IsResponse.

Parameters:

  • message: HTTP message bytes

Returns:

  • true if message is an HTTP request

Example:

isReq := IsRequest(data)  // "GET / HTTP/1.1..." → true
isReq := IsRequest(data)  // "HTTP/1.1 200 OK..." → false

func IsResponse

func IsResponse(message []byte) bool

IsResponse checks if message is an HTTP response (starts with "HTTP/").

Algorithm:

  1. Check if message starts with "HTTP/" (case-insensitive)
  2. Return true if it does

Parameters:

  • message: HTTP message bytes

Returns:

  • true if message is an HTTP response

Example:

isResp := IsResponse(data)  // "HTTP/1.1 200 OK..." → true
isResp := IsResponse(data)  // "GET / HTTP/1.1..." → false

func IsWellFormed

func IsWellFormed(request []byte) bool

IsWellFormed checks if request is well-formed (convenience wrapper).

Parameters:

  • request: HTTP request bytes

Returns:

  • true if request is valid

Example:

if IsWellFormed(request) {
    // Process request
}

func IsWhitespace

func IsWhitespace(c byte) bool

IsWhitespace checks if character is space or tab. Helper for TrimSpace.

Parameters:

  • c: Character byte

Returns:

  • true if space (0x20) or tab (0x09)

func MeasuredMillis

func MeasuredMillis(d time.Duration) int64

MeasuredMillis converts a measured duration to the milliseconds stored in http_records.response_time_ms (and the equivalent columns the discovery and browser stores carry it through).

It owns the one invariant the whole timing feature rests on. Those columns are INTEGER DEFAULT 0 with no NULL to express "never measured", and most responses in this process are rebuilt from stored bytes and genuinely have no timing — so a MEASURED value is floored at 1ms, and only an unmeasured one is allowed to be 0. Without the floor a loopback probe and an un-timed import are the same value, which is what made db stats' p50/p95/p99 a percentile over a column of zeros.

Every path that writes one of those columns must go through here. Three hand-written copies of this rule had already drifted into three shapes before it was centralized.

func MergeCookieHeaders

func MergeCookieHeaders(existing, carried string) string

MergeCookieHeaders returns a Cookie header value that keeps every cookie in existing and appends any cookie from carried whose name is not already present. Existing cookies always win — a request that already carries a cookie (e.g. a browser-crawled record) is never overwritten, we only add the clearance/session cookies it is missing.

func NormalizeHost

func NormalizeHost(host string) string

NormalizeHost lowercases a host and strips any port, returning the bare hostname used as the CarriedSession key and the request-match key. Delegates port-stripping to the package's IPv6-aware extractHostname.

func NormalizeLineEndings

func NormalizeLineEndings(request []byte) []byte

NormalizeLineEndings ensures all line endings are CRLF.

Parameters:

  • request: HTTP request bytes

Returns:

  • Normalized request

Example:

normalized := NormalizeLineEndings(request)

func OriginFromURL

func OriginFromURL(raw string) string

OriginFromURL returns the normalized origin (scheme://host[:port]) of raw, or "" when it has no scheme+host. A default port for the scheme is dropped so origins compare equal regardless of an explicit vs implicit default port.

func OriginMatchesURL

func OriginMatchesURL(origin string, u *url.URL) bool

OriginMatchesURL reports whether u has the same origin (scheme, host, port) as the normalized origin string produced by OriginFromURL. An empty origin or nil URL never matches.

func OriginRefererScheme

func OriginRefererScheme(raw, host string, targetPort int) (scheme, header string, ok bool)

OriginRefererScheme returns the URL scheme ("http" or "https") declared by the request's Origin or Referer header, but only when that header names the same host — and, when targetPort > 0, the same port — we are about to connect to. It lets raw requests captured from a browser/proxy keep their real scheme when the request line is origin-form (no scheme) and the port is non-standard — e.g. an http service on :3000. A cross-origin Origin/Referer (a different host, or a different port when targetPort is known, as in a CORS request or an external referrer) is ignored so it cannot mislead scheme inference; Origin is preferred over Referer as it is the exact origin the browser attached. host must be the bare hostname (no port); targetPort is the port being connected to (0 when unknown, which falls back to host-only matching). header names which header supplied the scheme ("Origin"/"Referer"). Returns ok=false when neither header yields a same-origin http/https scheme.

func ParseContentType

func ParseContentType(headers []string) (contentType string, boundary string)

ParseContentType extracts MIME type and boundary from Content-Type header. Handles multipart/form-data boundary parsing.

Parameters:

  • headers: List of header lines from ExtractHeaders

Returns:

  • contentType: MIME type (e.g., "multipart/form-data")
  • boundary: Boundary string for multipart, or empty string

Logic:

  1. Get Content-Type header value
  2. Parse MIME type (before semicolon)
  3. Parse parameters (after semicolon)
  4. Extract boundary parameter if present

Example:

headers := []string{"POST / HTTP/1.1", "Content-Type: multipart/form-data; boundary=----WebKit"}
contentType, boundary := ParseContentType(headers)
// contentType = "multipart/form-data"
// boundary = "----WebKit"

func ParseHostPort

func ParseHostPort(hostBytes []byte) (host string, port int)

ParseHostPort extracts host and port from "host:port" string. Loop-based parsing (no strings.Split or strconv).

Parameters:

  • hostBytes: Bytes containing host:port (e.g., "example.com:8080")

Returns:

  • host: Hostname or IP address
  • port: Port number, or -1 if not specified

Algorithm:

  1. Find ':' separator using loop
  2. Extract host before ':'
  3. Extract port after ':' and parse as integer
  4. Return -1 for port if no ':' found

Example:

hostBytes := []byte("example.com:8080")
host, port := ParseHostPort(hostBytes)
// host = "example.com", port = 8080

hostBytes := []byte("example.com")
host, port := ParseHostPort(hostBytes)
// host = "example.com", port = -1
Example

ExampleParseHostPort demonstrates parsing host and port

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	hostBytes := []byte("api.example.com:8443")

	host, port := httpmsg.ParseHostPort(hostBytes)

	fmt.Printf("Host: %s\n", host)
	fmt.Printf("Port: %d\n", port)

}
Output:
Host: api.example.com
Port: 8443
Example (NoPort)

ExampleParseHostPort_noPort demonstrates parsing host without port

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	hostBytes := []byte("example.com")

	host, port := httpmsg.ParseHostPort(hostBytes)

	fmt.Printf("Host: %s\n", host)
	fmt.Printf("Port: %d (not specified)\n", port)

}
Output:
Host: example.com
Port: -1 (not specified)

func ParseInt

func ParseInt(s string) int

ParseInt parses an integer string using loops (no strconv.Atoi). Loop-based digit-by-digit parsing.

Parameters:

  • s: String containing digits

Returns:

  • Parsed integer, or -1 if invalid

Algorithm:

  1. Loop through each character
  2. Check if character is digit (0-9)
  3. Build result: result = result*10 + digit
  4. Return -1 if non-digit found

Example:

port := ParseInt("8080")
// Returns 8080

port := ParseInt("abc")
// Returns -1 (invalid)

func ParseParameter

func ParseParameter(params string, name string) string

ParseParameter extracts parameter value from header parameter string. Loop-based parsing for "name=value" or "name=\"value\"" format.

Parameters:

  • params: Parameter string (e.g., "boundary=----WebKit; charset=utf-8")
  • name: Parameter name to extract

Returns:

  • Parameter value, or empty string if not found

Logic:

  1. Find parameter name (case-insensitive)
  2. Skip past "name="
  3. Handle quoted values ("value")
  4. Handle unquoted values (until semicolon or end)

Example:

params := "boundary=----WebKit; charset=utf-8"
value := ParseParameter(params, "boundary")
// value = "----WebKit"

func PrependBody

func PrependBody(request []byte, data []byte) ([]byte, error)

PrependBody prepends data to the existing request body. Provides body prepend operation.

Algorithm:

  1. Get current body
  2. Prepend new data
  3. Set combined body

Parameters:

  • request: HTTP request bytes
  • data: Data to prepend

Returns:

  • Modified request with prepended body
  • Error if request malformed

Example:

modified, _ := PrependBody(request, []byte("prefix_"))

func PutRequestBuffer

func PutRequestBuffer(buf []byte)

PutRequestBuffer returns a buffer to the pool. Buffers larger than 1 MiB are not pooled to avoid holding excessive memory.

func ReconcileDeclaredBody added in v0.5.2

func ReconcileDeclaredBody(raw []byte) []byte

ReconcileDeclaredBody returns raw with its body and its declared Content-Length in agreement. See the file comment for which side wins and why. It returns raw itself — not a copy — whenever there is nothing to reconcile, which is the overwhelmingly common case.

func RemoveAllParametersByType

func RemoveAllParametersByType(request []byte, paramType ParamType) ([]byte, error)

RemoveAllParametersByType removes all parameters of a specific type. Provides batch remove operation.

Parameters:

  • request: HTTP request bytes
  • paramType: Parameter type to remove

Returns:

  • Modified request without parameters of specified type
  • Error if request malformed

Example:

modified, _ := RemoveAllParametersByType(request, ParamURL)

func RemoveCookie

func RemoveCookie(request []byte, name string) ([]byte, error)

RemoveCookie removes a cookie.

Parameters:

  • request: HTTP request bytes
  • name: Cookie name

Returns:

  • Modified request without cookie
  • Error if request malformed

Example:

modified, _ := RemoveCookie(request, "tracking")

func RemoveHeader

func RemoveHeader(message []byte, name string) ([]byte, error)

RemoveHeader removes a header from an HTTP message.

Algorithm:

  1. Extract all headers and body offset
  2. Filter out matching header (case-insensitive)
  3. Rebuild message

Parameters:

  • message: Original HTTP message
  • name: Header name to remove (case-insensitive)

Returns:

  • Modified HTTP message without the header
  • Error if message is malformed

Example:

request := []byte("GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\n")
modified, _ := RemoveHeader(request, "User-Agent")
// Returns: "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"

func RemoveParameter

func RemoveParameter(request []byte, param *Param) ([]byte, error)

RemoveParameter removes a parameter from an HTTP request.

Algorithm:

  1. Analyze request to get all parameters
  2. Find parameter to remove by matching name and type
  3. Remove parameter from appropriate location (URL, body, cookie)
  4. Update Content-Length if body changed

Parameters:

  • request: Original HTTP request
  • param: Parameter to remove (name and type must match)

Returns:

  • Modified request without the parameter
  • Error if parameter not found or request malformed

Example:

param := BuildParameter("debug", "", ParamURL)
modified, _ := RemoveParameter(request, param)

func RemoveParametersByName

func RemoveParametersByName(request []byte, names []string, paramType ParamType) ([]byte, error)

RemoveParametersByName removes all parameters matching names. Provides batch name-based removal.

Parameters:

  • request: HTTP request bytes
  • names: Parameter names to remove
  • paramType: Parameter type

Returns:

  • Modified request without specified parameters
  • Error if request malformed

Example:

modified, _ := RemoveParametersByName(request, []string{"debug", "trace"}, ParamURL)

func ReplaceHeader

func ReplaceHeader(request []byte, name, value string) ([]byte, error)

ReplaceHeader atomically replaces a header value (remove + add).

Algorithm:

  1. Remove existing header if present
  2. Add new header

Parameters:

  • request: HTTP request bytes
  • name: Header name
  • value: New header value

Returns:

  • Modified request with replaced header
  • Error if request malformed

Example:

modified, _ := ReplaceHeader(request, "User-Agent", "CustomAgent/1.0")

func RequestsEqual

func RequestsEqual(req1, req2 []byte) bool

RequestsEqual checks if two requests are byte-level equal.

Parameters:

  • req1: First request
  • req2: Second request

Returns:

  • true if requests are identical

Example:

if RequestsEqual(req1, req2) {
    // Requests are identical
}

func RequestsEqualNormalized

func RequestsEqualNormalized(req1, req2 []byte) bool

RequestsEqualNormalized checks if requests are equal after normalization.

Parameters:

  • req1: First request
  • req2: Second request

Returns:

  • true if requests are equal after normalization

Example:

if RequestsEqualNormalized(req1, req2) {
    // Requests are functionally identical
}

func SetAllCookies

func SetAllCookies(request []byte, cookies map[string]string) ([]byte, error)

SetAllCookies replaces all cookies with those from a map.

Parameters:

  • request: HTTP request bytes
  • cookies: Map of cookie name → value

Returns:

  • Modified request with new cookies
  • Error if request malformed

Example:

cookies := map[string]string{"session": "abc", "tracking": "xyz"}
modified, _ := SetAllCookies(request, cookies)

func SetBody

func SetBody(request []byte, body []byte) ([]byte, error)

SetBody replaces the body of an HTTP request. Provides direct body modification.

Algorithm:

  1. Extract headers
  2. Build new request with headers + new body
  3. Update Content-Length header

Parameters:

  • request: HTTP request bytes
  • body: New body bytes

Returns:

  • Modified request with new body
  • Error if request malformed

Example:

newBody := []byte("new data")
modified, _ := SetBody(request, newBody)

func SetBodyParametersMap

func SetBodyParametersMap(request []byte, params map[string]string) ([]byte, error)

SetBodyParametersMap replaces all body parameters with those from a map. Provides map-based body parameter setting.

Parameters:

  • request: HTTP request bytes
  • params: Map of parameter name → value

Returns:

  • Modified request with new body parameters
  • Error if request malformed

Example:

params := map[string]string{"username": "admin", "password": "test"}
modified, _ := SetBodyParametersMap(request, params)

func SetBodyString

func SetBodyString(request []byte, body string) ([]byte, error)

SetBodyString replaces the body with a string value. Provides string body modification.

Algorithm:

  1. Convert string to bytes
  2. Call SetBody

Parameters:

  • request: HTTP request bytes
  • body: New body as string

Returns:

  • Modified request with new body
  • Error if request malformed

Example:

modified, _ := SetBodyString(request, "new data")

func SetBuildVersion

func SetBuildVersion(v string)

SetBuildVersion records the running binary version so a configured User-Agent containing the {version} placeholder resolves correctly. Called once from the CLI layer (httpmsg cannot import the version, that would cycle).

func SetContentType

func SetContentType(request []byte, contentType string) ([]byte, error)

SetContentType sets the Content-Type header.

Algorithm:

  1. Call ReplaceHeader for "Content-Type"

Parameters:

  • request: HTTP request bytes
  • contentType: Content-Type value

Returns:

  • Modified request with Content-Type set
  • Error if request malformed

Example:

modified, _ := SetContentType(request, "application/json")

func SetCookie

func SetCookie(request []byte, name, value string) ([]byte, error)

SetCookie sets or updates a cookie.

Parameters:

  • request: HTTP request bytes
  • name: Cookie name
  • value: Cookie value

Returns:

  • Modified request with cookie set
  • Error if request malformed

Example:

modified, _ := SetCookie(request, "session", "abc123")

func SetDefaultUserAgent

func SetDefaultUserAgent(ua string)

SetDefaultUserAgent installs the process-global User-Agent selector applied to every outgoing HTTP request across all scan phases. Accepts a selector keyword ("preset" / "random"), a blank string (== "random"), or any literal User-Agent (with {version} expansion). Safe for concurrent use; last writer wins.

func SetMethod

func SetMethod(request []byte, method string) ([]byte, error)

SetMethod replaces the HTTP method in a request. Provides direct method modification.

Algorithm:

  1. Extract all headers and body
  2. Parse current request line
  3. Build new request line with new method
  4. Rebuild request with modified line

Parameters:

  • request: HTTP request bytes
  • method: New HTTP method (e.g., "POST", "PUT")

Returns:

  • Modified request with new method
  • Error if request malformed

Example:

modified, _ := SetMethod(request, "POST")
// Changes "GET /api HTTP/1.1" to "POST /api HTTP/1.1"

func SetPath

func SetPath(request []byte, path string) ([]byte, error)

SetPath replaces the full path (including query string) in a request. Provides direct path modification.

Algorithm:

  1. Extract all headers and body
  2. Parse current request line
  3. Build new request line with new path
  4. Rebuild request with modified line

Parameters:

  • request: HTTP request bytes
  • path: New path (can include query string)

Returns:

  • Modified request with new path
  • Error if request malformed

Example:

modified, _ := SetPath(request, "/api/v2/users?id=456")
// Changes path from old to new

func SetPathOnly

func SetPathOnly(request []byte, pathOnly string) ([]byte, error)

SetPathOnly replaces the path portion while preserving the query string. Provides path-only modification.

Algorithm:

  1. Get current full path
  2. Extract query string if present
  3. Combine new path with existing query
  4. Call SetPath with combined result

Parameters:

  • request: HTTP request bytes
  • pathOnly: New path without query string

Returns:

  • Modified request with new path, preserving query
  • Error if request malformed

Example:

modified, _ := SetPathOnly(request, "/api/v2/users")
// For "/api/users?id=123" changes to "/api/v2/users?id=123"

func SetQueryString

func SetQueryString(request []byte, query string) ([]byte, error)

SetQueryString replaces the query string in a request. Provides direct query modification.

Algorithm:

  1. Get current path without query using GetPathOnly
  2. Append new query string with '?'
  3. Call SetPath with combined result

Parameters:

  • request: HTTP request bytes
  • query: New query string without '?' (e.g., "id=456&name=new")

Returns:

  • Modified request with new query string
  • Error if request malformed

Example:

modified, _ := SetQueryString(request, "id=456&name=new")
// Changes "/api?id=123" to "/api?id=456&name=new"

func SetURLParametersMap

func SetURLParametersMap(request []byte, params map[string]string) ([]byte, error)

SetURLParametersMap replaces all URL parameters with those from a map. Provides map-based parameter setting.

Algorithm:

  1. Build query string from map
  2. URL-encode each name and value
  3. Join with '&'
  4. Call SetQueryString

Parameters:

  • request: HTTP request bytes
  • params: Map of parameter name → value

Returns:

  • Modified request with new parameters
  • Error if request malformed

Example:

params := map[string]string{"id": "456", "name": "new"}
modified, _ := SetURLParametersMap(request, params)
// Creates query string "id=456&name=new"

func SkipLineTerminator

func SkipLineTerminator(data []byte, pos, end int) int

SkipLineTerminator advances position past line ending. Helper function for header parsing.

Parameters:

  • data: Byte slice
  • pos: Current position (at CR or LF)
  • end: End boundary

Returns:

  • New position after line terminator

Logic:

  • If at CRLF (\r\n), skip 2 bytes
  • If at LF (\n) alone, skip 1 byte
  • If at CR (\r) alone, skip 1 byte

func SliceBytes

func SliceBytes(data []byte, start, end int) []byte

SliceBytes performs safe byte slicing with bounds checking.

Example:

data := []byte("hello world")
slice := SliceBytes(data, 0, 5)  // Returns []byte("hello")
slice := SliceBytes(data, 6, 20) // Returns []byte("world") - capped at length

Parameters:

  • data: Source byte array
  • start: Start index (inclusive)
  • end: End index (exclusive)

Returns:

  • Sliced byte array
  • Empty slice if start >= len(data)
  • Capped at len(data) if end > len(data)
Example

Example demonstrating SliceBytes for safe byte slicing

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	data := []byte("Hello, World!")

	// Normal slice
	slice1 := httpmsg.SliceBytes(data, 0, 5)
	fmt.Printf("Slice [0:5]: %s\n", string(slice1))

	// Slice with end beyond length (automatically capped)
	slice2 := httpmsg.SliceBytes(data, 7, 100)
	fmt.Printf("Slice [7:100]: %s\n", string(slice2))

}
Output:
Slice [0:5]: Hello
Slice [7:100]: World!

func StartsWithIgnoreCase

func StartsWithIgnoreCase(s, prefix string) bool

StartsWithIgnoreCase checks if string starts with prefix (case-insensitive). Helper function for header matching.

func StringToBytes

func StringToBytes(s string) []byte

StringToBytes converts a string to byte array.

Algorithm:

  1. Create byte array with length = string length
  2. Loop through each character
  3. Cast each char to byte
  4. Store in byte array

Note: This truncates Unicode to single bytes. For proper Unicode handling, use []byte(s) directly in production code.

Example:

StringToBytes("hello")   // Returns []byte{104, 101, 108, 108, 111}
StringToBytes("")        // Returns []byte{}

Parameters:

  • s: String to convert

Returns:

  • Byte array

func StripTrailingData

func StripTrailingData(request []byte) []byte

StripTrailingData removes any data after the body.

Parameters:

  • request: HTTP request bytes

Returns:

  • Request without trailing data

Example:

cleaned := StripTrailingData(request)

func SwitchMethod

func SwitchMethod(request []byte, newMethod string) ([]byte, error)

SwitchMethod changes the request to use a different HTTP method. Generalized method switching (beyond GET<->POST).

Algorithm:

  1. Use SetMethod to change method
  2. If switching to GET and has body, optionally move params to URL
  3. If switching from GET to POST, keep params where they are

Note: Unlike ToggleRequestMethod, this doesn't move parameters. Use ToggleRequestMethod for GET↔POST with parameter conversion.

Parameters:

  • request: HTTP request bytes
  • newMethod: Target HTTP method

Returns:

  • Modified request with new method
  • Error if request malformed

Example:

modified, _ := SwitchMethod(request, "PUT")
// Changes method to PUT, keeps params in same locations

func ToLower

func ToLower(c byte) byte

ToLower converts ASCII character to lowercase. Loop-based helper (no library functions).

Parameters:

  • c: Character byte

Returns:

  • Lowercase version if uppercase ASCII letter, otherwise unchanged

func ToLowerByte

func ToLowerByte(b byte) byte

ToLowerByte converts a single byte to lowercase if it's an ASCII letter.

Example:

ToLowerByte('A') // returns 'a'
ToLowerByte('z') // returns 'z'
ToLowerByte('1') // returns '1'

Parameters:

  • b: Byte to convert

Returns:

  • Lowercase byte if input is uppercase letter, otherwise unchanged

func ToLowerString

func ToLowerString(s string) string

ToLowerString converts a string to lowercase using loops (no strings.ToLower).

Parameters:

  • s: String to convert

Returns:

  • Lowercase version of string

Algorithm:

  1. Create result buffer
  2. Loop through each character
  3. Convert uppercase to lowercase using ToLower
  4. Return result string

Example:

lower := ToLowerString("HTTP")
// Returns "http"

func ToggleRequestMethod

func ToggleRequestMethod(request []byte) ([]byte, error)

ToggleRequestMethod toggles between GET and POST methods.

Algorithm:

  1. Parse request to get current method and parameters
  2. Determine new method: GET→POST or POST→GET
  3. If current method is NOT GET: - Convert BODY params to URL params - URL-encode parameter values
  4. If current method IS GET: - Convert URL params to BODY params
  5. Rebuild request with new method

Parameters:

  • request: HTTP request

Returns:

  • Request with toggled method
  • Error if request malformed

Example:

// GET /api?foo=bar → POST /api with body: foo=bar
// POST /api with body: foo=bar → GET /api?foo=bar
toggled, _ := ToggleRequestMethod(request)

func TrimSpace

func TrimSpace(s string) string

TrimSpace removes leading and trailing whitespace. Loop-based implementation (NO regex or strings.TrimSpace).

Parameters:

  • s: String to trim

Returns:

  • String with leading/trailing spaces and tabs removed

Logic:

  1. Find first non-whitespace character (from start)
  2. Find last non-whitespace character (from end)
  3. Return substring

func UpdateContentLength

func UpdateContentLength(message []byte) ([]byte, error)

UpdateContentLength updates or adds Content-Length header.

Algorithm:

  1. Find body offset with strict CRLF CRLF check
  2. If no separator, return unchanged
  3. Extract headers
  4. Calculate body length
  5. If body exists OR Content-Length header exists, update/add it
  6. Return updated message

Parameters:

  • message: HTTP message

Returns:

  • Message with correct Content-Length
  • Error if message is malformed

Example:

request := []byte("POST / HTTP/1.1\r\nHost: example.com\r\n\r\ntest")
updated, _ := UpdateContentLength(request)
// Returns: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\ntest"

func UpdateParameter

func UpdateParameter(request []byte, param *Param) ([]byte, error)

UpdateParameter updates a parameter's value in an HTTP request.

Algorithm:

  1. Remove parameter from request
  2. Add parameter with new value
  3. Return modified request

Note: Update is implemented as remove+add, not direct replacement. This ensures proper handling of parameter encoding and structure.

Parameters:

  • request: Original HTTP request
  • param: Parameter with new value (name, type, and value)

Returns:

  • Modified request with updated parameter
  • Error if parameter not found or request malformed

Example:

param := BuildParameter("version", "2.0", ParamURL)
modified, _ := UpdateParameter(request, param)

func UrlDecode

func UrlDecode(s string) string

UrlDecode decodes a URL-encoded string. Delegates to DecodeQueryValue in query_parser.go.

Example:

UrlDecode("hello+world")      // Returns "hello world"
UrlDecode("a%3Db%26c%3Dd")    // Returns "a=b&c=d"

Parameters:

  • s: URL-encoded string to decode

Returns:

  • Decoded string

func UrlDecodeBytes

func UrlDecodeBytes(data []byte) ([]byte, error)

UrlDecodeBytes decodes a URL-encoded byte array.

Algorithm:

  1. Loop through each byte
  2. Convert '+' to space (byte 32)
  3. Decode %XX hex sequences
  4. Handle %uXXXX unicode sequences (4 hex digits)
  5. Copy other bytes as-is

Example:

UrlDecodeBytes([]byte("hello+world"))      // Returns []byte("hello world")
UrlDecodeBytes([]byte("a%3Db%26c%3Dd"))    // Returns []byte("a=b&c=d")

Parameters:

  • data: URL-encoded byte array to decode

Returns:

  • Decoded byte array
  • Error if decoding fails (for API consistency, though current implementation doesn't error)

func UrlEncode

func UrlEncode(s string) string

UrlEncode encodes a string for safe use in URLs. Delegates to EncodeQueryValue in query_parser.go.

Example:

UrlEncode("hello world")   // Returns "hello+world"
UrlEncode("a=b&c=d")       // Returns "a%3Db%26c%3Dd"

Parameters:

  • s: String to encode

Returns:

  • URL-encoded string

func UrlEncodeBytes

func UrlEncodeBytes(data []byte) []byte

UrlEncodeBytes encodes byte array for safe use in URLs.

Algorithm:

  1. Loop through each byte
  2. Encode special characters (#, %, &, +, :, ;, =, ?, @) as %XX
  3. Encode space as '+'
  4. Leave other characters as-is

Example:

UrlEncodeBytes([]byte("hello world"))   // Returns []byte("hello+world")
UrlEncodeBytes([]byte("a=b&c=d"))       // Returns []byte("a%3Db%26c%3Dd")

Parameters:

  • data: Byte array to encode

Returns:

  • URL-encoded byte array

func UserAgentIsHonestPreset

func UserAgentIsHonestPreset() bool

UserAgentIsHonestPreset reports whether the effective User-Agent selector is the self-identifying "preset" default. When true the operator has not opted into stealth (random/custom), so browser-harvested User-Agents should not be silently pinned onto downstream requests.

func ValidateHeaders

func ValidateHeaders(request []byte) error

ValidateHeaders checks if headers are well-formed.

Parameters:

  • request: HTTP request bytes

Returns:

  • Error if headers malformed, nil if valid

Example:

if err := ValidateHeaders(request); err != nil {
    // Handle invalid headers
}

func ValidateRequest

func ValidateRequest(request []byte) error

ValidateRequest checks if a request is well-formed.

Parameters:

  • request: HTTP request bytes

Returns:

  • Error if request is malformed, nil if valid

Example:

if err := ValidateRequest(request); err != nil {
    // Handle invalid request
}

func ValidateRequestLine

func ValidateRequestLine(request []byte) error

ValidateRequestLine checks if the request line is well-formed.

Parameters:

  • request: HTTP request bytes

Returns:

  • Error if request line malformed, nil if valid

Example:

if err := ValidateRequestLine(request); err != nil {
    // Handle invalid request line
}

Types

type Base64Encoder

type Base64Encoder struct {
	// contains filtered or unexported fields
}

Base64Encoder implements standard Base64 encoding with offset tracking.

func NewBase64Encoder

func NewBase64Encoder() *Base64Encoder

NewBase64Encoder creates a new Base64 encoder in standard mode.

func NewBase64MIMEEncoder

func NewBase64MIMEEncoder() *Base64Encoder

NewBase64MIMEEncoder creates a new Base64 encoder in MIME mode (76-char line breaks).

func (*Base64Encoder) Decode

func (e *Base64Encoder) Decode(input []byte) []byte

Decode performs Base64 decoding.

func (*Base64Encoder) Encode

func (e *Base64Encoder) Encode(input []byte, offsets []int) []byte

Encode performs Base64 encoding with offset tracking.

type BaseInsertionPointInterface

type BaseInsertionPointInterface interface {
	// BuildPayload creates the encoded request with payload injected.
	BuildPayload(payloadBytes []byte, encodingType byte, offsetsIn []int) []byte

	// ComputeOffsets calculates payload offsets in encoded request.
	ComputeOffsets(payloadBytes []byte, encodingType byte, offsetsOut []int) []int
}

BaseInsertionPointInterface defines the abstract methods that insertion points must implement. This interface allows EncodedRequestImpl to call buildPayload/computeOffsets without depending on the concrete BaseInsertionPoint type.

type CarriedCookie added in v0.5.2

type CarriedCookie struct {
	Name  string
	Value string
	// Domain is the cookie's domain WITHOUT a leading dot, lowercased. For a
	// host-only cookie it is the exact host the cookie was set on.
	Domain string
	// Path is the cookie's path attribute; "/" when the browser reported none.
	Path string
	// HostOnly is true for a cookie with no Domain attribute, which applies to
	// the exact host only and never to its subdomains. CDP reports host-only
	// cookies with a bare domain and domain cookies with a leading dot, which is
	// how the distinction survives the conversion.
	HostOnly bool
	// Secure restricts the cookie to https (and, per RFC 6265bis, trustworthy
	// origins; this evaluates scheme only, which is what the scanner can see).
	Secure bool
	// Expires is the cookie's expiry. A zero value is a session cookie, which
	// never expires on its own within a scan.
	Expires time.Time
}

CarriedCookie keeps the attributes a flat Cookie header loses. A Cookie request header is only names and values, so once the browser's jar has been flattened there is no way to tell a host-only cookie from a domain-wide one, a /admin cookie from a site-wide one, or a Secure cookie from one that may ride plain HTTP. Carrying them lets the HTTP path evaluate the jar per request the way a browser does, instead of attaching every harvested cookie to every request to the host.

func CarriedCookiesFromHTTP added in v0.5.2

func CarriedCookiesFromHTTP(host string, cookies []*http.Cookie) []CarriedCookie

CarriedCookiesFromHTTP converts a browser-harvested jar into CarriedCookies scoped to host, keeping the attributes a flat Cookie header loses. Cookies that do not apply to host are dropped (a session is only ever sent to the host it was harvested from), as are nameless and expired ones.

The host-only bit is recovered from the Domain attribute: CDP reports a host-only cookie with a bare domain ("example.com") and a domain cookie with a leading dot (".example.com"). A cookie with no domain at all is treated as host-only on host, which is the narrowest honest reading.

type CarriedSession

type CarriedSession struct {
	// CookieHeader is the flattened Cookie request-header value ("a=1; b=2").
	// It is the fallback for sessions harvested without attributes (Cookies
	// empty), and a reasonable summary of the session for logging.
	CookieHeader string
	// Cookies is the harvested jar with the attributes a flat header loses.
	// When non-empty it is authoritative: CookieHeaderFor evaluates it per
	// request rather than sending CookieHeader verbatim.
	Cookies []CarriedCookie
	// UserAgent is the browser User-Agent to pin on downstream requests. Empty
	// means "leave the configured User-Agent alone".
	UserAgent string
	// AuthorizationHeader is a token-based session credential (e.g. "Bearer <jwt>")
	// harvested from the crawl's authenticated traffic. Token-based SPAs (Juice
	// Shop, most JWT apps) keep the session in localStorage and send it as an
	// Authorization header rather than a cookie, so carrying only cookies leaves the
	// scan unauthenticated. Applied only when the outgoing request has no
	// Authorization of its own, and before the operator's -H headers so an explicit
	// -H Authorization still wins. Empty means "carry no token".
	AuthorizationHeader string
	// Origin is the normalized origin (scheme://host[:port]) the token was harvested
	// from. Bearer tokens are origin-scoped (scheme+host+port), unlike cookies, so the
	// AuthorizationHeader is attached only to requests whose origin matches this — a
	// token minted for https://host:3000 must not leak to http://host:8080 on the same
	// hostname. Empty means "no origin recorded": fall back to hostname-only scoping
	// (the pre-origin behavior) so older harvests keep working.
	Origin string
}

CarriedSession is a browser-harvested session (cookies + optional pinned User-Agent) scoped to a single hostname. It is produced by the spidering phase (the real browser establishes a WAF/bot-cleared session) and carried forward into later phases — content discovery and dynamic assessment — so their requests inherit that cleared session instead of starting cold. Callers key these by hostname (same-host only), so the host is not stored.

Cookies carries the harvested jar with its attributes intact and is what CookieHeaderFor evaluates; CookieHeader is the pre-flattened fallback for a harvest that recorded no attributes. UserAgent is only set when the operator opted into a non-default User-Agent (see the runner's carry decision), so the honest "preset" identity is never silently replaced.

func (CarriedSession) CookieHeaderFor added in v0.5.2

func (s CarriedSession) CookieHeaderFor(u *url.URL, now time.Time) string

CookieHeaderFor evaluates the carried jar for ONE outgoing URL the way a browser would (RFC 6265 §5.4): host-only cookies need an exact host match and domain cookies a domain match; the path must match; a Secure cookie is only sent over https; expired cookies are dropped; longer paths come first.

A session harvested before attributes were recorded has no Cookies, and falls back to the pre-flattened CookieHeader — so older harvests keep working exactly as they did.

Where a browser would send two cookies of the same name (one per path), this sends only the most specific. The carried session leaves here as a single flat header and MergeCookieHeaders dedups by name downstream, so the second copy would be dropped there regardless; keeping the longest-path one makes that choice deliberate rather than positional.

func (CarriedSession) CookieHeaderForOrigin added in v0.5.2

func (s CarriedSession) CookieHeaderForOrigin(scheme, host string, now time.Time) string

CookieHeaderForOrigin evaluates the carried jar for an ORIGIN, ignoring path: it is for callers that configure one static Cookie header for a whole target (the content-discovery engine) and so have no single request path to match. Domain and Secure are still honoured — a Secure cookie must not be handed to an http crawl of the same host.

With no path to match, a duplicated cookie name keeps its BROADEST (shortest) path, the copy most likely to apply to an arbitrary path on the origin.

type ContentType

type ContentType byte

ContentType represents the content type of an HTTP request body.

const (
	// ContentTypeNone represents no body or cookie-only content (value: 0)
	ContentTypeNone ContentType = 0

	// ContentTypeURLEncoded represents application/x-www-form-urlencoded (value: 1)
	ContentTypeURLEncoded ContentType = 1

	// ContentTypeMultipart represents multipart/form-data (value: 2)
	ContentTypeMultipart ContentType = 2

	// ContentTypeXML represents XML content (value: 3)
	ContentTypeXML ContentType = 3

	// ContentTypeJSON represents application/json (value: 4)
	ContentTypeJSON ContentType = 4

	// ContentTypeAMF represents serialized AMF (Action Message Format) (value: 5)
	ContentTypeAMF ContentType = 5

	// ContentTypeUnknown represents unknown content type (value: 255)
	ContentTypeUnknown ContentType = 255
)

func GetBodyContentType

func GetBodyContentType(headers []string, request []byte, bodyOffset int) ContentType

GetBodyContentType determines the content type of the request body. Parses Content-Type header and maps to ContentType enum.

Algorithm:

  1. Check if body exists
  2. Parse Content-Type header
  3. Map MIME types to ContentType values: - "multipart" -> MULTIPART - "xml" -> XML - "json" -> JSON - "application/x-amf" -> SER_AMF - default -> URL_ENCODED

Example:

headers := []string{"POST / HTTP/1.1", "Content-Type: application/json"}
bodyOffset := 38
contentType := GetBodyContentType(headers, []byte{}, bodyOffset)
// contentType = ContentTypeJSON

Parameters:

  • headers: List of HTTP header lines
  • request: Complete HTTP request bytes
  • bodyOffset: Offset where body starts

Returns:

  • ContentType enum value
type Cookie struct {
	// Name is the cookie name
	Name string

	// Value is the cookie value
	Value string

	// Domain is the domain attribute
	Domain string

	// Path is the path attribute
	Path string

	// Expiration is the expires/max-age timestamp
	// Returns nil if no expiration set
	Expiration *time.Time
}

Cookie represents an HTTP cookie from Set-Cookie header.

type EncodedInsertionPoint

type EncodedInsertionPoint struct {
	// contains filtered or unexported fields
}

EncodedInsertionPoint implements insertion point with custom encoding.

This insertion point extends the base functionality with: 1. Custom encoder support (URL encoding, Base64, etc.) 2. Prefix/suffix handling 3. Context-aware encoding

func NewEncodedInsertionPoint

func NewEncodedInsertionPoint(name string, request []byte, startOffset, endOffset int, encoder Encoder, prefix []byte, ipType InsertionPointType) *EncodedInsertionPoint

NewEncodedInsertionPoint creates a new encoded insertion point over a private copy of request, so a caller-owned slice cannot be mutated underneath it.

func (*EncodedInsertionPoint) BaseValue

func (e *EncodedInsertionPoint) BaseValue() string

BaseValue returns the encoded base value.

func (*EncodedInsertionPoint) BuildRequest

func (e *EncodedInsertionPoint) BuildRequest(payload []byte) []byte

BuildRequest creates a new request with payload injected and encoded.

func (*EncodedInsertionPoint) GetEncoder

func (e *EncodedInsertionPoint) GetEncoder() Encoder

GetEncoder returns the current encoder.

func (*EncodedInsertionPoint) Name

func (e *EncodedInsertionPoint) Name() string

Name returns the insertion point name.

func (*EncodedInsertionPoint) PayloadOffsets

func (e *EncodedInsertionPoint) PayloadOffsets(payload []byte) []int

PayloadOffsets returns the byte offsets of the payload in the built request.

func (*EncodedInsertionPoint) SetEncoder

func (e *EncodedInsertionPoint) SetEncoder(encoder Encoder)

SetEncoder sets a new encoder.

func (*EncodedInsertionPoint) Type

Type returns the type constant.

type EncodedRequest

type EncodedRequest interface {
	// EncodingType returns the encoding type identifier.
	EncodingType() byte

	// EncodedBytes returns the complete encoded HTTP request.
	// Lazily computed and cached on first access.
	EncodedBytes() []byte

	// Markers returns list of payload position markers.
	Markers() [][]int

	// PayloadOffsets returns byte offsets of payload in encoded request.
	// Lazily computed and cached on first access.
	PayloadOffsets() []int
}

EncodedRequest interface defines access to an encoded HTTP request.

The interface allows callers to get encoded request data without knowing the implementation details of how encoding is performed.

func NewEncodedRequest

func NewEncodedRequest(wrapper *PayloadWrapper, insertionPoint BaseInsertionPointInterface) EncodedRequest

NewEncodedRequest creates a new EncodedRequest implementation.

Parameters:

  • wrapper: Payload wrapper with payload bytes and encoding type
  • insertionPoint: Base insertion point with abstract methods

Returns:

  • New EncodedRequest instance

type EncodedRequestImpl

type EncodedRequestImpl struct {
	// contains filtered or unexported fields
}

EncodedRequestImpl implements EncodedRequest with thread-safe caching.

This implementation provides: - Synchronized access to cached values - Lazy computation via insertion point methods - Thread-safe single computation guarantee

func (*EncodedRequestImpl) EncodedBytes

func (e *EncodedRequestImpl) EncodedBytes() []byte

EncodedBytes returns the encoded request bytes with thread-safe caching. Lazily computed on first access via insertionPoint.BuildPayload().

func (*EncodedRequestImpl) EncodingType

func (e *EncodedRequestImpl) EncodingType() byte

EncodingType returns the encoding type identifier.

func (*EncodedRequestImpl) Markers

func (e *EncodedRequestImpl) Markers() [][]int

Markers returns list of payload position markers. Returns a single marker derived from PayloadOffsets().

func (*EncodedRequestImpl) PayloadOffsets

func (e *EncodedRequestImpl) PayloadOffsets() []int

PayloadOffsets returns the payload offsets with thread-safe caching. Lazily computed on first access via insertionPoint.ComputeOffsets().

type Encoder

type Encoder interface {
	// Encode encodes the payload and adjusts offset positions.
	// payload: the bytes to encode
	// offsets: [startOffset, endOffset] pair that will be updated to reflect encoded positions
	// Returns: encoded bytes
	Encode(payload []byte, offsets []int) []byte

	// Decode reverses the encoding to recover original payload.
	// encoded: the encoded bytes
	// Returns: decoded bytes
	Decode(encoded []byte) []byte
}

Encoder defines the interface for encoding payloads at insertion points.

func GetEncoder

func GetEncoder(encoderType int) Encoder

GetEncoder returns an encoder instance for the specified type.

type GzipEncoder

type GzipEncoder struct{}

GzipEncoder implements Gzip compression with offset tracking.

func NewGzipEncoder

func NewGzipEncoder() *GzipEncoder

NewGzipEncoder creates a new Gzip encoder instance.

func (*GzipEncoder) Decode

func (e *GzipEncoder) Decode(input []byte) []byte

Decode performs Gzip decompression.

func (*GzipEncoder) Encode

func (e *GzipEncoder) Encode(input []byte, offsets []int) []byte

Encode performs Gzip compression with offset tracking.

type HeaderInsertionPoint

type HeaderInsertionPoint struct {
	// contains filtered or unexported fields
}

HeaderInsertionPoint implements InsertionPoint for HTTP header value injection.

BuildRequest uses a precomputed-offset splice (a single allocation, no header re-parse) when the layout is eligible, mirroring the ParameterInsertionPoint fast path. It falls back to AddOrReplaceHeader otherwise (and when the IP is constructed directly without precomputed offsets). The splice keeps an existing header in its original position instead of moving it to the end as AddOrReplaceHeader (RemoveHeader+AddHeader) does — an HTTP-semantically irrelevant difference, the same trade-off buildWithContentLength makes.

func (*HeaderInsertionPoint) BaseValue

func (h *HeaderInsertionPoint) BaseValue() string

BaseValue returns the original header value.

func (*HeaderInsertionPoint) BuildRequest

func (h *HeaderInsertionPoint) BuildRequest(payload []byte) []byte

BuildRequest creates a new request with the payload injected as the header value. Works for both existing and synthetic headers.

func (*HeaderInsertionPoint) Name

func (h *HeaderInsertionPoint) Name() string

Name returns the header name.

func (*HeaderInsertionPoint) PayloadOffsets

func (h *HeaderInsertionPoint) PayloadOffsets(payload []byte) []int

PayloadOffsets returns the byte offsets of the payload in the built request. Builds the request first, then uses GetHeaderOffsets to locate the value.

func (*HeaderInsertionPoint) Type

Type returns INS_HEADER.

type HexBuilder

type HexBuilder struct {
	// contains filtered or unexported fields
}

HexBuilder builds hex-encoded insertion point metadata strings.

Algorithm: 1. Compute encoding value from parameter types 2. Store offset values 3. Format as hex string

The encoding value is computed using a mapping table that combines current and legacy parameter types with different values for same vs. different types.

func NewHexBuilder

func NewHexBuilder(paramType, legacyType ParamType) *HexBuilder

NewHexBuilder creates a new HexBuilder from parameter type and legacy type.

Parameters:

  • paramType: Current parameter type
  • legacyType: Legacy parameter type

Returns:

  • New HexBuilder instance

func NewHexBuilderSingleType

func NewHexBuilderSingleType(paramType ParamType) *HexBuilder

NewHexBuilderSingleType creates a HexBuilder with modified encoding for same type. Adds 128 to the computed encoding value.

Parameters:

  • paramType: Parameter type

Returns:

  • New HexBuilder instance with modified encoding

func (*HexBuilder) GetOffset1

func (h *HexBuilder) GetOffset1() int

GetOffset1 returns the first offset value (-1 if not set).

func (*HexBuilder) GetOffset2

func (h *HexBuilder) GetOffset2() int

GetOffset2 returns the second offset value (-1 if not set).

func (*HexBuilder) SetOffset1

func (h *HexBuilder) SetOffset1(offset int)

SetOffset1 sets the first offset value.

func (*HexBuilder) SetOffset2

func (h *HexBuilder) SetOffset2(offset int)

SetOffset2 sets the second offset value.

func (*HexBuilder) String

func (h *HexBuilder) String() string

String returns the hex-encoded string representation. Format: "HEXxOFFSET1" or "HEXxOFFSET1xOFFSET2" if offset2 is set. Panics if offset1 is not set.

type HexValue

type HexValue struct {
	// contains filtered or unexported fields
}

HexValue represents a parsed hex-encoded insertion point metadata string.

Algorithm: 1. Split string by 'x' separator 2. Validate 2 or 3 components 3. Parse first component as hex integer 4. Set isValid flag based on parsing success

Example:

"5f2x3bx40" -> hexValue=0x5f2, offset1=0x3b, offset2=0x40
"2x5" -> hexValue=0x2, offset1=0x5, offset2=0 (no third component)

func NewHexValue

func NewHexValue(hexStr string) *HexValue

NewHexValue creates a new HexValue by parsing the hex string.

Parameters:

  • hexStr: Hex-encoded string in format "HEXxOFFSETxOFFSET"

Returns:

  • New HexValue instance (isValid=false if parse failed)

func (*HexValue) Equals

func (h *HexValue) Equals(other *HexValue) bool

Equals compares two HexValue instances for equality.

func (*HexValue) HashCode

func (h *HexValue) HashCode() int

HashCode returns a hash code for this HexValue.

func (*HexValue) IsValid

func (h *HexValue) IsValid() bool

IsValid returns whether the hex string was successfully parsed.

func (*HexValue) Offset1

func (h *HexValue) Offset1() int

Offset1 returns the first offset value (second component).

func (*HexValue) Offset2

func (h *HexValue) Offset2() int

Offset2 returns the second offset value (third component). Returns 0 if not present.

func (*HexValue) String

func (h *HexValue) String() string

String returns the original hex string.

func (*HexValue) Value

func (h *HexValue) Value() int

Value returns the parsed hex value (first component).

type HttpHeader

type HttpHeader struct {
	Name  string
	Value string
}

HttpHeader represents an HTTP header with name and value.

func AppendHttpHeader

func AppendHttpHeader(headers []HttpHeader, name, value string) []HttpHeader

AppendHttpHeader returns a new headers slice with the header appended. Does not check for duplicates - adds unconditionally.

func CloneHttpHeaders

func CloneHttpHeaders(headers []HttpHeader) []HttpHeader

CloneHttpHeaders creates a deep copy of the headers slice.

func NewHttpHeader

func NewHttpHeader(name, value string) HttpHeader

NewHttpHeader creates a new HttpHeader with the given name and value.

func ParseHeadersFromStrings

func ParseHeadersFromStrings(headerStrings []string) []HttpHeader

ParseHeadersFromStrings converts a slice of header strings to HttpHeader slice. Skips the first line (request/status line) automatically.

func ParseHttpHeader

func ParseHttpHeader(line string) HttpHeader

ParseHttpHeader parses a header line in "Name: Value" format. Returns empty HttpHeader if the line is invalid.

func RemoveHttpHeader

func RemoveHttpHeader(headers []HttpHeader, name string) []HttpHeader

RemoveHttpHeader returns a new headers slice with all headers of the given name removed.

func SetHttpHeader

func SetHttpHeader(headers []HttpHeader, name, value string) []HttpHeader

SetHttpHeader returns a new headers slice with the header added or updated. If a header with the same name exists, it will be replaced.

func (HttpHeader) String

func (h HttpHeader) String() string

String returns the header as "Name: Value" format.

type HttpRequest

type HttpRequest struct {
	// contains filtered or unexported fields
}

HttpRequest represents an HTTP request with raw bytes as source of truth.

Design:

  • Raw bytes are the source of truth
  • Parsed fields (method, path, headers) are cached on first access
  • With* methods return new instances (immutable pattern)
  • Service contains host, port, protocol info

func HttpRequestFromURL

func HttpRequestFromURL(urlStr string) (*HttpRequest, error)

HttpRequestFromURL creates a basic GET request from a URL string. Uses DefaultBrowserHeaders to mimic a real Chrome browser.

func NewHttpRequest

func NewHttpRequest(raw []byte) *HttpRequest

NewHttpRequest creates a new HttpRequest from raw bytes.

func NewHttpRequestWithService

func NewHttpRequestWithService(service *Service, raw []byte) *HttpRequest

NewHttpRequestWithService creates a new HttpRequest with service info.

func (*HttpRequest) Apply

func (r *HttpRequest) Apply(opts ...RequestOption) *HttpRequest

Apply applies multiple options to the request in a single operation. This is more efficient than chaining multiple With* methods because it only rebuilds the request once at the end.

Example:

// Inefficient - rebuilds 3 times:
req = req.WithMethod("POST").WithHeader("X-A", "1").WithHeader("X-B", "2")

// Efficient - rebuilds once:
req = req.Apply(
    WithOptMethod("POST"),
    WithOptHeader("X-A", "1"),
    WithOptHeader("X-B", "2"),
)

func (*HttpRequest) Body

func (r *HttpRequest) Body() []byte

Body returns the request body as bytes.

func (*HttpRequest) BodyOffset

func (r *HttpRequest) BodyOffset() int

BodyOffset returns the byte offset where body starts.

func (*HttpRequest) BodyToString

func (r *HttpRequest) BodyToString() string

BodyToString returns the body as a string.

func (*HttpRequest) Clone

func (r *HttpRequest) Clone() *HttpRequest

Clone creates a deep copy of the HttpRequest.

func (*HttpRequest) HasHeader

func (r *HttpRequest) HasHeader(name string) bool

HasHeader checks if a header exists (case-insensitive).

func (*HttpRequest) Header

func (r *HttpRequest) Header(name string) string

Header returns the value of a specific header (case-insensitive). Returns empty string if not found.

func (*HttpRequest) Headers

func (r *HttpRequest) Headers() []HttpHeader

Headers returns all HTTP headers as a slice. Lazily parsed from raw bytes on first access.

func (*HttpRequest) ID

func (r *HttpRequest) ID() string

ID returns a unique hash identifier for this request. The hash is computed once and cached for subsequent calls. Thread-safe.

func (*HttpRequest) IdentityFingerprint

func (r *HttpRequest) IdentityFingerprint() string

IdentityFingerprint returns a stable, non-secret label for the credentials carried by this request. It is suitable for cache/dedup keys; raw cookies and authorization values are never returned. Anonymous requests share one label.

func (*HttpRequest) IsMediaPath

func (r *HttpRequest) IsMediaPath() bool

IsMediaPath reports whether the request targets a media or JS/static asset (the default-skip set used by active-module eligibility). The result is computed once from the cached URL path and memoized, so the regex is not re-run for every module's CanProcess on the dispatch hot path. A request whose URL cannot be parsed is treated as not media (false); callers that must reject unparseable URLs check URL() separately.

func (*HttpRequest) Method

func (r *HttpRequest) Method() string

Method returns the HTTP method (GET, POST, etc.). Lazily parsed from raw bytes on first access.

func (*HttpRequest) Parameters

func (r *HttpRequest) Parameters() ([]*Param, error)

Parameters analyzes the request and returns all parameters. This is NOT cached as it involves deep parsing.

func (*HttpRequest) Path

func (r *HttpRequest) Path() string

Path returns the request path including query string. Lazily parsed from raw bytes on first access.

func (*HttpRequest) Raw

func (r *HttpRequest) Raw() []byte

Raw returns the raw HTTP request bytes.

func (*HttpRequest) Service

func (r *HttpRequest) Service() *Service

Service returns the HTTP service (host, port, protocol).

func (*HttpRequest) TruncateBody

func (r *HttpRequest) TruncateBody(maxSize int)

TruncateBody truncates the request body to maxSize bytes. Headers are preserved. No-op if body is already within limit.

It rewrites r.raw, so every cache derived from those bytes MUST be dropped. ID() memoizes the SHA-256 of r.raw and was left stale, so a request truncated after its id had been read kept reporting the pre-truncation hash — the value used for record dedup, stored-record lookup, the host-error tracker, the insertion-point cache and the response-cluster key. (Response truncation already invalidates its own body-derived caches.)

func (*HttpRequest) URL

func (r *HttpRequest) URL() (*urlutil.URL, error)

URL constructs and returns the full URL.

The parsed URL is computed once and cached for subsequent calls (raw bytes are immutable; the cache is invalidated when the service changes via HttpRequestResponse.WithService). The returned *urlutil.URL is SHARED across callers and MUST be treated as read-only — callers that need to mutate it (e.g. rewrite the path or params) must Clone() it first, as BuildRetryableRequest already does.

func (*HttpRequest) WithAddedHeader

func (r *HttpRequest) WithAddedHeader(name, value string) *HttpRequest

WithAddedHeader returns a new HttpRequest with a header added. Does not check for duplicates.

func (*HttpRequest) WithBody

func (r *HttpRequest) WithBody(body []byte) *HttpRequest

WithBody returns a new HttpRequest with the body replaced. Updates Content-Length header automatically.

func (*HttpRequest) WithHeader

func (r *HttpRequest) WithHeader(name, value string) *HttpRequest

WithHeader returns a new HttpRequest with the header set (add or update).

func (*HttpRequest) WithMethod

func (r *HttpRequest) WithMethod(method string) *HttpRequest

WithMethod returns a new HttpRequest with the method replaced.

func (*HttpRequest) WithPath

func (r *HttpRequest) WithPath(path string) *HttpRequest

WithPath returns a new HttpRequest with the path replaced.

func (*HttpRequest) WithRemovedHeader

func (r *HttpRequest) WithRemovedHeader(name string) *HttpRequest

WithRemovedHeader returns a new HttpRequest with the header removed.

func (*HttpRequest) WithService

func (r *HttpRequest) WithService(service *Service) *HttpRequest

WithService returns a new HttpRequest with the given service.

type HttpRequestResponse

type HttpRequestResponse struct {
	// contains filtered or unexported fields
}

HttpRequestResponse is a unified struct containing HTTP request and response.

Design:

  • Contains HttpRequest (required) and HttpResponse (optional)
  • Service info is delegated to the request
  • Provides convenience methods for common operations

func FromSentRequest

func FromSentRequest(req *http.Request) (*HttpRequestResponse, error)

FromSentRequest renders an http.Request that has ALREADY been sent into raw request bytes, paired with its Service.

Use it instead of FromStdRequest whenever the request's round trip is over. FromStdRequest calls httputil.DumpRequestOut, which renders the wire bytes by running the request through a dummy transport — a real round trip that honours the request's context. http.Client cancels that context when the response body closes, so on a completed request DumpRequestOut fails with "context canceled" and the caller silently gets nothing. Walking a followed redirect chain is the case that exposed this. Rendering directly is also cheaper: no round trip.

The body is deliberately not rendered. A sent request's body has already been consumed, and Go drops it entirely when following a 301/302/303, so there is nothing to recover and nothing to claim. Header values containing CR/LF are dropped for the same reason GetRawRequestFromURLWithMethod drops them: a stored header must not be able to smuggle a second request. Host is taken from the request rather than the header map, and header names are emitted in sorted order so the same request renders the same bytes every time — the raw request is hashed into the record's identity, and Go's header map iteration order is random.

func FromStdRequest

func FromStdRequest(req *http.Request) (*HttpRequestResponse, error)

FromStdRequest creates HttpRequestResponse from a standard http.Request.

func GetRawRequestFromURL

func GetRawRequestFromURL(url string) (*HttpRequestResponse, error)

GetRawRequestFromURL creates a basic GET request from a URL. Default browser headers will be applied by the HTTP client at request time.

func GetRawRequestFromURLWithMethod

func GetRawRequestFromURLWithMethod(rawURL, method string, headers map[string]string, body []byte) (*HttpRequestResponse, error)

GetRawRequestFromURLWithMethod creates a request from a URL while preserving the discovered HTTP method, request headers, and body. This is the method/body-aware counterpart to GetRawRequestFromURL: it lets non-GET discoveries (form POSTs, JS-derived API calls) be imported without being flattened to a bodyless GET, which would silently lose API and form coverage.

When method is empty/GET and body is empty it delegates to GetRawRequestFromURL for identical behavior. Header values containing CR/LF are dropped to avoid request smuggling from malformed stored headers; Host and Content-Length are always managed here (never taken from the stored map).

func NewHttpRequestResponse

func NewHttpRequestResponse(request *HttpRequest, response *HttpResponse) *HttpRequestResponse

NewHttpRequestResponse creates a new HttpRequestResponse from request and optional response.

func NewRequestResponseRaw

func NewRequestResponseRaw(raw []byte, service *Service) *HttpRequestResponse

NewRequestResponseRaw wraps already-built raw request bytes and a known service into an HttpRequestResponse WITHOUT re-parsing them. It is the fast-path replacement for the `ParseRawRequest(string(builtBytes)).WithService(svc)` idiom on the per-payload hot path: the raw produced by InsertionPoint.BuildRequest / SetMethod / AddHeader / SetPath / SetBody is already well-formed, so the textproto re-parse, the throwaway Service construction, and the []byte→string→ []byte round-trip that ParseRawRequest performs are pure overhead. The request parses its fields lazily (HttpRequest.ensureParsed) only if a caller reads them.

Use this only with TRUSTED, internally-built raw bytes. For untrusted external input that must be validated, use ParseRawRequest (it returns a parse error).

func ParseRawRequest

func ParseRawRequest(raw string) (rr *HttpRequestResponse, err error)

ParseRawRequest parses a raw HTTP request from a string. Note: Response field is optional and should be added manually if needed.

func ParseRawRequestWithURL

func ParseRawRequestWithURL(raw, url string) (*HttpRequestResponse, error)

ParseRawRequestWithURL parses a raw HTTP request with explicit URL override.

func (*HttpRequestResponse) BuildRetryableRequest

func (h *HttpRequestResponse) BuildRetryableRequest() (*retryablehttp.Request, error)

BuildRetryableRequest builds a retryablehttp request from the request response. Note: This method builds a fresh request every time (no caching).

func (*HttpRequestResponse) BuildRetryableRequestWithContext

func (h *HttpRequestResponse) BuildRetryableRequestWithContext(ctx context.Context) (*retryablehttp.Request, error)

BuildRetryableRequestWithContext is BuildRetryableRequest with ctx attached to the underlying *http.Request, so cancelling ctx aborts the in-flight request (and its retry loop). Like the stdlib, ctx must be non-nil; pass context.Background() when there is nothing to cancel against.

func (*HttpRequestResponse) Clone

Clone creates a deep copy of the HttpRequestResponse.

func (*HttpRequestResponse) CreateInsertionPoints

func (h *HttpRequestResponse) CreateInsertionPoints(includeNested bool) ([]InsertionPoint, error)

CreateInsertionPoints creates insertion points from the request. Convenience method that wraps CreateAllInsertionPoints.

func (*HttpRequestResponse) GetScanHash

func (h *HttpRequestResponse) GetScanHash(templateId string) string

GetScanHash returns a unique hash that represents a scan by hashing (URL + templateId).

func (*HttpRequestResponse) HasResponse

func (h *HttpRequestResponse) HasResponse() bool

HasResponse returns true if there is an HTTP response.

func (*HttpRequestResponse) ID

func (h *HttpRequestResponse) ID() string

ID returns a unique identifier for host-based tracking. Returns FNV-1a hash of host:port:method for efficient cache key usage.

func (*HttpRequestResponse) MarshalBytes

func (h *HttpRequestResponse) MarshalBytes() ([]byte, error)

MarshalBytes marshals to JSON bytes.

func (*HttpRequestResponse) MarshalJSON

func (h *HttpRequestResponse) MarshalJSON() ([]byte, error)

MarshalJSON marshals the request response to JSON. Uses jsoniter with shadow structs for single-pass serialization, eliminating the previous triple-marshal pattern.

func (*HttpRequestResponse) MarshalString

func (h *HttpRequestResponse) MarshalString() (string, error)

MarshalString marshals to JSON string.

func (*HttpRequestResponse) MustMarshalBytes

func (h *HttpRequestResponse) MustMarshalBytes() []byte

MustMarshalBytes marshals to JSON bytes (ignores error).

func (*HttpRequestResponse) MustMarshalString

func (h *HttpRequestResponse) MustMarshalString() string

MustMarshalString marshals to JSON string (ignores error).

func (*HttpRequestResponse) PrettyPrint

func (h *HttpRequestResponse) PrettyPrint() string

PrettyPrint returns a formatted string for display.

func (*HttpRequestResponse) Request

func (h *HttpRequestResponse) Request() *HttpRequest

Request returns the HTTP request.

func (*HttpRequestResponse) Response

func (h *HttpRequestResponse) Response() *HttpResponse

Response returns the HTTP response (may be nil).

func (*HttpRequestResponse) Service

func (h *HttpRequestResponse) Service() *Service

Service returns the HTTP service info (delegated to request).

func (*HttpRequestResponse) Target

func (h *HttpRequestResponse) Target() string

Target returns the target URL as a string.

func (*HttpRequestResponse) URL

func (h *HttpRequestResponse) URL() (*urlutil.URL, error)

URL returns the URL by delegating to Request.URL().

func (*HttpRequestResponse) UnmarshalJSON

func (h *HttpRequestResponse) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals the request response from JSON.

func (*HttpRequestResponse) WithResponse

func (h *HttpRequestResponse) WithResponse(response *HttpResponse) *HttpRequestResponse

WithResponse returns a new HttpRequestResponse with the given response. This allows setting a response on a request-only HttpRequestResponse.

func (*HttpRequestResponse) WithService

func (h *HttpRequestResponse) WithService(service *Service) *HttpRequestResponse

WithService sets the service on the request and returns the same HttpRequestResponse. This is a mutating method for convenience when building requests.

type HttpResponse

type HttpResponse struct {
	// contains filtered or unexported fields
}

HttpResponse represents an HTTP response with raw bytes as source of truth.

Design:

  • Raw bytes are the source of truth
  • Parsed fields (statusCode, headers) are cached on first access
  • With* methods return new instances (immutable pattern)

func NewHttpResponse

func NewHttpResponse(raw []byte) *HttpResponse

NewHttpResponse creates a new HttpResponse from raw bytes, with no timing information. Use NewHttpResponseWithDuration when the response was actually sent and timed.

func NewHttpResponseWithDuration

func NewHttpResponseWithDuration(raw []byte, d time.Duration) *HttpResponse

NewHttpResponseWithDuration creates a new HttpResponse from raw bytes and the measured round-trip duration. Only callers that performed a real network send should use it — a response rebuilt from stored bytes has no duration to report and must keep the zero that means "unknown".

func (*HttpResponse) Body

func (r *HttpResponse) Body() []byte

Body returns the response body as bytes.

func (*HttpResponse) BodyLowerString

func (r *HttpResponse) BodyLowerString() string

BodyLowerString returns the lowercased body as a string, memoized and shared. Prefer this over strings.ToLower(resp.BodyToString()) in passive detectors so the lowercased body is computed once per response, not once per module. It reuses the memoized BodyToString so it adds a single lowered-string allocation.

func (*HttpResponse) BodyOffset

func (r *HttpResponse) BodyOffset() int

BodyOffset returns the byte offset where body starts.

func (*HttpResponse) BodyToString

func (r *HttpResponse) BodyToString() string

BodyToString returns the body as a string. The conversion is memoized: every caller for this response shares one allocation, so a wide passive-module fan-out over the same record doesn't re-copy the body per module.

The miss is resolved under the write lock, not outside it. Computing outside and publishing after is the cheaper shape for an uncontended cache, but this cache is contended by construction: the dynamic-assessment phase defaults to ParallelPassive, which fans every eligible per-request module out over ONE shared response, and 93 of them call this method. Per-host modules are claimed once per origin, so from the second record of a host onward nothing warms the cache first and the whole fan-out arrives cold together — each goroutine copying the same body, for one winner's worth of result. Serializing the miss makes the herd pay for a single copy; the hit path below is unchanged and still takes only the read lock.

func (*HttpResponse) Clone

func (r *HttpResponse) Clone() *HttpResponse

Clone creates a deep copy of the HttpResponse.

func (*HttpResponse) Cookies

func (r *HttpResponse) Cookies() []*Cookie

Cookies parses and returns cookies from Set-Cookie headers. This is NOT cached as it involves parsing.

func (*HttpResponse) Duration

func (r *HttpResponse) Duration() time.Duration

Duration returns the measured round-trip duration for this response, or zero when it was never measured (see the field comment: zero is "unknown", never "instant"). Deliberately NOT carried by the With* builders — those return a synthesized copy, and stamping it with the original's timing would report a measurement that never happened.

func (*HttpResponse) HasHeader

func (r *HttpResponse) HasHeader(name string) bool

HasHeader checks if a header exists (case-insensitive).

func (*HttpResponse) Head

func (r *HttpResponse) Head() []byte

Head returns the response head — the status line and headers up to and including the blank-line separator, without the body. Returns nil when the body offset can't be determined (e.g. an empty or unparsable response).

func (*HttpResponse) Header

func (r *HttpResponse) Header(name string) string

Header returns the value of a specific header (case-insensitive). Returns empty string if not found.

func (*HttpResponse) Headers

func (r *HttpResponse) Headers() []HttpHeader

Headers returns all HTTP headers as a slice. Lazily parsed from raw bytes on first access.

func (*HttpResponse) ID

func (r *HttpResponse) ID() string

ID returns a unique hash identifier for this response.

func (*HttpResponse) RatioSignature

func (r *HttpResponse) RatioSignature(compute func(body string) any) any

RatioSignature returns an opaque, memoized token-similarity signature for this response's body, computed by compute on first access and shared across callers. compute receives the memoized body string. The signature type lives in the modkit package (which imports httpmsg), so httpmsg keeps it opaque to avoid an import cycle. Invalidated by TruncateBody.

Unlike BodyToString this deliberately keeps the compute-outside-the-lock shape, so concurrent cold callers can each run compute and all but one throw the result away. Two reasons it stays that way: compute is caller-supplied, so holding r.mu across it would let a callback that touches this response deadlock the whole record; and the herd BodyToString guards against is a passive-fan-out effect, while this cache exists for an active module reusing one baseline across its own sequential probe loop, where there is no herd to collapse.

func (*HttpResponse) Raw

func (r *HttpResponse) Raw() []byte

Raw returns the raw HTTP response bytes.

func (*HttpResponse) StatusCode

func (r *HttpResponse) StatusCode() int

StatusCode returns the HTTP status code. Lazily parsed from raw bytes on first access.

func (*HttpResponse) TruncateBody

func (r *HttpResponse) TruncateBody(maxSize int)

TruncateBody truncates the response body to maxSize bytes. Headers are preserved. No-op if body is already within limit.

func (*HttpResponse) WithAddedHeader

func (r *HttpResponse) WithAddedHeader(name, value string) *HttpResponse

WithAddedHeader returns a new HttpResponse with a header added.

func (*HttpResponse) WithBody

func (r *HttpResponse) WithBody(body []byte) *HttpResponse

WithBody returns a new HttpResponse with the body replaced. Updates Content-Length header automatically.

func (*HttpResponse) WithHeader

func (r *HttpResponse) WithHeader(name, value string) *HttpResponse

WithHeader returns a new HttpResponse with the header set (add or update).

func (*HttpResponse) WithRemovedHeader

func (r *HttpResponse) WithRemovedHeader(name string) *HttpResponse

WithRemovedHeader returns a new HttpResponse with the header removed.

func (*HttpResponse) WithStatusCode

func (r *HttpResponse) WithStatusCode(code int) *HttpResponse

WithStatusCode returns a new HttpResponse with the status code replaced.

type InsertionPoint

type InsertionPoint interface {
	// Name returns the name of this insertion point.
	// For parameters: returns parameter name (e.g., "id", "username")
	// For custom points: returns user-defined name
	Name() string

	// BaseValue returns the original value at this insertion point.
	// For parameters: returns original parameter value
	// For custom points: returns bytes at original position
	BaseValue() string

	// Type returns the type constant for this insertion point.
	// Returns one of the INS_* constants
	Type() InsertionPointType

	// BuildRequest creates a new HTTP request with the payload injected.
	//
	// Algorithm:
	// 1. Take base request bytes
	// 2. Replace insertion point value with payload
	// 3. Apply appropriate encoding (URL encoding, etc.)
	// 4. Update Content-Length if needed
	// 5. Return modified request
	BuildRequest(payload []byte) []byte

	// PayloadOffsets returns the byte offsets of the payload in the built request.
	//
	// This allows scanners to locate the payload position after request is built.
	// Useful for response analysis to map responses back to specific payloads.
	PayloadOffsets(payload []byte) []int
}

InsertionPoint interface defines how to inject payloads into HTTP requests.

Methods: - Name(): Returns the name/identifier (e.g., parameter name) - BaseValue(): Returns the original value before payload injection - Type(): Returns the insertion point type constant - BuildRequest(): Creates new request with payload injected - PayloadOffsets(): Returns byte offsets of payload in built request

func CreateAllInsertionPoints

func CreateAllInsertionPoints(request []byte, includeNested bool) ([]InsertionPoint, error)

CreateAllInsertionPoints creates insertion points with optional nested discovery.

This is the main factory function for creating insertion points: 1. Extract all parameters from request (URL, body, cookies, headers, etc.) 2. Create standard insertion points for each parameter 3. Optionally detect and create nested insertion points (JSON in params, etc.)

The includeNested parameter controls nested structure scanning:

  • false: Only scan direct parameter values (faster, less thorough)
  • true: Also scan nested structures like JSON/XML/Base64 (slower, more thorough)

Algorithm:

  1. Call AnalyzeRequest() to extract all parameters
  2. Create ParameterInsertionPoint for each parameter
  3. If includeNested == true: a. Call DiscoverNestedInsertionPoints() with all parameters b. Append discovered nested IPs to results
  4. Return complete list of insertion points

Example workflow:

request := []byte("GET /api?data={\"user\":\"admin\"} HTTP/1.1\r\n\r\n")
points, _ := CreateAllInsertionPoints(request, true)
// Returns:
//   1. ParameterInsertionPoint for "data" parameter
//   2. NestedInsertionPoint for "user" within JSON (if includeNested=true)

Parameters:

  • request: Complete HTTP request bytes
  • includeNested: Whether to detect and create nested insertion points

Returns:

  • List of InsertionPoint interfaces (standard + nested if enabled)
  • Error if request analysis fails

func UnwrapChain

func UnwrapChain(ip InsertionPoint) InsertionPoint

UnwrapChain unwraps a nested insertion point chain to find the root parent.

type InsertionPointType

type InsertionPointType byte

InsertionPointType represents where payload injection occurs in an HTTP request.

const (
	INS_PARAM_URL            InsertionPointType = 0   // URL parameter value
	INS_PARAM_BODY           InsertionPointType = 1   // POST body parameter value
	INS_PARAM_COOKIE         InsertionPointType = 2   // Cookie value
	INS_PARAM_XML            InsertionPointType = 3   // XML element value
	INS_PARAM_XML_ATTR       InsertionPointType = 4   // XML attribute value
	INS_PARAM_MULTIPART_ATTR InsertionPointType = 5   // Multipart attribute value
	INS_PARAM_JSON           InsertionPointType = 6   // JSON value
	INS_PARAM_AMF            InsertionPointType = 7   // AMF parameter
	INS_HEADER               InsertionPointType = 32  // HTTP header value
	INS_URL_PATH_FOLDER      InsertionPointType = 33  // REST URL path folder
	INS_PARAM_NAME_URL       InsertionPointType = 34  // URL parameter name
	INS_PARAM_NAME_BODY      InsertionPointType = 35  // Body parameter name
	INS_ENTIRE_BODY          InsertionPointType = 36  // Entire request body
	INS_URL_PATH_FILENAME    InsertionPointType = 37  // REST URL path filename
	INS_USER_PROVIDED        InsertionPointType = 64  // User-defined position
	INS_EXTENSION_PROVIDED   InsertionPointType = 65  // Extension-provided position
	INS_UNKNOWN              InsertionPointType = 127 // Unknown/unclassified
)

func (InsertionPointType) String

func (t InsertionPointType) String() string

String returns a human-readable name for the insertion point type.

type JSONEscapeEncoder

type JSONEscapeEncoder struct{}

JSONEscapeEncoder performs full JSON escaping including all control characters. Handles: " → \", / → \/, \ → \\, plus \n, \r, \t, \b, \f, and \u00xx for non-printable

func (*JSONEscapeEncoder) Decode

func (e *JSONEscapeEncoder) Decode(encoded []byte) []byte

Decode reverses full JSON escaping.

func (*JSONEscapeEncoder) Encode

func (e *JSONEscapeEncoder) Encode(payload []byte, offsets []int) []byte

Encode performs full JSON escaping and tracks offset positions.

type JSONStringEncoder

type JSONStringEncoder struct{}

JSONStringEncoder escapes double quotes for JSON string values. Only handles: " → \"

func (*JSONStringEncoder) Decode

func (e *JSONStringEncoder) Decode(encoded []byte) []byte

Decode reverses quote escaping: \" → "

func (*JSONStringEncoder) Encode

func (e *JSONStringEncoder) Encode(payload []byte, offsets []int) []byte

Encode escapes double quotes and tracks offset positions.

type JSONValueType

type JSONValueType int

JSONValueType represents the original JSON value type for ParamJSON parameters. Used for type-aware payload injection to ensure valid JSON output.

const (
	JSONTypeUnknown JSONValueType = iota
	JSONTypeString
	JSONTypeNumber
	JSONTypeBool
	JSONTypeNull
)

type NestedInsertionPoint

type NestedInsertionPoint struct {
	// contains filtered or unexported fields
}

NestedInsertionPoint implements insertion point for nested parameters with multi-layer encoding.

This handles parameters containing embedded parameters in different formats (e.g., URL parameter containing JSON, Cookie containing URL-encoded data).

The critical feature is the inner-to-outer encoding chain:

  1. Apply encoding to child parameter (innermost) → get encoded1
  2. Apply encoding to parent parameter with encoded1 as input → get encoded2
  3. Return encoded2 (double-encoded result)

func DiscoverNestedInsertionPoints

func DiscoverNestedInsertionPoints(request []byte, params []*Param) []*NestedInsertionPoint

DiscoverNestedInsertionPoints analyzes parameters for nested structures and creates nested insertion points by reusing existing parsers.

Prefer discoverNestedInsertionPointsShared when a *sharedBaseRequest is already in hand; this entry point exists for callers holding only raw bytes and simply wraps them.

func NewNestedInsertionPoint

func NewNestedInsertionPoint(request []byte, parentParam, childParam InsertionPoint) *NestedInsertionPoint

NewNestedInsertionPoint creates a new nested insertion point.

func NewNestedInsertionPointFromIP

func NewNestedInsertionPointFromIP(request []byte, parentNested *NestedInsertionPoint, childParam InsertionPoint) *NestedInsertionPoint

NewNestedInsertionPointFromIP creates a nested insertion point where the parent is another nested IP.

func (*NestedInsertionPoint) BaseValue

func (n *NestedInsertionPoint) BaseValue() string

BaseValue returns the child parameter's original value.

func (*NestedInsertionPoint) BuildRequest

func (n *NestedInsertionPoint) BuildRequest(payload []byte) []byte

BuildRequest creates request with payload injected through nested encoding chain.

func (*NestedInsertionPoint) BuildState

func (n *NestedInsertionPoint) BuildState() int

BuildState returns the current build state.

func (*NestedInsertionPoint) EncodingChain

func (n *NestedInsertionPoint) EncodingChain() string

EncodingChain returns the list of encoding transformations applied.

func (*NestedInsertionPoint) Name

func (n *NestedInsertionPoint) Name() string

Name returns the child parameter name (the innermost parameter being tested).

func (*NestedInsertionPoint) PayloadOffsets

func (n *NestedInsertionPoint) PayloadOffsets(payload []byte) []int

PayloadOffsets returns the byte offsets of the payload in the built request.

func (*NestedInsertionPoint) SetBuildState

func (n *NestedInsertionPoint) SetBuildState(state int)

SetBuildState sets the build state (depth counter for stack traversal).

func (*NestedInsertionPoint) Type

Type returns the child parameter's insertion point type.

type NoopEncoder

type NoopEncoder struct{}

NoopEncoder is a passthrough encoder that returns payloads unchanged.

func (*NoopEncoder) Decode

func (e *NoopEncoder) Decode(encoded []byte) []byte

Decode returns the encoded payload unchanged.

func (*NoopEncoder) Encode

func (e *NoopEncoder) Encode(payload []byte, offsets []int) []byte

Encode returns the payload unchanged.

type Param

type Param struct {
	// contains filtered or unexported fields
}

Param represents an HTTP parameter with its location information in the request. Uses unexported fields with getter methods to ensure immutability after creation.

Design rationale:

  • Unexported fields prevent direct mutation
  • Getter methods provide read-only access
  • Constructor functions control valid creation
  • Position tracking enables precise payload injection

func BuildParameter

func BuildParameter(name, value string, paramType ParamType) *Param

BuildParameter creates a new Param object.

Parameters:

  • name: Parameter name
  • value: Parameter value
  • paramType: Parameter type (ParamURL, ParamBody, ParamCookie)

Returns:

  • Param object ready for use with AddParameter

Example:

param := BuildParameter("sessionid", "abc123", ParamCookie)
request, _ := AddParameter(request, param)

func ExtractBodyParameters

func ExtractBodyParameters(request []byte) (params []*Param, bodyOffset int, err error)

ExtractBodyParameters extracts all body parameters from a complete HTTP request. Automatically detects body offset and parses URL-encoded parameters.

Algorithm:

  1. Find header/body separator (FindBodyOffset)
  2. Validate Content-Type is application/x-www-form-urlencoded
  3. Parse body parameters using parseURLEncodedParameters

Example:

request := []byte("POST /api HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nkey=value")
params, bodyOffset, _ := ExtractBodyParameters(request)
// params contains extracted parameters with ParamBody type
// bodyOffset indicates where body starts

Parameters:

  • request: Complete HTTP request bytes

Returns:

  • params: List of extracted body parameters
  • bodyOffset: Byte offset where body starts
  • error: Any parsing error

func ExtractQueryParameters

func ExtractQueryParameters(request []byte, urlStart, urlEnd int) ([]*Param, error)

ExtractQueryParameters extracts query parameters from a URL within an HTTP request. The HTTP request format is: "GET /path?key=value HTTP/1.1\r\n..."

Algorithm:

  1. Extract URL portion from HTTP request (between method and HTTP version)
  2. Find query string boundaries within the URL
  3. Parse parameters using parseURLEncodedParameters
  4. Adjust offsets to be relative to full request (not just URL)

Example:

request := []byte("GET /path?foo=bar&name=value HTTP/1.1\r\nHost: example.com\r\n\r\n")
params, err := ExtractQueryParameters(request, 0, len(request))
// Returns parameters with offsets relative to the full request

Parameters:

  • request: Full HTTP request bytes
  • urlStart: Start position of URL in request
  • urlEnd: End position of URL in request

Returns:

  • List of Param objects with ParamURL type
  • Error if parsing fails
Example

ExampleExtractQueryParameters demonstrates extracting query params from HTTP request

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte("GET /api/users?status=active&limit=10 HTTP/1.1\r\nHost: example.com\r\n\r\n")

	// URL is between position 4 and the space before HTTP/1.1
	// "GET /api/users?status=active&limit=10 HTTP/1.1"
	//      ^                                 ^
	//      4                                38
	params, err := httpmsg.ExtractQueryParameters(request, 4, 38)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Query parameters found: %d\n", len(params))
	for _, p := range params {
		fmt.Printf("  %s = %s (offsets: %d-%d)\n", p.Name(), p.Value(), p.NameStart(), p.ValueEnd())
	}

}
Output:
Query parameters found: 2
  status = active (offsets: 15-28)
  limit = 10 (offsets: 29-37)

func GetAllParameters

func GetAllParameters(request []byte) ([]*Param, error)

GetAllParameters returns all parameters from a request. Wrapper for AnalyzeRequest.

Parameters:

  • request: HTTP request bytes

Returns:

  • All parameters
  • Error if request malformed

Example:

params, _ := GetAllParameters(request)

func GetParametersByType

func GetParametersByType(request []byte, paramType ParamType) ([]*Param, error)

GetParametersByType returns all parameters of a specific type. Filters parameters by type.

Parameters:

  • request: HTTP request bytes
  • paramType: Parameter type to filter

Returns:

  • Parameters of specified type
  • Error if request malformed

Example:

urlParams, _ := GetParametersByType(request, ParamURL)

func GetRequestParameter

func GetRequestParameter(request []byte, name string) (*Param, error)

GetRequestParameter finds a parameter by name in a request.

Example:

request := []byte("GET /api?id=123&name=test HTTP/1.1\r\n\r\n")
param, _ := GetRequestParameter(request, "id")
// param.Name = "id", param.Value = "123"

param, _ := GetRequestParameter(request, "missing")
// param = nil

Parameters:

  • request: HTTP request bytes (cannot be null)
  • name: Parameter name to find (cannot be null)

Returns:

  • Param object or nil if not found
  • Error if request is malformed or inputs are null

func NewBodyParam

func NewBodyParam(name, value string) *Param

NewBodyParam creates a new URL-encoded body parameter.

func NewCookieParam

func NewCookieParam(name, value string) *Param

NewCookieParam creates a new cookie parameter.

func NewJSONParam

func NewJSONParam(name, value string) *Param

NewJSONParam creates a new JSON parameter.

func NewJSONParsedParam

func NewJSONParsedParam(name, value string, nameStart, nameEnd, valueStart, valueEnd int, metadata string, jsonType JSONValueType) *Param

NewJSONParsedParam creates a JSON parameter with type information. Used by the JSON parser to preserve original value type for correct encoding.

func NewParam

func NewParam(ptype ParamType, name, value string) *Param

NewParam creates a new Param with basic fields. Use this for simple parameter creation without byte offsets.

func NewParsedParam

func NewParsedParam(ptype ParamType, name, value string, nameStart, nameEnd, valueStart, valueEnd int) *Param

NewParsedParam creates a Param with all fields including byte offsets. This is the internal constructor used by parsers that track positions.

func NewParsedParamWithMetadata

func NewParsedParamWithMetadata(ptype ParamType, name, value string, nameStart, nameEnd, valueStart, valueEnd int, metadata string) *Param

NewParsedParamWithMetadata creates a Param with all fields including metadata. Used by parsers that need to store additional context (e.g., JSON path).

func NewPathFilenameParam

func NewPathFilenameParam(name, value string) *Param

NewPathFilenameParam creates a new REST path filename parameter.

func NewPathFolderParam

func NewPathFolderParam(name, value string) *Param

NewPathFolderParam creates a new REST path folder parameter.

func NewURLParam

func NewURLParam(name, value string) *Param

NewURLParam creates a new URL query parameter.

func NewXMLParam

func NewXMLParam(name, value string) *Param

NewXMLParam creates a new XML element parameter.

func ParseJSONBody

func ParseJSONBody(request []byte, bodyOffset int) ([]*Param, error)

ParseJSONBody parses application/json body and extracts parameters.

Algorithm:

  1. Create parser instance with data range
  2. Call parseValue to start parsing from root
  3. Return collected parameters

Parameters:

  • request: Full HTTP request bytes OR just JSON bytes
  • bodyOffset: Starting position of JSON in request (0 if request IS the JSON)

Returns:

  • List of parameters with JSON paths and byte offsets
  • Error if JSON structure is invalid

func ParseMultipartBody

func ParseMultipartBody(request []byte, bodyOffset int, boundary string) ([]*Param, error)

ParseMultipartBody parses multipart/form-data body and extracts parameters. This is the main entry point for multipart parsing.

Algorithm:

  1. Extract boundary from Content-Type header
  2. Construct boundary markers: "--boundary" for parts, "--boundary--" for end
  3. Find all boundary positions using loop-based search
  4. For each part between boundaries: - Search for "name=\"" and extract name attribute - Search for "filename=\"" and extract filename if present - Find end of headers (double newline sequence) - Extract value content between headers and next boundary - Create Parameter with type BODY_PARAM_MULTIPART
  5. Track all byte offsets relative to request start

Example:

request := []byte("POST / HTTP/1.1\r\n" +
    "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary\r\n\r\n" +
    "------WebKitFormBoundary\r\n" +
    "Content-Disposition: form-data; name=\"field\"\r\n\r\n" +
    "value\r\n" +
    "------WebKitFormBoundary--")

bodyOffset := FindBodyOffset(request)
params, err := ParseMultipartBody(request, bodyOffset, "----WebKitFormBoundary")
// Returns: []*Param with ParamBodyMultipart type

Parameters:

  • request: Full HTTP request bytes
  • bodyOffset: Position where body starts (after headers)
  • boundary: Boundary string from Content-Type header

Returns:

  • List of Param objects with ParamBodyMultipart type
  • Error if boundary is empty or parsing fails
Example

ExampleParseMultipartBody demonstrates parsing a simple multipart form.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	boundary := "----WebKitFormBoundary"
	request := []byte(
		"POST /upload HTTP/1.1\r\n" +
			"Content-Type: multipart/form-data; boundary=" + boundary + "\r\n\r\n" +
			"------WebKitFormBoundary\r\n" +
			"Content-Disposition: form-data; name=\"username\"\r\n\r\n" +
			"john_doe\r\n" +
			"------WebKitFormBoundary--")

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseMultipartBody(request, bodyOffset, boundary)

	for _, param := range params {
		fmt.Printf("Name: %s, Value: %s\n", param.Name(), param.Value())
	}
}
Output:
Name: username, Value: john_doe
Example (ComplexForm)

ExampleParseMultipartBody_complexForm demonstrates a realistic form with mixed content.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	boundary := "----WebKitFormBoundaryABC123"
	request := []byte(
		"POST /api/profile HTTP/1.1\r\n" +
			"Content-Type: multipart/form-data; boundary=" + boundary + "\r\n\r\n" +
			"------WebKitFormBoundaryABC123\r\n" +
			"Content-Disposition: form-data; name=\"userId\"\r\n\r\n" +
			"42\r\n" +
			"------WebKitFormBoundaryABC123\r\n" +
			"Content-Disposition: form-data; name=\"bio\"\r\n\r\n" +
			"Software developer\r\n" +
			"------WebKitFormBoundaryABC123\r\n" +
			"Content-Disposition: form-data; name=\"avatar\"; filename=\"me.png\"\r\n" +
			"Content-Type: image/png\r\n\r\n" +
			"PNG_DATA\r\n" +
			"------WebKitFormBoundaryABC123--")

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseMultipartBody(request, bodyOffset, boundary)

	textFields := 0
	fileFields := 0

	for _, param := range params {
		if param.Type() == httpmsg.ParamBodyMultipart {
			if param.Metadata() == "" {
				textFields++
			} else {
				fileFields++
			}
		}
	}

	fmt.Printf("Text fields: %d\n", textFields)
	fmt.Printf("File fields: %d\n", fileFields)
}
Output:
Text fields: 2
File fields: 1
Example (EmptyValue)

ExampleParseMultipartBody_emptyValue demonstrates handling empty values.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	boundary := "----WebKitFormBoundary"
	request := []byte(
		"POST /submit HTTP/1.1\r\n" +
			"Content-Type: multipart/form-data; boundary=" + boundary + "\r\n\r\n" +
			"------WebKitFormBoundary\r\n" +
			"Content-Disposition: form-data; name=\"empty\"\r\n\r\n" +
			"\r\n" +
			"------WebKitFormBoundary--")

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseMultipartBody(request, bodyOffset, boundary)

	for _, param := range params {
		fmt.Printf("Name: %s, Empty: %t\n", param.Name(), param.Value() == "")
	}
}
Output:
Name: empty, Empty: true
Example (FileUpload)

ExampleParseMultipartBody_fileUpload demonstrates parsing a file upload.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	boundary := "----WebKitFormBoundary"
	request := []byte(
		"POST /upload HTTP/1.1\r\n" +
			"Content-Type: multipart/form-data; boundary=" + boundary + "\r\n\r\n" +
			"------WebKitFormBoundary\r\n" +
			"Content-Disposition: form-data; name=\"avatar\"; filename=\"photo.jpg\"\r\n" +
			"Content-Type: image/jpeg\r\n\r\n" +
			"binary image data here\r\n" +
			"------WebKitFormBoundary--")

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseMultipartBody(request, bodyOffset, boundary)

	for _, param := range params {
		switch param.Type() {
		case httpmsg.ParamBodyMultipart:
			fmt.Printf("Field: %s\n", param.Name())
		case httpmsg.ParamMultipartAttr:
			fmt.Printf("Attribute: %s = %s\n", param.Name(), param.Value())
		}
	}
}
Output:
Field: avatar
Attribute: filename = photo.jpg
Example (MultipleFields)

ExampleParseMultipartBody_multipleFields demonstrates parsing multiple fields.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	boundary := "----WebKitFormBoundary"
	request := []byte(
		"POST /submit HTTP/1.1\r\n" +
			"Content-Type: multipart/form-data; boundary=" + boundary + "\r\n\r\n" +
			"------WebKitFormBoundary\r\n" +
			"Content-Disposition: form-data; name=\"email\"\r\n\r\n" +
			"user@example.com\r\n" +
			"------WebKitFormBoundary\r\n" +
			"Content-Disposition: form-data; name=\"message\"\r\n\r\n" +
			"Hello World\r\n" +
			"------WebKitFormBoundary--")

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseMultipartBody(request, bodyOffset, boundary)

	for _, param := range params {
		fmt.Printf("%s: %s\n", param.Name(), param.Value())
	}
}
Output:
email: user@example.com
message: Hello World
Example (Offsets)

ExampleParseMultipartBody_offsets demonstrates byte offset tracking.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	boundary := "----WebKit"
	request := []byte(
		"POST / HTTP/1.1\r\n" +
			"Content-Type: multipart/form-data; boundary=" + boundary + "\r\n\r\n" +
			"------WebKit\r\n" +
			"Content-Disposition: form-data; name=\"test\"\r\n\r\n" +
			"data\r\n" +
			"------WebKit--")

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseMultipartBody(request, bodyOffset, boundary)

	if len(params) > 0 {
		param := params[0]
		// Verify offsets by extracting from original request
		extractedName := string(request[param.NameStart():param.NameEnd()])
		extractedValue := string(request[param.ValueStart():param.ValueEnd()])

		fmt.Printf("Name: %s (extracted: %s)\n", param.Name(), extractedName)
		fmt.Printf("Value: %s (extracted: %s)\n", param.Value(), extractedValue)
		fmt.Printf("Offsets valid: %t\n", extractedName == param.Name() && extractedValue == param.Value())
	}
}
Output:
Name: test (extracted: test)
Value: data (extracted: data)
Offsets valid: true
Example (WithMetadata)

ExampleParseMultipartBody_withMetadata demonstrates accessing metadata.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	boundary := "----WebKitFormBoundary"
	request := []byte(
		"POST /upload HTTP/1.1\r\n" +
			"Content-Type: multipart/form-data; boundary=" + boundary + "\r\n\r\n" +
			"------WebKitFormBoundary\r\n" +
			"Content-Disposition: form-data; name=\"document\"; filename=\"doc.pdf\"\r\n" +
			"Content-Type: application/pdf\r\n\r\n" +
			"PDF content\r\n" +
			"------WebKitFormBoundary--")

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseMultipartBody(request, bodyOffset, boundary)

	for _, param := range params {
		if param.Type() == httpmsg.ParamBodyMultipart && param.Metadata() != "" {
			fmt.Printf("Field: %s\n", param.Name())
			fmt.Printf("Has metadata: yes\n")
		}
	}
}
Output:
Field: document
Has metadata: yes

func ParseMultipartRequest

func ParseMultipartRequest(request []byte) ([]*Param, error)

ParseMultipartRequest is a convenience function that extracts the boundary from headers and parses the multipart body in one call.

This combines boundary extraction and multipart parsing.

Algorithm:

  1. Find Content-Type header in request
  2. Extract boundary from Content-Type
  3. Find body offset
  4. Call ParseMultipartBody with extracted boundary

Example:

request := []byte("POST / HTTP/1.1\r\n" +
    "Content-Type: multipart/form-data; boundary=----WebKit\r\n\r\n" +
    "------WebKit\r\n" +
    "Content-Disposition: form-data; name=\"field\"\r\n\r\n" +
    "value\r\n" +
    "------WebKit--")

params, err := ParseMultipartRequest(request)
// Automatically extracts boundary and parses body

Parameters:

  • request: Full HTTP request bytes

Returns:

  • List of Param objects with ParamBodyMultipart type
  • Error if Content-Type not found or parsing fails
Example

ExampleParseMultipartRequest demonstrates automatic boundary extraction.

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte(
		"POST /api/data HTTP/1.1\r\n" +
			"Host: example.com\r\n" +
			"Content-Type: multipart/form-data; boundary=----WebKitFormBoundary\r\n\r\n" +
			"------WebKitFormBoundary\r\n" +
			"Content-Disposition: form-data; name=\"field\"\r\n\r\n" +
			"value\r\n" +
			"------WebKitFormBoundary--")

	params, err := httpmsg.ParseMultipartRequest(request)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	for _, param := range params {
		fmt.Printf("%s=%s\n", param.Name(), param.Value())
	}
}
Output:
field=value

func ParsePathParameters

func ParsePathParameters(request []byte) ([]*Param, error)

ParsePathParameters extracts REST-style path parameters from an HTTP request. This function parses the URL path (not query string) and creates parameters for each path segment separated by '/'.

Algorithm:

  1. Skip HTTP method
  2. Skip spaces after method
  3. Loop through URL path: a. Check for '/' separator b. Find segment end - stop at: - Whitespace (<=32) - Another '/' (path separator) - '?' (query string start) - ';', '&', '=' (URL special chars) c. Classify segment: - If followed by '/' → PATH_FOLDER_PARAM - If NOT followed by '/' → PATH_FILENAME_PARAM d. Create parameter with sequential integer name
  4. Return list of path parameters

Classification logic:

  • PATH_FOLDER: segment followed by another '/' (e.g., /api/ in /api/users)
  • PATH_FILENAME: last segment or segment before query/fragment (e.g., profile in /api/profile)

Example:

request := []byte("GET /api/users/123/profile HTTP/1.1\r\nHost: example.com\r\n\r\n")
params, err := ParsePathParameters(request)
// Returns:
//   [0] = {Type: ParamPathFolder, Name: "1", Value: "api", ValueStart: 5, ValueEnd: 8}
//   [1] = {Type: ParamPathFolder, Name: "2", Value: "users", ValueStart: 9, ValueEnd: 14}
//   [2] = {Type: ParamPathFolder, Name: "3", Value: "123", ValueStart: 15, ValueEnd: 18}
//   [3] = {Type: ParamPathFilename, Name: "4", Value: "profile", ValueStart: 19, ValueEnd: 26}

Parameters:

  • request: Complete HTTP request bytes including headers and body

Returns:

  • List of Param objects with ParamPathFolder or ParamPathFilename types
  • Error if parsing fails (currently never returns error for compatibility)

func ParseQueryParameters

func ParseQueryParameters(queryString []byte) []*Param

ParseQueryParameters parses URL-encoded parameters from a query string portion. Takes JUST the query parameters WITHOUT '?' (e.g., "id=123&name=test"). For full URLs with '?', use ParseQueryString instead.

This is the low-level parameter parsing API where the caller has already identified the query portion and passes it directly for parsing.

Algorithm:

  1. Validate input
  2. Call parseURLEncodedParameters directly on entire input
  3. No boundary detection (no '?' search) - input is already the query portion

Parameters:

  • queryString: Query parameters WITHOUT '?' prefix

Returns:

  • List of Param objects with ParamURL type

Example:

queryString := []byte("id=123&name=test")  // No '?' prefix
params := ParseQueryParameters(queryString)
// Returns: [{Name: "id", Value: "123"}, {Name: "name", Value: "test"}]

func ParseQueryString

func ParseQueryString(url []byte) ([]*Param, error)

ParseQueryString extracts query string parameters from a URL byte array. This function parses URL query parameters in the format: ?key1=value1&key2=value2

Algorithm:

  1. Find query string boundaries (after '?' and before '#' or end)
  2. Loop through query string character by character
  3. Parse each parameter as name=value pair separated by '&'
  4. Handle edge cases: empty values, no values, trailing separators
  5. Track byte offsets for name and value positions

Example:

url := []byte("http://example.com/path?foo=bar&name=John%20Doe&flag")
params, err := ParseQueryString(url)
// Returns:
//   [0] = {Type: ParamURL, Name: "foo", Value: "bar", NameStart: 28, NameEnd: 31, ValueStart: 32, ValueEnd: 35}
//   [1] = {Type: ParamURL, Name: "name", Value: "John%20Doe", NameStart: 36, NameEnd: 40, ValueStart: 41, ValueEnd: 51}
//   [2] = {Type: ParamURL, Name: "flag", Value: "", NameStart: 52, NameEnd: 56, ValueStart: 56, ValueEnd: 56}

Parameters:

  • url: Complete URL as byte array

Returns:

  • List of Param objects with ParamURL type
  • Error if parsing fails (currently never returns error for compatibility)
Example

ExampleParseQueryString demonstrates parsing query parameters from a URL

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com/api?name=John%20Doe&age=30&active")

	params, err := httpmsg.ParseQueryString(url)
	if err != nil {
		panic(err)
	}

	for _, p := range params {
		fmt.Printf("Name: %s, Value: %s\n", p.Name(), p.Value())
	}

}
Output:
Name: name, Value: John Doe
Name: age, Value: 30
Name: active, Value:
Example (Complex)

ExampleParseQueryString_complex demonstrates complex real-world scenario

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	// Real-world example: search query with filters
	url := []byte("http://api.example.com/v1/search?q=golang+tutorial&category=programming&sort=date&page=1&limit=20")

	params, err := httpmsg.ParseQueryString(url)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Search API Parameters:\n")
	fmt.Printf("----------------------\n")
	for _, p := range params {
		// Decode value for display
		decodedValue := httpmsg.DecodeQueryValue(p.Value())
		fmt.Printf("%-10s: %s\n", p.Name(), decodedValue)
	}

}
Output:
Search API Parameters:
----------------------
q         : golang tutorial
category  : programming
sort      : date
page      : 1
limit     : 20
Example (EmptyAndMissing)

ExampleParseQueryString_emptyAndMissing demonstrates handling empty and missing values

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com?key1=value1&key2=&key3&key4=")

	params, err := httpmsg.ParseQueryString(url)
	if err != nil {
		panic(err)
	}

	for _, p := range params {
		if p.Value() == "" {
			fmt.Printf("%s: (empty)\n", p.Name())
		} else {
			fmt.Printf("%s: %s\n", p.Name(), p.Value())
		}
	}

}
Output:
key1: value1
key2: (empty)
key3: (empty)
key4: (empty)
Example (MultipleParameters)

ExampleParseQueryString_multipleParameters demonstrates parsing multiple parameters

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com/search?q=golang&page=1&sort=desc")

	params, err := httpmsg.ParseQueryString(url)
	if err != nil {
		panic(err)
	}

	fmt.Printf("Found %d parameters:\n", len(params))
	for i, p := range params {
		fmt.Printf("%d: %s=%s\n", i+1, p.Name(), p.Value())
	}

}
Output:
Found 3 parameters:
1: q=golang
2: page=1
3: sort=desc
Example (Offsets)

ExampleParseQueryString_offsets demonstrates accessing parameter offsets

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com?foo=bar&name=value")

	params, err := httpmsg.ParseQueryString(url)
	if err != nil {
		panic(err)
	}

	// Show how to extract parameter segments using offsets
	for i, p := range params {
		nameBytes := url[p.NameStart():p.NameEnd()]
		valueBytes := url[p.ValueStart():p.ValueEnd()]

		fmt.Printf("Param %d:\n", i+1)
		fmt.Printf("  Name:  %s (bytes %d-%d)\n", nameBytes, p.NameStart(), p.NameEnd())
		fmt.Printf("  Value: %s (bytes %d-%d)\n", valueBytes, p.ValueStart(), p.ValueEnd())
	}

}
Output:
Param 1:
  Name:  foo (bytes 19-22)
  Value: bar (bytes 23-26)
Param 2:
  Name:  name (bytes 27-31)
  Value: value (bytes 32-37)
Example (WithFragment)

ExampleParseQueryString_withFragment demonstrates parsing with URL fragment

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com/page?tab=overview&section=intro#details")

	params, err := httpmsg.ParseQueryString(url)
	if err != nil {
		panic(err)
	}

	for _, p := range params {
		fmt.Printf("%s: %s\n", p.Name(), p.Value())
	}

}
Output:
tab: overview
section: intro

func ParseURLEncodedBody

func ParseURLEncodedBody(request []byte, bodyOffset int) ([]*Param, error)

ParseURLEncodedBody parses application/x-www-form-urlencoded body parameters. This is the primary function for extracting parameters from POST body data.

Algorithm:

  1. Initialize position markers: pos, nameStart, nameEnd, valueStart, valueEnd
  2. Loop through body bytes character by character
  3. Skip leading CRLF/LF at parameter start
  4. Find '=' character to separate name from value
  5. Find '&' character to separate parameters
  6. Handle edge cases: whitespace/control characters
  7. Parse value portion after '='
  8. Create Parameter object with type ParamBody
  9. Move to next parameter

Example:

request := []byte("POST / HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nname=John&age=30")
bodyOffset := FindBodyOffset(request)
params, err := ParseURLEncodedBody(request, bodyOffset)
// Returns:
//   [0] = {Type: ParamBody, Name: "name", Value: "John", NameStart: 74, NameEnd: 78, ValueStart: 79, ValueEnd: 83}
//   [1] = {Type: ParamBody, Name: "age", Value: "30", NameStart: 84, NameEnd: 87, ValueStart: 88, ValueEnd: 90}

Parameters:

  • request: Complete HTTP request bytes including headers and body
  • bodyOffset: Byte offset where body starts (from FindBodyOffset)

Returns:

  • List of Parameter objects with ParamBody type
  • Error if parsing fails (currently never returns error for compatibility)

func ParseURLEncodedBodyString

func ParseURLEncodedBodyString(body string) ([]*Param, error)

ParseURLEncodedBodyString parses URL-encoded body from a string. Convenience function for testing and simple use cases.

Parameters:

  • body: URL-encoded body string (e.g., "name=value&foo=bar")

Returns:

  • List of Parameter objects with ParamBody type
  • Error if parsing fails

Example:

body := "username=admin&password=secret123&remember=on"
params, _ := ParseURLEncodedBodyString(body)
// Returns 3 parameters with ParamBody type

func ParseXMLBody

func ParseXMLBody(request []byte, bodyOffset int) ([]*Param, error)

ParseXMLBody parses XML body and extracts parameters.

Algorithm:

  1. Parse XML structure using iterative parser
  2. Walk through XML elements maintaining current tag context
  3. For each element: a. If OPEN/SELF_CLOSE: extract all attributes as ParamXMLAttr b. If TEXT: extract text content as ParamXML (using current tag path) c. If CLOSE after OPEN with no content: create empty ParamXML
  4. Build hierarchical parameter names (e.g., "root.user.name")
  5. Track byte offsets for all parameters

Example:

request := []byte(`POST / HTTP/1.1
Content-Type: application/xml

<root>
  <user id="123">
    <name>John</name>
  </user>
</root>`)

bodyOffset := FindBodyOffset(request)
params, err := ParseXMLBody(request, bodyOffset)
// Returns:
//   [0] = {Type: ParamXMLAttr, Name: "root.user@id", Value: "123", ...}
//   [1] = {Type: ParamXML, Name: "root.user.name", Value: "John", ...}

Parameters:

  • request: Full HTTP request bytes
  • bodyOffset: Byte offset where HTTP body starts

Returns:

  • List of Param objects (ParamXML and ParamXMLAttr types)
  • Error if XML parsing fails (returns empty list, no error for compatibility)
Example
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte(`POST /api HTTP/1.1
Host: example.com
Content-Type: application/xml

<user>
  <name>John</name>
  <age>30</age>
</user>`)

	// Find body offset
	bodyOffset := httpmsg.FindBodyOffset(request)

	// Parse XML parameters
	params, err := httpmsg.ParseXMLBody(request, bodyOffset)
	if err != nil {
		panic(err)
	}

	// Print extracted parameters
	for _, p := range params {
		fmt.Printf("%s: %s = %s\n",
			p.Type(),
			p.Name(),
			p.Value())
	}

}
Output:
XML_PARAM: name = John
XML_PARAM: age = 30
Example (EmptyAndWhitespace)
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte(`POST / HTTP/1.1
Content-Type: application/xml

<root>
  <empty></empty>
  <whitespace>   </whitespace>
  <content>data</content>
</root>`)

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseXMLBody(request, bodyOffset)

	// Only non-empty text content is extracted
	for _, p := range params {
		if p.Type() == httpmsg.ParamXML {
			fmt.Printf("%s = '%s'\n", p.Name(), p.Value())
		}
	}

}
Output:
content = 'data'
Example (Nested)
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte(`POST / HTTP/1.1
Content-Type: application/xml

<company>
  <department id="IT">
    <employee>
      <name>John Doe</name>
      <position>Developer</position>
    </employee>
  </department>
</company>`)

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseXMLBody(request, bodyOffset)

	// Print all parameters
	for _, p := range params {
		fmt.Printf("%s: %s = %s\n",
			p.Type(),
			p.Name(),
			p.Value())
	}

}
Output:
XML_ATTR: department@id = it
XML_PARAM: name = John Doe
XML_PARAM: position = Developer
Example (Offsets)
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte(`POST / HTTP/1.1
Content-Type: application/xml

<user id="123">
  <name>John</name>
</user>`)

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseXMLBody(request, bodyOffset)

	// Show parameter offsets
	for _, p := range params {
		fmt.Printf("%s '%s':\n", p.Type(), p.Name())
		fmt.Printf("  Value: '%s'\n", p.Value())
		fmt.Printf("  ValueStart: %d, ValueEnd: %d\n", p.ValueStart(), p.ValueEnd())

		// Extract actual value from request using offsets
		actualValue := string(request[p.ValueStart():p.ValueEnd()])
		fmt.Printf("  Extracted: '%s'\n", actualValue)
	}

}
Output:
XML_ATTR 'user@id':
  Value: '123'
  ValueStart: 57, ValueEnd: 60
  Extracted: '123'
XML_PARAM 'name':
  Value: 'John'
  ValueStart: 71, ValueEnd: 75
  Extracted: 'John'
Example (RealWorldAPI)
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	// Real-world API request example
	request := []byte(`POST /api/v1/users HTTP/1.1
Host: api.example.com
Content-Type: application/xml
Authorization: Bearer token123

<?xml version="1.0" encoding="UTF-8"?>
<UserRequest xmlns="http://example.com/api/v1" version="1.0">
  <Authentication token="abc123" timestamp="2024-01-01T00:00:00Z"/>
  <UserData>
    <Username>admin</Username>
    <Email>admin@example.com</Email>
    <Preferences theme="dark" notifications="enabled"/>
  </UserData>
</UserRequest>`)

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseXMLBody(request, bodyOffset)

	fmt.Printf("Total parameters extracted: %d\n\n", len(params))

	// Group by type
	attrCount := 0
	elemCount := 0
	for _, p := range params {
		switch p.Type() {
		case httpmsg.ParamXMLAttr:
			attrCount++
		case httpmsg.ParamXML:
			elemCount++
		}
	}

	fmt.Printf("Attributes: %d\n", attrCount)
	fmt.Printf("Elements: %d\n", elemCount)

}
Output:
Total parameters extracted: 8

Attributes: 6
Elements: 2
Example (SelfClosing)
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte(`POST / HTTP/1.1
Content-Type: application/xml

<config>
  <option name="debug" value="true"/>
  <option name="verbose" value="false"/>
</config>`)

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseXMLBody(request, bodyOffset)

	// Self-closing tags only have attributes
	for _, p := range params {
		if p.Type() == httpmsg.ParamXMLAttr {
			fmt.Printf("%s = %s\n", p.Name(), p.Value())
		}
	}

}
Output:
option@name = debug
option@value = true
option@name = verbose
option@value = false
Example (Soap)
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	// Example SOAP request
	request := []byte(`POST /soap/endpoint HTTP/1.1
Host: api.example.com
Content-Type: application/xml

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUser>
      <UserId>12345</UserId>
      <IncludeDetails>true</IncludeDetails>
    </GetUser>
  </soap:Body>
</soap:Envelope>`)

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseXMLBody(request, bodyOffset)

	// Print all text elements
	for _, p := range params {
		if p.Type() == httpmsg.ParamXML {
			fmt.Printf("%s = %s\n", p.Name(), p.Value())
		}
	}

}
Output:
userid = 12345
includedetails = true
Example (WithAttributes)
package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	request := []byte(`POST /api HTTP/1.1
Host: example.com
Content-Type: application/xml

<user id="123" role="admin">
  <name>John</name>
</user>`)

	bodyOffset := httpmsg.FindBodyOffset(request)
	params, _ := httpmsg.ParseXMLBody(request, bodyOffset)

	// Print attributes and elements separately
	fmt.Println("Attributes:")
	for _, p := range params {
		if p.Type() == httpmsg.ParamXMLAttr {
			fmt.Printf("  %s = %s\n", p.Name(), p.Value())
		}
	}

	fmt.Println("Elements:")
	for _, p := range params {
		if p.Type() == httpmsg.ParamXML {
			fmt.Printf("  %s = %s\n", p.Name(), p.Value())
		}
	}

}
Output:
Attributes:
  user@id = 123
  user@role = admin
Elements:
  name = John

func (*Param) CanURLEncode

func (p *Param) CanURLEncode() bool

CanURLEncode returns whether this parameter type supports URL encoding.

func (*Param) HasNameOffsets

func (p *Param) HasNameOffsets() bool

HasNameOffsets returns whether this parameter has valid name offsets.

func (*Param) HasOffsets

func (p *Param) HasOffsets() bool

HasOffsets returns whether this parameter has valid byte offsets. Some parameters (like path segments) may not have name offsets.

func (*Param) IsPathParameter

func (p *Param) IsPathParameter() bool

IsPathParameter returns whether this parameter is a REST-style path parameter.

func (*Param) JSONType

func (p *Param) JSONType() JSONValueType

JSONType returns the original JSON value type for JSON parameters.

func (*Param) Metadata

func (p *Param) Metadata() string

Metadata returns additional metadata (e.g., JSON path for JSON params).

func (*Param) Name

func (p *Param) Name() string

Name returns the parameter name.

func (*Param) NameEnd

func (p *Param) NameEnd() int

NameEnd returns the byte offset where the name ends in the request.

func (*Param) NameStart

func (p *Param) NameStart() int

NameStart returns the byte offset where the name starts in the request.

func (*Param) RequiresContentLengthUpdate

func (p *Param) RequiresContentLengthUpdate() bool

RequiresContentLengthUpdate returns whether modifying this parameter requires updating the Content-Length header.

func (*Param) ToInsertionPointType

func (p *Param) ToInsertionPointType() InsertionPointType

ToInsertionPointType returns the corresponding InsertionPointType for scanning.

func (*Param) Type

func (p *Param) Type() ParamType

Type returns the parameter type.

func (*Param) Value

func (p *Param) Value() string

Value returns the parameter value.

func (*Param) ValueEnd

func (p *Param) ValueEnd() int

ValueEnd returns the byte offset where the value ends in the request.

func (*Param) ValueStart

func (p *Param) ValueStart() int

ValueStart returns the byte offset where the value starts in the request.

func (*Param) WithAdjustedOffsets

func (p *Param) WithAdjustedOffsets(delta int) *Param

WithAdjustedOffsets returns a new Param with all offsets adjusted by delta. Useful when the parameter is part of a larger request and needs repositioning.

func (*Param) WithJSONType

func (p *Param) WithJSONType(jsonType JSONValueType) *Param

WithJSONType returns a new Param with the JSON type set. The original Param is not modified.

func (*Param) WithMetadata

func (p *Param) WithMetadata(metadata string) *Param

WithMetadata returns a new Param with the metadata changed.

func (*Param) WithOffsets

func (p *Param) WithOffsets(nameStart, nameEnd, valueStart, valueEnd int) *Param

WithOffsets returns a new Param with adjusted offsets. Useful when embedding a parameter in a larger context.

func (*Param) WithValue

func (p *Param) WithValue(value string) *Param

WithValue returns a new Param with the value changed. The original Param is not modified.

type ParamType

type ParamType uint8

ParamType represents the source location of an HTTP parameter during parsing. This is used for parameter EXTRACTION (where the parameter came from). For payload INJECTION points, use InsertionPointType instead.

Key difference:

  • ParamType: describes WHERE a parameter was parsed FROM (URL query, body, cookie, etc.)
  • InsertionPointType: describes HOW to inject a payload (encoding, position, etc.)
const (
	ParamURL           ParamType = 0   // URL query parameter value
	ParamBody          ParamType = 1   // URL-encoded body parameter
	ParamCookie        ParamType = 2   // Cookie value
	ParamXML           ParamType = 3   // XML element content
	ParamXMLAttr       ParamType = 4   // XML attribute value
	ParamMultipartAttr ParamType = 5   // Multipart form field attribute (e.g., filename)
	ParamJSON          ParamType = 6   // JSON value
	ParamPathFolder    ParamType = 7   // REST path folder segment
	ParamBodyMultipart ParamType = 8   // Multipart form field value
	ParamPathFilename  ParamType = 9   // REST path filename segment
	ParamNone          ParamType = 255 // No parameter / placeholder
)

func InsertionPointTypeToParamType

func InsertionPointTypeToParamType(ipt InsertionPointType) ParamType

InsertionPointTypeToParamType converts InsertionPointType back to ParamType. Note: This is a lossy conversion since multiple ParamTypes can map to the same InsertionPointType.

func (ParamType) CanURLEncode

func (pt ParamType) CanURLEncode() bool

CanURLEncode returns whether this parameter type supports URL encoding.

func (ParamType) IsPathParam

func (pt ParamType) IsPathParam() bool

IsPathParam returns whether this parameter is a REST-style path parameter.

func (ParamType) RequiresContentLengthUpdate

func (pt ParamType) RequiresContentLengthUpdate() bool

RequiresContentLengthUpdate returns whether modifying this parameter requires updating the Content-Length header.

func (ParamType) String

func (pt ParamType) String() string

String returns a human-readable name for the parameter type.

func (ParamType) ToInsertionPointType

func (pt ParamType) ToInsertionPointType() InsertionPointType

ToInsertionPointType converts ParamType to the corresponding InsertionPointType. This centralized mapping function handles the translation between the two type systems.

type ParameterDescriptor

type ParameterDescriptor struct {
	// contains filtered or unexported fields
}

ParameterDescriptor represents a parameter descriptor with name and type information. Used for parameter filtering in scanner configuration.

func NewParameterDescriptor

func NewParameterDescriptor(ipType InsertionPointType, name string) *ParameterDescriptor

NewParameterDescriptor creates a new parameter descriptor.

Parameters:

  • ipType: Insertion point type
  • name: Parameter name

Returns:

  • New ParameterDescriptor instance

func (*ParameterDescriptor) Name

func (pd *ParameterDescriptor) Name() string

Name returns the parameter name.

func (*ParameterDescriptor) Type

Type returns the insertion point type.

type ParameterFormat

type ParameterFormat int

ParameterFormat represents the detected content format of a parameter value.

const (
	// FormatNone indicates no structured format detected
	FormatNone ParameterFormat = iota
	// FormatJSON indicates JSON object or array format
	FormatJSON
	// FormatXML indicates XML document format
	FormatXML
	// FormatURLEncoded indicates URL-encoded key-value pairs
	FormatURLEncoded
	// FormatBase64 indicates Base64-encoded data
	FormatBase64
)

func DetectParameterFormat

func DetectParameterFormat(value string) ParameterFormat

DetectParameterFormat analyzes a parameter value to detect if it contains a nested structure (JSON, XML, Base64, or URL-encoded data).

Uses structural markers to identify content types:

  • JSON: starts with { or [, ends with } or ]
  • XML: starts with <, ends with >
  • Base64: matches Base64 alphabet with valid padding
  • URL-encoded: contains %XX sequences or name=value patterns

The detection is conservative to avoid false positives.

func (ParameterFormat) String

func (f ParameterFormat) String() string

String returns the string representation of the format

type ParameterInsertionPoint

type ParameterInsertionPoint struct {
	// contains filtered or unexported fields
}

ParameterInsertionPoint implements parameter-aware insertion point with encoding.

This insertion point is aware of parameter types and applies appropriate encoding: - URL parameters: URL-encode special characters - Body parameters: URL-encode for application/x-www-form-urlencoded - JSON parameters: No encoding (raw injection) - XML parameters: No encoding (raw injection) - Cookie parameters: URL-encode

func NewParameterInsertionPoint

func NewParameterInsertionPoint(request []byte, param *Param) *ParameterInsertionPoint

NewParameterInsertionPoint creates a new parameter-aware insertion point.

func (*ParameterInsertionPoint) BaseValue

func (p *ParameterInsertionPoint) BaseValue() string

BaseValue returns the parameter's original value.

func (*ParameterInsertionPoint) BuildRequest

func (p *ParameterInsertionPoint) BuildRequest(payload []byte) []byte

BuildRequest creates a new request with payload injected and properly encoded.

func (*ParameterInsertionPoint) BuildRequestPooled

func (p *ParameterInsertionPoint) BuildRequestPooled(payload []byte) []byte

BuildRequestPooled creates a new request with payload injected, using a pooled buffer. The caller MUST call PutRequestBuffer(result) when the returned slice is no longer needed. This avoids per-call allocations in tight fuzzing loops.

func (*ParameterInsertionPoint) Name

func (p *ParameterInsertionPoint) Name() string

Name returns the parameter name.

func (*ParameterInsertionPoint) PayloadOffsets

func (p *ParameterInsertionPoint) PayloadOffsets(payload []byte) []int

PayloadOffsets returns the byte offsets of the payload in the built request.

func (*ParameterInsertionPoint) Type

Type returns the insertion point type.

type ParsedURL

type ParsedURL struct {
	Protocol string // "http" or "https"
	Host     string // "example.com" or "192.168.1.1"
	Port     int    // 8080 (default: 80 for http, 443 for https)
	Path     string // "/api/users"
	Query    string // "id=123&name=test" (without leading ?)
	Fragment string // "section1" (without leading #)

	// Byte offsets in original URL
	ProtocolStart int
	ProtocolEnd   int
	HostStart     int
	HostEnd       int
	PathStart     int
	PathEnd       int
	QueryStart    int
	QueryEnd      int
	FragmentStart int
	FragmentEnd   int
}

ParsedURL represents a parsed URL with all components and byte offsets.

func ParseURL

func ParseURL(urlBytes []byte) (*ParsedURL, error)

ParseURL parses a URL byte slice into components. Main URL parsing function that handles both absolute and relative URLs.

Parameters:

Returns:

  • ParsedURL with all components extracted
  • error if URL is malformed

Algorithm:

  1. Parse protocol (http:// or https://)
  2. Parse host and port
  3. Parse path
  4. Parse query string (after ?)
  5. Parse fragment (after #)

Example:

url := []byte("http://example.com:8080/path?query=1#section")
parsed, _ := ParseURL(url)
// parsed.Protocol = "http"
// parsed.Host = "example.com"
// parsed.Port = 8080
// parsed.Path = "/path"
// parsed.Query = "query=1"
// parsed.Fragment = "section"
Example

ExampleParseURL demonstrates parsing an absolute HTTP URL with all components

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com:8080/api/users?id=123&page=1#results")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Protocol: %s\n", parsed.Protocol)
	fmt.Printf("Host: %s\n", parsed.Host)
	fmt.Printf("Port: %d\n", parsed.Port)
	fmt.Printf("Path: %s\n", parsed.Path)
	fmt.Printf("Query: %s\n", parsed.Query)
	fmt.Printf("Fragment: %s\n", parsed.Fragment)

}
Output:
Protocol: http
Host: example.com
Port: 8080
Path: /api/users
Query: id=123&page=1
Fragment: results
Example (ComplexQueryString)

ExampleParseURL_complexQueryString demonstrates parsing URL with complex query

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("https://api.example.com/search?q=golang+tutorial&category=web&sort=relevance&page=1")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Host: %s\n", parsed.Host)
	fmt.Printf("Path: %s\n", parsed.Path)
	fmt.Printf("Query: %s\n", parsed.Query)

}
Output:
Host: api.example.com
Path: /search
Query: q=golang+tutorial&category=web&sort=relevance&page=1
Example (FragmentOnly)

ExampleParseURL_fragmentOnly demonstrates URL with only fragment

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("https://example.com/docs#section-intro")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Path: %s\n", parsed.Path)
	fmt.Printf("Query: %q (empty)\n", parsed.Query)
	fmt.Printf("Fragment: %s\n", parsed.Fragment)

}
Output:
Path: /docs
Query: "" (empty)
Fragment: section-intro
Example (HttpsDefaultPort)

ExampleParseURL_httpsDefaultPort demonstrates HTTPS URL with default port

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("https://secure.example.com/login")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Protocol: %s\n", parsed.Protocol)
	fmt.Printf("Host: %s\n", parsed.Host)
	fmt.Printf("Port: %d\n", parsed.Port)
	fmt.Printf("Path: %s\n", parsed.Path)

}
Output:
Protocol: https
Host: secure.example.com
Port: 443
Path: /login
Example (IpAddress)

ExampleParseURL_ipAddress demonstrates parsing URL with IP address

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://192.168.1.1:3000/admin")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Host: %s\n", parsed.Host)
	fmt.Printf("Port: %d\n", parsed.Port)
	fmt.Printf("Path: %s\n", parsed.Path)

}
Output:
Host: 192.168.1.1
Port: 3000
Path: /admin
Example (Localhost)

ExampleParseURL_localhost demonstrates parsing localhost URL

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://localhost:8080/debug/pprof")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Protocol: %s\n", parsed.Protocol)
	fmt.Printf("Host: %s\n", parsed.Host)
	fmt.Printf("Port: %d\n", parsed.Port)
	fmt.Printf("Path: %s\n", parsed.Path)

}
Output:
Protocol: http
Host: localhost
Port: 8080
Path: /debug/pprof
Example (RelativePath)

ExampleParseURL_relativePath demonstrates parsing relative URL

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("/api/search?q=test&limit=10")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Protocol: %q (empty for relative)\n", parsed.Protocol)
	fmt.Printf("Path: %s\n", parsed.Path)
	fmt.Printf("Query: %s\n", parsed.Query)

}
Output:
Protocol: "" (empty for relative)
Path: /api/search
Query: q=test&limit=10
Example (RootPath)

ExampleParseURL_rootPath demonstrates parsing URL with root path

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("https://example.com/")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Protocol: %s\n", parsed.Protocol)
	fmt.Printf("Host: %s\n", parsed.Host)
	fmt.Printf("Port: %d\n", parsed.Port)
	fmt.Printf("Path: %s\n", parsed.Path)

}
Output:
Protocol: https
Host: example.com
Port: 443
Path: /

func (*ParsedURL) PathOnly

func (p *ParsedURL) PathOnly() string

PathOnly returns path component without query string.

func (*ParsedURL) PathWithQuery

func (p *ParsedURL) PathWithQuery() string

PathWithQuery returns path with query string.

Example

ExampleParsedURL_PathWithQuery demonstrates getting path with query string

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com/search?q=golang&page=2")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Get path with query string
	pathWithQuery := parsed.PathWithQuery()
	fmt.Printf("Path with query: %s\n", pathWithQuery)

}
Output:
Path with query: /search?q=golang&page=2

func (*ParsedURL) String

func (p *ParsedURL) String() string

String reconstructs full URL from parsed components (implements fmt.Stringer).

Example:

parsed := &ParsedURL{
    Protocol: "http",
    Host: "example.com",
    Port: 80,
    Path: "/path",
    Query: "id=123",
    Fragment: "section",
}
url := parsed.String()
// Returns "http://example.com/path?id=123#section"
Example

ExampleParsedURL_String demonstrates reconstructing a full URL

package main

import (
	"fmt"

	"github.com/vigolium/vigolium/pkg/httpmsg"
)

func main() {
	url := []byte("http://example.com:8080/path?query=1#section")

	parsed, err := httpmsg.ParseURL(url)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Reconstruct full URL
	fullURL := parsed.String()
	fmt.Printf("Full URL: %s\n", fullURL)

}
Output:
Full URL: http://example.com:8080/path?query=1#section

type PayloadMap

type PayloadMap map[InsertionPoint][]byte

PayloadMap maps insertion points to their payloads.

type PayloadWrapper

type PayloadWrapper struct {
	PayloadBytes []byte
	EncodingType byte
	OffsetsIn    []int
	OffsetsOut   []int
}

PayloadWrapper wraps a payload with encoding metadata. The wrapper acts as a data container that insertion points use to build encoded requests with proper offset tracking.

Example:

wrapper := NewPayloadWrapper([]byte("test"), 0)
encoded := wrapper.Apply(insertionPoint)
bytes := encoded.EncodedBytes()

func NewPayloadWrapper

func NewPayloadWrapper(payloadBytes []byte, encodingType byte) *PayloadWrapper

NewPayloadWrapper creates a new PayloadWrapper with default offsets. Default offsets are [0, payloadLength] for both input and output.

Parameters:

  • payloadBytes: Raw payload bytes to wrap
  • encodingType: Encoding type identifier

Returns:

  • New PayloadWrapper instance

func NewPayloadWrapperWithOffsets

func NewPayloadWrapperWithOffsets(payloadBytes []byte, encodingType byte, offsetsIn, offsetsOut []int) *PayloadWrapper

NewPayloadWrapperWithOffsets creates a PayloadWrapper with custom offsets.

Parameters:

  • payloadBytes: Raw payload bytes to wrap
  • encodingType: Encoding type identifier
  • offsetsIn: Input offsets for encoding
  • offsetsOut: Output offsets for encoding

Returns:

  • New PayloadWrapper instance

func (*PayloadWrapper) Apply

Apply creates an EncodedRequest using this wrapper and an insertion point. The returned EncodedRequest will lazily build the encoded request when EncodedBytes() or PayloadOffsets() is first called.

Parameters:

  • insertionPoint: Base insertion point that implements buildPayload/computeOffsets

Returns:

  • New EncodedRequest instance

type RequestInfo

type RequestInfo struct {
	// Headers is the list of HTTP header strings (including the request line)
	// Example: ["GET /path HTTP/1.1", "Host: example.com", "User-Agent: ..."]
	Headers []string

	// HTTPVersion indicates the HTTP version as an integer
	// Example: 11 for HTTP/1.1, 20 for HTTP/2.0
	HTTPVersion int

	// BodyOffset is the byte offset where the message body begins
	// This is the position immediately after the \r\n\r\n (or \n\n) separator
	BodyOffset int

	// Method is the HTTP request method (GET, POST, etc.)
	Method string

	// URL is the full request URL as a string
	URL string

	// HasBody indicates whether the request contains a message body
	// True for POST/PUT/PATCH requests, false for GET/HEAD/etc.
	HasBody bool

	// HttpService stores the target host from the Host header
	// Extracted from the request headers during AnalyzeRequest
	// Example: "example.com" or "example.com:8080"
	HttpService string

	// ContentType indicates the request body content type
	// Uses ContentType constants defined above
	ContentType ContentType

	// Parameters is the list of extracted HTTP parameters
	// Includes URL params, body params, cookies, etc. depending on request type
	Parameters []*Param
}

RequestInfo represents parsed HTTP request information. This is the primary container for analyzed request data.

Field layout: - Headers: HTTP headers including request line - HTTPVersion: HTTP version (11 for HTTP/1.1) - BodyOffset: byte offset where body starts - Method: HTTP method (GET, POST, etc.) - URL: request path with query string - HasBody: whether request has a body - HttpService: Host header value - ContentType: parsed content type - Parameters: extracted parameters from URL, body, cookies

func AnalyzeRequest

func AnalyzeRequest(request []byte) (*RequestInfo, error)

AnalyzeRequest analyzes an HTTP request and returns parsed information. This is the main entry point that orchestrates all parsing components.

Algorithm: 1. Extract headers and find body offset 2. Parse request line (method, URL, HTTP version) from first header 3. Determine Content-Type from headers 4. Parse URL query parameters from request line 5. Dispatch to appropriate body parser based on Content-Type:

  • application/x-www-form-urlencoded → ParseURLEncodedBody
  • multipart/form-data → ParseMultipartBody
  • application/json → ParseJSONBody
  • application/xml, text/xml → ParseXMLBody

6. Extract cookies from Cookie headers 7. Combine all parameters into single list 8. Populate and return RequestInfo

Example:

request := []byte("POST /api?filter=active HTTP/1.1\r\n" +
    "Cookie: session=abc123; user=john\r\n" +
    "Content-Type: application/json\r\n\r\n" +
    `{"action":"update"}`)

info, err := AnalyzeRequest(request)
// info.Method = "POST"
// info.URL = "/api?filter=active"
// info.Parameters contains:
//   - 1 URL parameter (filter)
//   - 2 cookie parameters (session, user)
//   - 1 JSON parameter (action)

Parameters:

  • request: Complete HTTP request bytes including headers and body

Returns:

  • RequestInfo with all parsed data
  • Error if parsing fails

func NewRequestInfo

func NewRequestInfo() *RequestInfo

NewRequestInfo creates a new RequestInfo instance with default values.

func (*RequestInfo) HasParameter

func (ri *RequestInfo) HasParameter(name string) bool

HasParameter checks if a parameter with the given name exists.

func (*RequestInfo) Header

func (ri *RequestInfo) Header(name string) string

Header finds and returns a specific header value by name (case-insensitive). Returns empty string if header is not found.

func (*RequestInfo) Parameter

func (ri *RequestInfo) Parameter(name string) *Param

Parameter finds and returns a specific parameter by name. Returns nil if parameter is not found.

func (*RequestInfo) ParametersByType

func (ri *RequestInfo) ParametersByType(paramType ParamType) []*Param

ParametersByType returns all parameters of a specific type.

type RequestOption

type RequestOption func(*requestBuilder)

RequestOption is a functional option for building requests efficiently. Use with Apply() to batch multiple modifications in a single rebuild.

Example:

req = req.Apply(
    WithOptMethod("POST"),
    WithOptHeader("Content-Type", "application/json"),
    WithOptHeader("X-Custom", "value"),
    WithOptBody(jsonBody),
)

func WithOptAddHeader

func WithOptAddHeader(name, value string) RequestOption

WithOptAddHeader returns an option that adds a header (may create duplicates).

func WithOptBody

func WithOptBody(body []byte) RequestOption

WithOptBody returns an option that sets the request body. Content-Length is updated automatically.

func WithOptHeader

func WithOptHeader(name, value string) RequestOption

WithOptHeader returns an option that sets or replaces a header.

func WithOptMethod

func WithOptMethod(method string) RequestOption

WithOptMethod returns an option that sets the HTTP method.

func WithOptPath

func WithOptPath(path string) RequestOption

WithOptPath returns an option that sets the request path.

func WithOptRemoveHeader

func WithOptRemoveHeader(name string) RequestOption

WithOptRemoveHeader returns an option that removes a header.

func WithOptService

func WithOptService(service *Service) RequestOption

WithOptService returns an option that sets the service.

type ResponseInfo

type ResponseInfo struct {
	// StatusCode is the HTTP status code
	StatusCode int16

	// Headers is the list of HTTP headers (including status line)
	Headers []string

	// BodyOffset is the byte offset where response body starts
	BodyOffset int

	// StatedMimeType is the MIME type from Content-Type header
	StatedMimeType string

	// InferredMimeType is the inferred MIME type from body content
	InferredMimeType string

	// Cookies is the list of parsed Set-Cookie headers
	Cookies []*Cookie
}

ResponseInfo represents parsed HTTP response information.

func AnalyzeResponse

func AnalyzeResponse(response []byte) (*ResponseInfo, error)

AnalyzeResponse analyzes an HTTP response and extracts structured information.

Algorithm:

  1. Find body offset
  2. Extract headers
  3. Parse status line for status code
  4. Extract Content-Type for StatedMimeType
  5. Infer MIME type from response body
  6. Parse all Set-Cookie headers into Cookie objects

Parameters:

  • response: Complete HTTP response bytes

Returns:

  • ResponseInfo with all fields populated
  • Error if parsing fails

Example:

response := []byte("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nSet-Cookie: id=123\r\n\r\n<html>")
info, _ := AnalyzeResponse(response)
// info.StatusCode = 200
// info.StatedMimeType = "text/html"
// info.Cookies[0].Name = "id", Value = "123"

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service represents an HTTP service (host, port, protocol).

func NewService

func NewService(host string, port int, protocol string) (*Service, error)

NewService creates a new Service instance from protocol string.

func NewServiceSecure

func NewServiceSecure(host string, port int, useHTTPS bool) *Service

NewServiceSecure creates a new Service instance from boolean flag.

func ParseService

func ParseService(urlStr string) (*Service, error)

ParseService parses a URL string and creates Service.

func (*Service) Host

func (s *Service) Host() string

Host returns the service hostname.

func (*Service) Port

func (s *Service) Port() int

Port returns the service port.

func (*Service) Protocol

func (s *Service) Protocol() string

Protocol returns the service protocol ("http" or "https").

type URLEncoder

type URLEncoder struct {
	// contains filtered or unexported fields
}

URLEncoder implements URL percent-encoding with custom charset support.

func NewURLEncoder

func NewURLEncoder() *URLEncoder

NewURLEncoder creates a new URL encoder instance.

func (*URLEncoder) AddAllowedChar

func (e *URLEncoder) AddAllowedChar(c byte)

AddAllowedChar adds a character to the allowed set (won't be encoded).

func (*URLEncoder) Decode

func (e *URLEncoder) Decode(input []byte) []byte

Decode performs URL decoding.

func (*URLEncoder) Encode

func (e *URLEncoder) Encode(input []byte, offsets []int) []byte

Encode performs URL encoding with offset tracking.

func (*URLEncoder) RemoveAllowedChar

func (e *URLEncoder) RemoveAllowedChar(c byte)

RemoveAllowedChar removes a character from the allowed set.

type URLEncoderExtended

type URLEncoderExtended struct {
	// contains filtered or unexported fields
}

URLEncoderExtended implements URL encoding with character substitution.

func NewURLEncoderExtended

func NewURLEncoderExtended() *URLEncoderExtended

NewURLEncoderExtended creates a new extended URL encoder instance.

func (*URLEncoderExtended) ClearCharMappings

func (e *URLEncoderExtended) ClearCharMappings()

ClearCharMappings removes all character substitutions.

func (*URLEncoderExtended) Decode

func (e *URLEncoderExtended) Decode(input []byte) []byte

Decode performs URL decoding.

func (*URLEncoderExtended) Encode

func (e *URLEncoderExtended) Encode(input []byte, offsets []int) []byte

Encode performs URL encoding with character substitution.

func (*URLEncoderExtended) RemoveCharMapping

func (e *URLEncoderExtended) RemoveCharMapping(char byte)

RemoveCharMapping removes a character substitution from the map.

func (*URLEncoderExtended) SetCharMapping

func (e *URLEncoderExtended) SetCharMapping(char byte, replacement []byte)

SetCharMapping sets a character substitution in the map.

type XMLAttribute

type XMLAttribute struct {
	Name       string // Attribute name
	Value      string // Attribute value (decoded)
	NameStart  int    // Byte offset where name starts
	NameEnd    int    // Byte offset where name ends
	ValueStart int    // Byte offset where value starts (excluding quotes)
	ValueEnd   int    // Byte offset where value ends (excluding quotes)
	QuoteType  byte   // Quote type used (0=", 1=', 2=`, 3=none)
}

XMLAttribute represents an XML attribute with its value and position.

type XMLElement

type XMLElement struct {
	Type       byte           // Element type (open, close, text, etc.)
	TagName    string         // Element tag name (lowercase)
	TagStart   int            // Byte offset where tag name starts
	TagEnd     int            // Byte offset where tag name ends
	Start      int            // Byte offset where element starts (at '<')
	End        int            // Byte offset where element ends (after '>')
	Text       string         // Text content (for text nodes)
	Attributes []XMLAttribute // Element attributes
}

XMLElement represents a parsed XML element with its attributes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL