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 ¶
- Constants
- Variables
- func AddHeader(message []byte, name, value string) ([]byte, error)
- func AddHeaderIfNotExists(request []byte, name, value string) ([]byte, error)
- func AddMultipleParameters(request []byte, params []*Param) ([]byte, error)
- func AddOrReplaceHeader(request []byte, name, value string) ([]byte, error)
- func AddParameter(request []byte, param *Param) ([]byte, error)
- func AppendBody(request []byte, data []byte) ([]byte, error)
- func AppendBodyParameter(request []byte, name, value string) ([]byte, error)
- func AppendToHeader(request []byte, name, appendValue string) ([]byte, error)
- func AppendToPath(request []byte, segment string) ([]byte, error)
- func AppendURLParameter(request []byte, name, value string) ([]byte, error)
- func Base64Decode(s string) ([]byte, error)
- func Base64DecodeBytes(data []byte) ([]byte, error)
- func Base64Encode(s string) string
- func Base64EncodeBytes(data []byte) string
- func BuildHttpMessage(headers []string, body []byte) []byte
- func BuildHttpRequest(urlStr string) ([]byte, error)
- func BuildRawResponse(statusCode int, headers map[string]string, body string) []byte
- func BuildRequestWithPayloads(request []byte, payloads PayloadMap) ([][]byte, error)
- func BuildRequestWithSamePayload(request []byte, insertionPoints []InsertionPoint, payload []byte) ([][]byte, error)
- func ByteArrayEquals(a, b []byte) bool
- func ByteArrayEqualsCaseInsensitive(a, b []byte) bool
- func ByteArrayStartsWith(haystack, needle []byte, caseSensitive bool, offset int) bool
- func BytesToHexString(data []byte) string
- func BytesToString(b []byte) string
- func ClearBody(request []byte) ([]byte, error)
- func ClearQueryString(request []byte) ([]byte, error)
- func CloneRequest(request []byte) []byte
- func CompressBytes(input []byte) []byte
- func ContainsBytes(data, pattern []byte) bool
- func ConvertToHTTP1(request []byte) []byte
- func CountMatches(data, pattern []byte) int
- func DecodePathValue(encoded string) string
- func DecodeQueryValue(encoded string) string
- func DecompressBytes(input []byte) []byte
- func DefaultUserAgent() string
- func EncodeBytesAbove7F(input string) string
- func EncodePathValue(decoded string) string
- func EncodePathValueBytes(src []byte) []byte
- func EncodeQueryValue(decoded string) string
- func EncodeQueryValueBytes(src []byte) []byte
- func EnsureURLScheme(target, defaultScheme string) string
- func EnsureURLSchemeTracked(target, defaultScheme string) (string, bool)
- func EqualsCaseInsensitive(a, b string) bool
- func EqualsIgnoreCase(a, b string) bool
- func EstimateRetainedBytes(points []InsertionPoint) int
- func ExtractAllHeaders(data []byte) ([]string, []int, int, error)
- func ExtractBoundary(contentType string) string
- func ExtractHeaders(request []byte, startOffset, endOffset int) ([]string, []int, error)
- func ExtractURLFromRequest(request []byte) ([]byte, int, int, error)
- func FilterString(input, safeChars string) string
- func FindBodyEnd(request []byte, startOffset int) int
- func FindBodyOffset(request []byte) int
- func FindCharIndex(s string, c byte) int
- func FindColonIndex(header string) int
- func FindHeaderBodySeparator(data []byte, startOffset int) int
- func FindHostEnd(url []byte, hostStart int) int
- func FindHttpHeader(headers []HttpHeader, name string) (string, bool)
- func FindLineEnd(data []byte, start, end int) int
- func FindPathEnd(url []byte, pathStart int) int
- func FindProtocolEnd(url []byte) int
- func FindQueryEnd(url []byte, queryStart int) int
- func FindQueryStart(url []byte) int
- func FindQueryStringBounds(urlPath []byte) (queryStart int, queryEnd int)
- func FlattenCookiesForHost(host string, cookies []*http.Cookie) string
- func GetAllCookies(request []byte) (map[string]string, error)
- func GetAllHeaderValues(request []byte, name string) ([]string, error)
- func GetBody(request []byte) ([]byte, error)
- func GetBodyBytes(request []byte, bodyOffset int) []byte
- func GetBodyOffset(request []byte) (int, error)
- func GetBodyParametersMap(request []byte) (map[string]string, error)
- func GetBodySize(request []byte) (int, error)
- func GetBodyString(request []byte) (string, error)
- func GetContentType(request []byte) (string, error)
- func GetCookie(request []byte, name string) (string, error)
- func GetDefaultPort(protocol string) int
- func GetExtension(request []byte) (string, error)
- func GetHTTPVersion(request []byte) (string, error)
- func GetHeaderOffsets(request []byte, name string) []int
- func GetHeaderValue(request []byte, name string) (string, error)
- func GetHeaders(message []byte) string
- func GetHeadersByPrefix(request []byte, prefix string) ([]string, error)
- func GetHeadersBytes(message []byte) []byte
- func GetHeadersSize(request []byte) (int, error)
- func GetHost(request []byte) (string, error)
- func GetMatches(data, pattern []byte, limit int) [][2]int
- func GetMethod(request []byte) (string, error)
- func GetNestedResponse(response []byte) []byte
- func GetParameter(request []byte, name string, paramType ParamType) (string, error)
- func GetParameterOrDefault(request []byte, name string, paramType ParamType, defaultVal string) string
- func GetPath(request []byte) (string, error)
- func GetPathOnly(request []byte) (string, error)
- func GetQueryString(request []byte) (string, error)
- func GetRequestBuffer(n int) []byte
- func GetRequestSize(request []byte) int
- func GetStartType(response []byte) string
- func GetStatusCode(response []byte) int16
- func GetURLFromRequest(scheme string, request []byte) string
- func GetURLFromService(request []byte, httpService *Service) (*urlutil.URL, error)
- func GetURLParameter(request []byte, name string) (string, error)
- func GetURLParametersMap(request []byte) (map[string]string, error)
- func HasBody(request []byte) (bool, error)
- func HasCookie(request []byte, name string) (bool, error)
- func HasHeader(request []byte, name string) (bool, error)
- func HasParameter(request []byte, name string, paramType ParamType) (bool, error)
- func HasURLEncodedBody(headers []string) bool
- func HasURLParameter(request []byte, name string) (bool, error)
- func Header(headers []string, name string) string
- func HeadersToStrings(headers []HttpHeader) []string
- func HexCharToValue(ch byte) int
- func HostnameFromURL(raw string) string
- func HttpHeadersContain(headers []HttpHeader, name string) bool
- func IndexOf(haystack, needle []byte, caseSensitive bool, start, end int) int
- func IndexOfByte(data []byte, target byte, startOffset int) int
- func IndexOfByteInRange(haystack []byte, target byte, caseSensitive bool, start, end int) int
- func IndexOfBytes(data []byte, target []byte, startOffset int) int
- func InsertionPointRequiresContentLengthUpdate(ipType InsertionPointType) bool
- func IntToString(n int) string
- func IsAbsoluteURL(urlBytes []byte) bool
- func IsHTTP2(request []byte) bool
- func IsMethodType(request []byte, method string) (bool, error)
- func IsRedirectStatus(code int) bool
- func IsRelativeURL(urlBytes []byte) bool
- func IsRequest(message []byte) bool
- func IsResponse(message []byte) bool
- func IsWellFormed(request []byte) bool
- func IsWhitespace(c byte) bool
- func MeasuredMillis(d time.Duration) int64
- func MergeCookieHeaders(existing, carried string) string
- func NormalizeHost(host string) string
- func NormalizeLineEndings(request []byte) []byte
- func OriginFromURL(raw string) string
- func OriginMatchesURL(origin string, u *url.URL) bool
- func OriginRefererScheme(raw, host string, targetPort int) (scheme, header string, ok bool)
- func ParseContentType(headers []string) (contentType string, boundary string)
- func ParseHostPort(hostBytes []byte) (host string, port int)
- func ParseInt(s string) int
- func ParseParameter(params string, name string) string
- func PrependBody(request []byte, data []byte) ([]byte, error)
- func PutRequestBuffer(buf []byte)
- func ReconcileDeclaredBody(raw []byte) []byte
- func RemoveAllParametersByType(request []byte, paramType ParamType) ([]byte, error)
- func RemoveCookie(request []byte, name string) ([]byte, error)
- func RemoveHeader(message []byte, name string) ([]byte, error)
- func RemoveParameter(request []byte, param *Param) ([]byte, error)
- func RemoveParametersByName(request []byte, names []string, paramType ParamType) ([]byte, error)
- func ReplaceHeader(request []byte, name, value string) ([]byte, error)
- func RequestsEqual(req1, req2 []byte) bool
- func RequestsEqualNormalized(req1, req2 []byte) bool
- func SetAllCookies(request []byte, cookies map[string]string) ([]byte, error)
- func SetBody(request []byte, body []byte) ([]byte, error)
- func SetBodyParametersMap(request []byte, params map[string]string) ([]byte, error)
- func SetBodyString(request []byte, body string) ([]byte, error)
- func SetBuildVersion(v string)
- func SetContentType(request []byte, contentType string) ([]byte, error)
- func SetCookie(request []byte, name, value string) ([]byte, error)
- func SetDefaultUserAgent(ua string)
- func SetMethod(request []byte, method string) ([]byte, error)
- func SetPath(request []byte, path string) ([]byte, error)
- func SetPathOnly(request []byte, pathOnly string) ([]byte, error)
- func SetQueryString(request []byte, query string) ([]byte, error)
- func SetURLParametersMap(request []byte, params map[string]string) ([]byte, error)
- func SkipLineTerminator(data []byte, pos, end int) int
- func SliceBytes(data []byte, start, end int) []byte
- func StartsWithIgnoreCase(s, prefix string) bool
- func StringToBytes(s string) []byte
- func StripTrailingData(request []byte) []byte
- func SwitchMethod(request []byte, newMethod string) ([]byte, error)
- func ToLower(c byte) byte
- func ToLowerByte(b byte) byte
- func ToLowerString(s string) string
- func ToggleRequestMethod(request []byte) ([]byte, error)
- func TrimSpace(s string) string
- func UpdateContentLength(message []byte) ([]byte, error)
- func UpdateParameter(request []byte, param *Param) ([]byte, error)
- func UrlDecode(s string) string
- func UrlDecodeBytes(data []byte) ([]byte, error)
- func UrlEncode(s string) string
- func UrlEncodeBytes(data []byte) []byte
- func UserAgentIsHonestPreset() bool
- func ValidateHeaders(request []byte) error
- func ValidateRequest(request []byte) error
- func ValidateRequestLine(request []byte) error
- type Base64Encoder
- type BaseInsertionPointInterface
- type CarriedCookie
- type CarriedSession
- type ContentType
- type Cookie
- type EncodedInsertionPoint
- func (e *EncodedInsertionPoint) BaseValue() string
- func (e *EncodedInsertionPoint) BuildRequest(payload []byte) []byte
- func (e *EncodedInsertionPoint) GetEncoder() Encoder
- func (e *EncodedInsertionPoint) Name() string
- func (e *EncodedInsertionPoint) PayloadOffsets(payload []byte) []int
- func (e *EncodedInsertionPoint) SetEncoder(encoder Encoder)
- func (e *EncodedInsertionPoint) Type() InsertionPointType
- type EncodedRequest
- type EncodedRequestImpl
- type Encoder
- type GzipEncoder
- type HeaderInsertionPoint
- type HexBuilder
- type HexValue
- type HttpHeader
- func AppendHttpHeader(headers []HttpHeader, name, value string) []HttpHeader
- func CloneHttpHeaders(headers []HttpHeader) []HttpHeader
- func NewHttpHeader(name, value string) HttpHeader
- func ParseHeadersFromStrings(headerStrings []string) []HttpHeader
- func ParseHttpHeader(line string) HttpHeader
- func RemoveHttpHeader(headers []HttpHeader, name string) []HttpHeader
- func SetHttpHeader(headers []HttpHeader, name, value string) []HttpHeader
- type HttpRequest
- func (r *HttpRequest) Apply(opts ...RequestOption) *HttpRequest
- func (r *HttpRequest) Body() []byte
- func (r *HttpRequest) BodyOffset() int
- func (r *HttpRequest) BodyToString() string
- func (r *HttpRequest) Clone() *HttpRequest
- func (r *HttpRequest) HasHeader(name string) bool
- func (r *HttpRequest) Header(name string) string
- func (r *HttpRequest) Headers() []HttpHeader
- func (r *HttpRequest) ID() string
- func (r *HttpRequest) IdentityFingerprint() string
- func (r *HttpRequest) IsMediaPath() bool
- func (r *HttpRequest) Method() string
- func (r *HttpRequest) Parameters() ([]*Param, error)
- func (r *HttpRequest) Path() string
- func (r *HttpRequest) Raw() []byte
- func (r *HttpRequest) Service() *Service
- func (r *HttpRequest) TruncateBody(maxSize int)
- func (r *HttpRequest) URL() (*urlutil.URL, error)
- func (r *HttpRequest) WithAddedHeader(name, value string) *HttpRequest
- func (r *HttpRequest) WithBody(body []byte) *HttpRequest
- func (r *HttpRequest) WithHeader(name, value string) *HttpRequest
- func (r *HttpRequest) WithMethod(method string) *HttpRequest
- func (r *HttpRequest) WithPath(path string) *HttpRequest
- func (r *HttpRequest) WithRemovedHeader(name string) *HttpRequest
- func (r *HttpRequest) WithService(service *Service) *HttpRequest
- type HttpRequestResponse
- func FromSentRequest(req *http.Request) (*HttpRequestResponse, error)
- func FromStdRequest(req *http.Request) (*HttpRequestResponse, error)
- func GetRawRequestFromURL(url string) (*HttpRequestResponse, error)
- func GetRawRequestFromURLWithMethod(rawURL, method string, headers map[string]string, body []byte) (*HttpRequestResponse, error)
- func NewHttpRequestResponse(request *HttpRequest, response *HttpResponse) *HttpRequestResponse
- func NewRequestResponseRaw(raw []byte, service *Service) *HttpRequestResponse
- func ParseRawRequest(raw string) (rr *HttpRequestResponse, err error)
- func ParseRawRequestWithURL(raw, url string) (*HttpRequestResponse, error)
- func (h *HttpRequestResponse) BuildRetryableRequest() (*retryablehttp.Request, error)
- func (h *HttpRequestResponse) BuildRetryableRequestWithContext(ctx context.Context) (*retryablehttp.Request, error)
- func (h *HttpRequestResponse) Clone() *HttpRequestResponse
- func (h *HttpRequestResponse) CreateInsertionPoints(includeNested bool) ([]InsertionPoint, error)
- func (h *HttpRequestResponse) GetScanHash(templateId string) string
- func (h *HttpRequestResponse) HasResponse() bool
- func (h *HttpRequestResponse) ID() string
- func (h *HttpRequestResponse) MarshalBytes() ([]byte, error)
- func (h *HttpRequestResponse) MarshalJSON() ([]byte, error)
- func (h *HttpRequestResponse) MarshalString() (string, error)
- func (h *HttpRequestResponse) MustMarshalBytes() []byte
- func (h *HttpRequestResponse) MustMarshalString() string
- func (h *HttpRequestResponse) PrettyPrint() string
- func (h *HttpRequestResponse) Request() *HttpRequest
- func (h *HttpRequestResponse) Response() *HttpResponse
- func (h *HttpRequestResponse) Service() *Service
- func (h *HttpRequestResponse) Target() string
- func (h *HttpRequestResponse) URL() (*urlutil.URL, error)
- func (h *HttpRequestResponse) UnmarshalJSON(data []byte) error
- func (h *HttpRequestResponse) WithResponse(response *HttpResponse) *HttpRequestResponse
- func (h *HttpRequestResponse) WithService(service *Service) *HttpRequestResponse
- type HttpResponse
- func (r *HttpResponse) Body() []byte
- func (r *HttpResponse) BodyLowerString() string
- func (r *HttpResponse) BodyOffset() int
- func (r *HttpResponse) BodyToString() string
- func (r *HttpResponse) Clone() *HttpResponse
- func (r *HttpResponse) Cookies() []*Cookie
- func (r *HttpResponse) Duration() time.Duration
- func (r *HttpResponse) HasHeader(name string) bool
- func (r *HttpResponse) Head() []byte
- func (r *HttpResponse) Header(name string) string
- func (r *HttpResponse) Headers() []HttpHeader
- func (r *HttpResponse) ID() string
- func (r *HttpResponse) RatioSignature(compute func(body string) any) any
- func (r *HttpResponse) Raw() []byte
- func (r *HttpResponse) StatusCode() int
- func (r *HttpResponse) TruncateBody(maxSize int)
- func (r *HttpResponse) WithAddedHeader(name, value string) *HttpResponse
- func (r *HttpResponse) WithBody(body []byte) *HttpResponse
- func (r *HttpResponse) WithHeader(name, value string) *HttpResponse
- func (r *HttpResponse) WithRemovedHeader(name string) *HttpResponse
- func (r *HttpResponse) WithStatusCode(code int) *HttpResponse
- type InsertionPoint
- type InsertionPointType
- type JSONEscapeEncoder
- type JSONStringEncoder
- type JSONValueType
- type NestedInsertionPoint
- func DiscoverNestedInsertionPoints(request []byte, params []*Param) []*NestedInsertionPoint
- func NewNestedInsertionPoint(request []byte, parentParam, childParam InsertionPoint) *NestedInsertionPoint
- func NewNestedInsertionPointFromIP(request []byte, parentNested *NestedInsertionPoint, childParam InsertionPoint) *NestedInsertionPoint
- func (n *NestedInsertionPoint) BaseValue() string
- func (n *NestedInsertionPoint) BuildRequest(payload []byte) []byte
- func (n *NestedInsertionPoint) BuildState() int
- func (n *NestedInsertionPoint) EncodingChain() string
- func (n *NestedInsertionPoint) Name() string
- func (n *NestedInsertionPoint) PayloadOffsets(payload []byte) []int
- func (n *NestedInsertionPoint) SetBuildState(state int)
- func (n *NestedInsertionPoint) Type() InsertionPointType
- type NoopEncoder
- type Param
- func BuildParameter(name, value string, paramType ParamType) *Param
- func ExtractBodyParameters(request []byte) (params []*Param, bodyOffset int, err error)
- func ExtractQueryParameters(request []byte, urlStart, urlEnd int) ([]*Param, error)
- func GetAllParameters(request []byte) ([]*Param, error)
- func GetParametersByType(request []byte, paramType ParamType) ([]*Param, error)
- func GetRequestParameter(request []byte, name string) (*Param, error)
- func NewBodyParam(name, value string) *Param
- func NewCookieParam(name, value string) *Param
- func NewJSONParam(name, value string) *Param
- func NewJSONParsedParam(name, value string, nameStart, nameEnd, valueStart, valueEnd int, ...) *Param
- func NewParam(ptype ParamType, name, value string) *Param
- func NewParsedParam(ptype ParamType, name, value string, ...) *Param
- func NewParsedParamWithMetadata(ptype ParamType, name, value string, ...) *Param
- func NewPathFilenameParam(name, value string) *Param
- func NewPathFolderParam(name, value string) *Param
- func NewURLParam(name, value string) *Param
- func NewXMLParam(name, value string) *Param
- func ParseJSONBody(request []byte, bodyOffset int) ([]*Param, error)
- func ParseMultipartBody(request []byte, bodyOffset int, boundary string) ([]*Param, error)
- func ParseMultipartRequest(request []byte) ([]*Param, error)
- func ParsePathParameters(request []byte) ([]*Param, error)
- func ParseQueryParameters(queryString []byte) []*Param
- func ParseQueryString(url []byte) ([]*Param, error)
- func ParseURLEncodedBody(request []byte, bodyOffset int) ([]*Param, error)
- func ParseURLEncodedBodyString(body string) ([]*Param, error)
- func ParseXMLBody(request []byte, bodyOffset int) ([]*Param, error)
- func (p *Param) CanURLEncode() bool
- func (p *Param) HasNameOffsets() bool
- func (p *Param) HasOffsets() bool
- func (p *Param) IsPathParameter() bool
- func (p *Param) JSONType() JSONValueType
- func (p *Param) Metadata() string
- func (p *Param) Name() string
- func (p *Param) NameEnd() int
- func (p *Param) NameStart() int
- func (p *Param) RequiresContentLengthUpdate() bool
- func (p *Param) ToInsertionPointType() InsertionPointType
- func (p *Param) Type() ParamType
- func (p *Param) Value() string
- func (p *Param) ValueEnd() int
- func (p *Param) ValueStart() int
- func (p *Param) WithAdjustedOffsets(delta int) *Param
- func (p *Param) WithJSONType(jsonType JSONValueType) *Param
- func (p *Param) WithMetadata(metadata string) *Param
- func (p *Param) WithOffsets(nameStart, nameEnd, valueStart, valueEnd int) *Param
- func (p *Param) WithValue(value string) *Param
- type ParamType
- type ParameterDescriptor
- type ParameterFormat
- type ParameterInsertionPoint
- func (p *ParameterInsertionPoint) BaseValue() string
- func (p *ParameterInsertionPoint) BuildRequest(payload []byte) []byte
- func (p *ParameterInsertionPoint) BuildRequestPooled(payload []byte) []byte
- func (p *ParameterInsertionPoint) Name() string
- func (p *ParameterInsertionPoint) PayloadOffsets(payload []byte) []int
- func (p *ParameterInsertionPoint) Type() InsertionPointType
- type ParsedURL
- type PayloadMap
- type PayloadWrapper
- type RequestInfo
- type RequestOption
- func WithOptAddHeader(name, value string) RequestOption
- func WithOptBody(body []byte) RequestOption
- func WithOptHeader(name, value string) RequestOption
- func WithOptMethod(method string) RequestOption
- func WithOptPath(path string) RequestOption
- func WithOptRemoveHeader(name string) RequestOption
- func WithOptService(service *Service) RequestOption
- type ResponseInfo
- type Service
- type URLEncoder
- type URLEncoderExtended
- func (e *URLEncoderExtended) ClearCharMappings()
- func (e *URLEncoderExtended) Decode(input []byte) []byte
- func (e *URLEncoderExtended) Encode(input []byte, offsets []int) []byte
- func (e *URLEncoderExtended) RemoveCharMapping(char byte)
- func (e *URLEncoderExtended) SetCharMapping(char byte, replacement []byte)
- type XMLAttribute
- type XMLElement
Examples ¶
- Package (AnalyzeRequest_Combined)
- Package (AnalyzeRequest_EmptyBody)
- Package (AnalyzeRequest_GET)
- Package (AnalyzeRequest_HTTPVersions)
- Package (AnalyzeRequest_HelperMethods)
- Package (AnalyzeRequest_JSON)
- Package (AnalyzeRequest_POST)
- Package (AnalyzeRequest_ParameterOffsets)
- Package (AnalyzeRequest_WithCookies)
- Package (CaseInsensitiveComparison)
- Package (ExtractAllHeaders)
- Package (ExtractHeaders)
- Package (FindHeaderBodySeparator)
- Package (GetHeader)
- Package (HeaderOffsets)
- Package (LfLineEndings)
- Package (MultipleHeaders)
- Package (ParseContentType)
- Package (ParseContentTypeSimple)
- DecodeQueryValue
- DecodeQueryValue (SpecialCharacters)
- EncodeQueryValue
- EncodeQueryValue (RoundTrip)
- ExtractBoundary
- ExtractBoundary (WithWhitespace)
- ExtractQueryParameters
- ExtractURLFromRequest
- ExtractURLFromRequest (Post)
- FindBodyEnd
- FindBodyOffset
- FindBodyOffset (HttpWorkflow)
- FindBodyOffset (Lf)
- FindQueryEnd
- FindQueryStart
- FindQueryStringBounds
- GetDefaultPort
- HexCharToValue
- IndexOfByte
- IndexOfBytes
- IsAbsoluteURL
- IsRelativeURL
- ParseHostPort
- ParseHostPort (NoPort)
- ParseMultipartBody
- ParseMultipartBody (ComplexForm)
- ParseMultipartBody (EmptyValue)
- ParseMultipartBody (FileUpload)
- ParseMultipartBody (MultipleFields)
- ParseMultipartBody (Offsets)
- ParseMultipartBody (WithMetadata)
- ParseMultipartRequest
- ParseQueryString
- ParseQueryString (Complex)
- ParseQueryString (EmptyAndMissing)
- ParseQueryString (MultipleParameters)
- ParseQueryString (Offsets)
- ParseQueryString (WithFragment)
- ParseURL
- ParseURL (ComplexQueryString)
- ParseURL (FragmentOnly)
- ParseURL (HttpsDefaultPort)
- ParseURL (IpAddress)
- ParseURL (Localhost)
- ParseURL (RelativePath)
- ParseURL (RootPath)
- ParseXMLBody
- ParseXMLBody (EmptyAndWhitespace)
- ParseXMLBody (Nested)
- ParseXMLBody (Offsets)
- ParseXMLBody (RealWorldAPI)
- ParseXMLBody (SelfClosing)
- ParseXMLBody (Soap)
- ParseXMLBody (WithAttributes)
- ParsedURL.PathWithQuery
- ParsedURL.String
- SliceBytes
Constants ¶
const ( // HTTP line ending bytes CR = byte(13) // \r (0x0D) LF = byte(10) // \n (0x0A) )
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
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
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.
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.
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.
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.
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.
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).
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 ¶
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.
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.
var ErrNilService = &nilServiceError{}
Sentinel error
Functions ¶
func AddHeader ¶
AddHeader adds a new header to an HTTP message.
Algorithm:
- Find header end position (before separator)
- Write message up to that position
- Write new header bytes
- Write CRLF
- Write rest of message from that position onward
- 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 ¶
AddHeaderIfNotExists adds a header only if it doesn't already exist.
Algorithm:
- Check if header exists
- If not exists, add header
- 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 ¶
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 ¶
AddOrReplaceHeader adds header if not exists, replaces if exists.
Algorithm:
- Check if header exists
- 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 ¶
AddParameter adds a parameter to an HTTP request.
Algorithm:
- Analyze request to get current parameters and structure
- Determine where to add parameter based on type
- For ParamURL: Add to query string
- For ParamBody: Add to request body (URL-encoded or multipart)
- For ParamCookie: Add to Cookie header
- Rebuild request with new parameter
- 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 ¶
AppendBody appends data to the existing request body. Provides body append operation.
Algorithm:
- Get current body
- Append new data
- 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 ¶
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 ¶
AppendToHeader appends a value to an existing header. If header doesn't exist, returns request unchanged.
Algorithm:
- Find header offsets using GetHeaderOffsets
- If header not found, return unchanged
- Insert append value at the end of header value
- 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 ¶
AppendToPath appends a segment to path (before query string).
Algorithm:
- Get current path without query
- Get query string if present
- Append segment to path
- Reattach query string
- 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 ¶
AppendURLParameter adds a URL parameter without removing existing ones. Provides append operation.
Algorithm:
- Get current query string
- URL-encode new parameter
- Append with '&' separator if query exists
- 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
BuildHttpMessage builds an HTTP message from headers and body.
Algorithm:
- Write each header line followed by CRLF
- Write additional CRLF to separate headers from body
- Append body bytes if present
- 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 ¶
BuildHttpRequest builds a basic HTTP GET request for a URL.
Parameters:
- urlStr: Complete URL string (e.g., "http://example.com:8080/path")
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
BytesToHexString converts bytes to hex string (space-separated).
Algorithm:
- Loop through bytes
- Convert each byte to 2-digit hex
- 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 ¶
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 ¶
ClearBody removes the body from an HTTP request. Provides body removal.
Algorithm:
- Set body to empty byte array
- 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 ¶
ClearQueryString removes all query parameters from a request. Provides query removal.
Algorithm:
- Get path without query using GetPathOnly
- 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 ¶
CloneRequest creates a deep copy of a request.
Parameters:
- request: HTTP request bytes
Returns:
- Cloned request
Example:
clone := CloneRequest(request)
func CompressBytes ¶
CompressBytes is a convenience function for simple gzip compression.
func ContainsBytes ¶
ContainsBytes checks if data contains the pattern.
Algorithm:
- Use IndexOfBytes to find first occurrence
- 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 ¶
ConvertToHTTP1 converts HTTP/2 request to HTTP/1.1.
Algorithm:
- Find "HTTP/2" in request line
- Replace with "HTTP/1.1"
- 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 ¶
CountMatches counts occurrences of pattern in data.
Algorithm:
- Start from position 0
- Find each occurrence using IndexOfBytes
- Move past each match and continue searching
- 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 ¶
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:
- Loop through string character by character
- When '%' found, decode next 2 hex characters
- '+' is kept as literal (NOT converted to space)
- 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 ¶
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:
- Loop through string character by character
- When '%' found, decode next 2 hex characters
- When '+' found, convert to space
- 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 ¶
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 ¶
EncodeBytesAbove7F URL-encodes bytes with value > 0x7F.
Algorithm:
- Loop through input bytes
- If byte > 0x7F, encode as %XX
- 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 ¶
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:
- Loop through string byte by byte
- Encode unreserved characters as-is: A-Z, a-z, 0-9, -, _, ., ~
- 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 ¶
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 ¶
EncodeQueryValue encodes a string for safe use in URL query parameters. Uses loop-based encoding (NO REGEX, NO url.QueryEscape per requirements).
Algorithm:
- Loop through string byte by byte
- Encode unreserved characters as-is: A-Z, a-z, 0-9, -, _, ., ~
- Encode space as '+'
- 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 ¶
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
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
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 ¶
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:
- Check length first (fast path)
- Loop through each character
- Convert to lowercase and compare
func EqualsIgnoreCase ¶
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 ¶
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:
- Find header/body separator
- Extract headers from start to separator
- 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 ¶
ExtractBoundary extracts the boundary string from a Content-Type header value.
Algorithm:
- Search for "boundary=" in Content-Type header
- Extract everything after "boundary="
- Trim whitespace
- 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 ¶
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:
- Loop through bytes from startOffset to endOffset
- Find CRLF (\r\n) line terminators
- Extract line between terminators
- Skip empty lines
- Track offset of each non-empty line
- 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 ¶
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:
- Skip leading whitespace
- Skip HTTP method (until first space)
- Skip whitespace after method
- Extract URL (until next space)
- 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 ¶
FilterString keeps only characters in safeChars.
Algorithm:
- Loop through input characters
- Check if character is in safeChars
- 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 ¶
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:
- Search backwards for CRLF CRLF or LF LF
- Return offset BEFORE the separator (end of actual body content)
- 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 ¶
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:
- Search for CRLF CRLF sequence (0x0D 0x0A 0x0D 0x0A)
- If not found, search for LF LF sequence (0x0A 0x0A)
- If found, return offset AFTER the separator (body start position)
- Handle edge case: check last 2 bytes for LF LF if main loop didn't find it
- 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 ¶
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 ¶
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 ¶
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:
- Search for CRLFCRLF (\r\n\r\n) sequence
- Also search for LFLF (\n\n) sequence
- Return position after separator
- 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 ¶
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:
- Loop from hostStart until path/query/fragment delimiter
- Check for '/', '?', '#'
- 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 ¶
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 ¶
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:
- Loop from pathStart looking for '?' or '#'
- Return position of delimiter
- Return length if no delimiter found
Example:
url := []byte("/api/users?id=123#section")
end := FindPathEnd(url, 0)
// Returns 10 (position of '?')
func FindProtocolEnd ¶
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:
- Loop through bytes looking for "://" sequence
- Return position of ':' when found
- Return -1 if not found
Example:
url := []byte("http://example.com")
pos := FindProtocolEnd(url)
// Returns 4 (position of ':' in "http:")
func FindQueryEnd ¶
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:
- Loop from queryStart looking for '#'
- Also check for newline (byte 10) or space as terminators
- Return position of delimiter
- 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 ¶
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:
- Loop through byte array
- Return position when '?' (63) found
- 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 ¶
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:
- Loop through bytes looking for '?'
- When found, mark start position
- Continue looking for '#' or whitespace
- 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 ¶
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 ¶
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 ¶
GetAllHeaderValues returns all values for a header (for multi-value headers).
Algorithm:
- Extract all headers
- Loop through and collect all matching header values
- 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 ¶
GetBody extracts the body portion from an HTTP request. Provides direct body access.
Algorithm:
- Find body offset using FindBodyOffset
- 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 ¶
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 ¶
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 ¶
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 ¶
GetBodySize returns the size of the request body. Provides body size calculation.
Algorithm:
- Find body offset
- Calculate total length - offset
Parameters:
- request: HTTP request bytes
Returns:
- Body size in bytes
- Error if request malformed
Example:
size, _ := GetBodySize(request)
func GetBodyString ¶
GetBodyString extracts the body as a string. Provides string body access.
Algorithm:
- Get body bytes using GetBody
- Convert to string
Parameters:
- request: HTTP request bytes
Returns:
- Body as string
- Error if request malformed
Example:
bodyStr, _ := GetBodyString(request)
func GetContentType ¶
GetContentType extracts the Content-Type header value.
Algorithm:
- Call GetHeaderValue for "Content-Type"
Parameters:
- request: HTTP request bytes
Returns:
- Content-Type value
- Error if request malformed
Example:
contentType, _ := GetContentType(request)
func GetCookie ¶
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 ¶
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:
- Convert protocol to lowercase using loop
- Check for known protocols
- 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 ¶
GetExtension extracts file extension from request path.
Algorithm:
- Get path without query string
- Find last '.' in path
- Find last '/' in path
- If '.' exists and is after last '/', return extension (including '.')
- 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 ¶
GetHTTPVersion extracts the HTTP version from a request. Provides direct version access.
Algorithm:
- Extract first header line (request line)
- Parse using parseRequestLineString
- 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 ¶
GetHeaderOffsets returns [lineStart, valueStart, valueEnd] offsets for a header.
Algorithm:
- Search through headers line by line
- Find header with matching name (case-insensitive)
- 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)
- 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 ¶
GetHeaderValue extracts a header value directly from request bytes.
Algorithm:
- Extract headers using ExtractAllHeaders
- 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 ¶
GetHeaders extracts all headers as string (excluding request/status line).
Algorithm:
- Find end of first line (request/status line)
- Find body offset
- 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 ¶
GetHeadersByPrefix returns all headers matching a prefix. Useful for getting all X-* headers, etc.
Algorithm:
- Extract all headers
- Loop through and collect headers starting with prefix
- 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 ¶
GetHeadersBytes extracts all headers as bytes (excluding request/status line). Returns the raw header bytes between first line and body.
Algorithm:
- Find end of first line (request/status line)
- Find body offset (header/body separator)
- 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 ¶
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 ¶
GetHost extracts the Host header value.
Algorithm:
- Call GetHeaderValue for "Host"
Parameters:
- request: HTTP request bytes
Returns:
- Host value
- Error if request malformed
Example:
host, _ := GetHost(request)
func GetMatches ¶
GetMatches returns all [start, end] positions of pattern in data.
Algorithm:
- Start from position 0
- Find each occurrence using IndexOfBytes
- Record [start, end] for each match
- Continue until no more matches or limit reached
- 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 ¶
GetMethod extracts the HTTP method from a request. Provides direct method access.
Algorithm:
- Extract first header line (request line)
- Parse using parseRequestLineString
- 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 ¶
GetNestedResponse extracts nested HTTP response from body. Useful for HTTP request smuggling detection.
Algorithm:
- Find body offset of outer response
- Search for "HTTP/" in body (start of nested response)
- 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 ¶
GetParameter extracts a single parameter value by name and type. Provides direct parameter access without AnalyzeRequest.
Algorithm:
- Call AnalyzeRequest to get all parameters
- Find parameter by name and type
- 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 ¶
GetPath extracts the full path (including query string) from a request. Provides direct path access.
Algorithm:
- Extract first header line (request line)
- Parse using parseRequestLineString
- 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 ¶
GetPathOnly extracts the path without query string from a request. Provides path-only access.
Algorithm:
- Get full path using GetPath
- Find '?' character
- 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 ¶
GetQueryString extracts the query string portion from a request path. Provides direct query access.
Algorithm:
- Get full path using GetPath
- Find '?' character
- 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 ¶
GetRequestBuffer returns a pooled buffer of at least size n. Caller must call PutRequestBuffer when done.
func GetRequestSize ¶
GetRequestSize returns the total size of the request.
Parameters:
- request: HTTP request bytes
Returns:
- Request size in bytes
Example:
size := GetRequestSize(request)
func GetStartType ¶
GetStartType detects content type from response body start.
Algorithm:
- Find body offset
- Skip leading whitespace
- Check for known content patterns
- 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 ¶
GetStatusCode extracts status code from response (shortcut for AnalyzeResponse).
Algorithm:
- Find first space (after HTTP version)
- Find second space (after status code)
- 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 ¶
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:
- Extract path with query string from request line
- Extract Host header value
- Combine scheme + host + path
Parameters:
- scheme: URL scheme ("http" or "https")
- request: Raw HTTP request bytes
Returns:
- Full URL string (e.g., "https://example.com/api?id=123")
- Empty string if request is malformed
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 ¶
GetURLFromService constructs a complete URL from raw request bytes and Service. Extracts path from raw bytes using GetPath(), then combines with Service data.
Algorithm:
- Extract path with query string from raw request
- Get scheme, host, port from Service
- Construct URL string: scheme://host:port/path
- 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 ¶
GetURLParameter extracts a single URL parameter value by name. Provides direct parameter access without AnalyzeRequest.
Algorithm:
- Get query string
- Use ParseQueryString to parse into parameters
- Find parameter by name
- 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 ¶
GetURLParametersMap returns all URL parameters as a map. Provides map-based parameter access.
Algorithm:
- Get query string
- Parse using ParseQueryString
- 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 ¶
HasBody checks if the request contains a body. Provides body existence check.
Algorithm:
- Find body offset
- 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 ¶
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 ¶
HasHeader checks if a header exists in the request.
Algorithm:
- Get header value
- 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 ¶
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 ¶
HasURLEncodedBody checks if an HTTP request has a URL-encoded body. Examines Content-Type header for application/x-www-form-urlencoded.
Algorithm:
- Extract Content-Type header from headers list
- Parse MIME type (before semicolon)
- 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 ¶
HasURLParameter checks if a URL parameter exists. Provides parameter existence check.
Algorithm:
- Get URL parameter value
- 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 ¶
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:
- Loop through headers, skipping request/status line
- Parse each header as "Name: Value"
- Compare name case-insensitively
- 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 ¶
HexCharToValue converts a hexadecimal character to its numeric value. Uses switch/case logic (NO REGEX per requirements).
Algorithm:
- Check if character is '0'-'9': return value 0-9
- Check if character is 'a'-'f': return value 10-15
- Check if character is 'A'-'F': return value 10-15
- 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 ¶
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 ¶
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 ¶
IndexOfByte finds the first occurrence of a target byte starting from startOffset.
Algorithm:
- Loop from startOffset to end of array
- Compare each byte with target
- Return index of first match
- 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 ¶
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 ¶
IndexOfBytes finds the first occurrence of a target byte sequence starting from startOffset.
Algorithm:
- Loop from startOffset to end
- For each position, check if target sequence matches
- Use nested loop for multi-byte matching
- Return index of first match
- 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 ¶
IntToString converts integer to string using loops (NO strconv.Itoa).
Parameters:
- n: Integer to convert
Returns:
- String representation
func IsAbsoluteURL ¶
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 ¶
IsHTTP2 checks if request uses HTTP/2 protocol.
Algorithm:
- Find end of first line
- Check if line ends with "HTTP/2" or starts with pseudo-headers
- 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 ¶
IsMethodType checks if the request uses a specific HTTP method. Provides method checking.
Algorithm:
- Extract method using GetMethod
- 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
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 ¶
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 ¶
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 ¶
IsResponse checks if message is an HTTP response (starts with "HTTP/").
Algorithm:
- Check if message starts with "HTTP/" (case-insensitive)
- 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
NormalizeLineEndings ensures all line endings are CRLF.
Parameters:
- request: HTTP request bytes
Returns:
- Normalized request
Example:
normalized := NormalizeLineEndings(request)
func OriginFromURL ¶
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 ¶
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 ¶
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 ¶
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:
- Get Content-Type header value
- Parse MIME type (before semicolon)
- Parse parameters (after semicolon)
- 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 ¶
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:
- Find ':' separator using loop
- Extract host before ':'
- Extract port after ':' and parse as integer
- 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 ¶
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:
- Loop through each character
- Check if character is digit (0-9)
- Build result: result = result*10 + digit
- Return -1 if non-digit found
Example:
port := ParseInt("8080")
// Returns 8080
port := ParseInt("abc")
// Returns -1 (invalid)
func ParseParameter ¶
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:
- Find parameter name (case-insensitive)
- Skip past "name="
- Handle quoted values ("value")
- Handle unquoted values (until semicolon or end)
Example:
params := "boundary=----WebKit; charset=utf-8" value := ParseParameter(params, "boundary") // value = "----WebKit"
func PrependBody ¶
PrependBody prepends data to the existing request body. Provides body prepend operation.
Algorithm:
- Get current body
- Prepend new data
- 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
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 ¶
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 ¶
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 ¶
RemoveHeader removes a header from an HTTP message.
Algorithm:
- Extract all headers and body offset
- Filter out matching header (case-insensitive)
- 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 ¶
RemoveParameter removes a parameter from an HTTP request.
Algorithm:
- Analyze request to get all parameters
- Find parameter to remove by matching name and type
- Remove parameter from appropriate location (URL, body, cookie)
- 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 ¶
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 ¶
ReplaceHeader atomically replaces a header value (remove + add).
Algorithm:
- Remove existing header if present
- 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 ¶
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 ¶
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 ¶
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 ¶
SetBody replaces the body of an HTTP request. Provides direct body modification.
Algorithm:
- Extract headers
- Build new request with headers + new body
- 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 ¶
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 ¶
SetBodyString replaces the body with a string value. Provides string body modification.
Algorithm:
- Convert string to bytes
- 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 ¶
SetContentType sets the Content-Type header.
Algorithm:
- 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 ¶
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 ¶
SetMethod replaces the HTTP method in a request. Provides direct method modification.
Algorithm:
- Extract all headers and body
- Parse current request line
- Build new request line with new method
- 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 ¶
SetPath replaces the full path (including query string) in a request. Provides direct path modification.
Algorithm:
- Extract all headers and body
- Parse current request line
- Build new request line with new path
- 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 ¶
SetPathOnly replaces the path portion while preserving the query string. Provides path-only modification.
Algorithm:
- Get current full path
- Extract query string if present
- Combine new path with existing query
- 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 ¶
SetQueryString replaces the query string in a request. Provides direct query modification.
Algorithm:
- Get current path without query using GetPathOnly
- Append new query string with '?'
- 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 ¶
SetURLParametersMap replaces all URL parameters with those from a map. Provides map-based parameter setting.
Algorithm:
- Build query string from map
- URL-encode each name and value
- Join with '&'
- 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 ¶
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 ¶
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 ¶
StartsWithIgnoreCase checks if string starts with prefix (case-insensitive). Helper function for header matching.
func StringToBytes ¶
StringToBytes converts a string to byte array.
Algorithm:
- Create byte array with length = string length
- Loop through each character
- Cast each char to byte
- 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 ¶
StripTrailingData removes any data after the body.
Parameters:
- request: HTTP request bytes
Returns:
- Request without trailing data
Example:
cleaned := StripTrailingData(request)
func SwitchMethod ¶
SwitchMethod changes the request to use a different HTTP method. Generalized method switching (beyond GET<->POST).
Algorithm:
- Use SetMethod to change method
- If switching to GET and has body, optionally move params to URL
- 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 ¶
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 ¶
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 ¶
ToLowerString converts a string to lowercase using loops (no strings.ToLower).
Parameters:
- s: String to convert
Returns:
- Lowercase version of string
Algorithm:
- Create result buffer
- Loop through each character
- Convert uppercase to lowercase using ToLower
- Return result string
Example:
lower := ToLowerString("HTTP")
// Returns "http"
func ToggleRequestMethod ¶
ToggleRequestMethod toggles between GET and POST methods.
Algorithm:
- Parse request to get current method and parameters
- Determine new method: GET→POST or POST→GET
- If current method is NOT GET: - Convert BODY params to URL params - URL-encode parameter values
- If current method IS GET: - Convert URL params to BODY params
- 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 ¶
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:
- Find first non-whitespace character (from start)
- Find last non-whitespace character (from end)
- Return substring
func UpdateContentLength ¶
UpdateContentLength updates or adds Content-Length header.
Algorithm:
- Find body offset with strict CRLF CRLF check
- If no separator, return unchanged
- Extract headers
- Calculate body length
- If body exists OR Content-Length header exists, update/add it
- 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 ¶
UpdateParameter updates a parameter's value in an HTTP request.
Algorithm:
- Remove parameter from request
- Add parameter with new value
- 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 ¶
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 ¶
UrlDecodeBytes decodes a URL-encoded byte array.
Algorithm:
- Loop through each byte
- Convert '+' to space (byte 32)
- Decode %XX hex sequences
- Handle %uXXXX unicode sequences (4 hex digits)
- 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 ¶
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 ¶
UrlEncodeBytes encodes byte array for safe use in URLs.
Algorithm:
- Loop through each byte
- Encode special characters (#, %, &, +, :, ;, =, ?, @) as %XX
- Encode space as '+'
- 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 ¶
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 ¶
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 ¶
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.
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
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:
- Check if body exists
- Parse Content-Type header
- 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 ¶
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 ¶
func (e *EncodedInsertionPoint) Type() InsertionPointType
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 ¶
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.
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 ¶
func (h *HeaderInsertionPoint) Type() InsertionPointType
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 ¶
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) Offset2 ¶
Offset2 returns the second offset value (third component). Returns 0 if not present.
type HttpHeader ¶
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 ¶
func (h *HttpRequestResponse) Clone() *HttpRequestResponse
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:
- Call AnalyzeRequest() to extract all parameters
- Create ParameterInsertionPoint for each parameter
- If includeNested == true: a. Call DiscoverNestedInsertionPoints() with all parameters b. Append discovered nested IPs to results
- 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.
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: \" → "
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:
- Apply encoding to child parameter (innermost) → get encoded1
- Apply encoding to parent parameter with encoded1 as input → get encoded2
- 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 ¶
func (n *NestedInsertionPoint) Type() InsertionPointType
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.
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 ¶
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 ¶
ExtractBodyParameters extracts all body parameters from a complete HTTP request. Automatically detects body offset and parses URL-encoded parameters.
Algorithm:
- Find header/body separator (FindBodyOffset)
- Validate Content-Type is application/x-www-form-urlencoded
- 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 ¶
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:
- Extract URL portion from HTTP request (between method and HTTP version)
- Find query string boundaries within the URL
- Parse parameters using parseURLEncodedParameters
- 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 ¶
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 ¶
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 ¶
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 ¶
NewBodyParam creates a new URL-encoded body parameter.
func NewCookieParam ¶
NewCookieParam creates a new cookie parameter.
func NewJSONParam ¶
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 ¶
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 ¶
NewPathFilenameParam creates a new REST path filename parameter.
func NewPathFolderParam ¶
NewPathFolderParam creates a new REST path folder parameter.
func NewURLParam ¶
NewURLParam creates a new URL query parameter.
func NewXMLParam ¶
NewXMLParam creates a new XML element parameter.
func ParseJSONBody ¶
ParseJSONBody parses application/json body and extracts parameters.
Algorithm:
- Create parser instance with data range
- Call parseValue to start parsing from root
- 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 ¶
ParseMultipartBody parses multipart/form-data body and extracts parameters. This is the main entry point for multipart parsing.
Algorithm:
- Extract boundary from Content-Type header
- Construct boundary markers: "--boundary" for parts, "--boundary--" for end
- Find all boundary positions using loop-based search
- 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
- 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 ¶
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:
- Find Content-Type header in request
- Extract boundary from Content-Type
- Find body offset
- 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 ¶
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:
- Skip HTTP method
- Skip spaces after method
- 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
- 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 ¶
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:
- Validate input
- Call parseURLEncodedParameters directly on entire input
- 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 ¶
ParseQueryString extracts query string parameters from a URL byte array. This function parses URL query parameters in the format: ?key1=value1&key2=value2
Algorithm:
- Find query string boundaries (after '?' and before '#' or end)
- Loop through query string character by character
- Parse each parameter as name=value pair separated by '&'
- Handle edge cases: empty values, no values, trailing separators
- 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§ion=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 ¶
ParseURLEncodedBody parses application/x-www-form-urlencoded body parameters. This is the primary function for extracting parameters from POST body data.
Algorithm:
- Initialize position markers: pos, nameStart, nameEnd, valueStart, valueEnd
- Loop through body bytes character by character
- Skip leading CRLF/LF at parameter start
- Find '=' character to separate name from value
- Find '&' character to separate parameters
- Handle edge cases: whitespace/control characters
- Parse value portion after '='
- Create Parameter object with type ParamBody
- 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 ¶
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 ¶
ParseXMLBody parses XML body and extracts parameters.
Algorithm:
- Parse XML structure using iterative parser
- Walk through XML elements maintaining current tag context
- 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
- Build hierarchical parameter names (e.g., "root.user.name")
- 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 ¶
CanURLEncode returns whether this parameter type supports URL encoding.
func (*Param) HasNameOffsets ¶
HasNameOffsets returns whether this parameter has valid name offsets.
func (*Param) HasOffsets ¶
HasOffsets returns whether this parameter has valid byte offsets. Some parameters (like path segments) may not have name offsets.
func (*Param) IsPathParameter ¶
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) RequiresContentLengthUpdate ¶
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) ValueStart ¶
ValueStart returns the byte offset where the value starts in the request.
func (*Param) WithAdjustedOffsets ¶
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 ¶
WithMetadata returns a new Param with the metadata changed.
func (*Param) WithOffsets ¶
WithOffsets returns a new Param with adjusted offsets. Useful when embedding a parameter in a larger context.
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 ¶
CanURLEncode returns whether this parameter type supports URL encoding.
func (ParamType) IsPathParam ¶
IsPathParam returns whether this parameter is a REST-style path parameter.
func (ParamType) RequiresContentLengthUpdate ¶
RequiresContentLengthUpdate returns whether modifying this parameter requires updating the Content-Length header.
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 ¶
func (pd *ParameterDescriptor) Type() InsertionPointType
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 ¶
func (p *ParameterInsertionPoint) Type() InsertionPointType
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 ¶
ParseURL parses a URL byte slice into components. Main URL parsing function that handles both absolute and relative URLs.
Parameters:
- urlBytes: URL as byte slice (e.g., "http://example.com:8080/path?query#frag")
Returns:
- ParsedURL with all components extracted
- error if URL is malformed
Algorithm:
- Parse protocol (http:// or https://)
- Parse host and port
- Parse path
- Parse query string (after ?)
- 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) PathWithQuery ¶
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 ¶
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 ¶
func (w *PayloadWrapper) Apply(insertionPoint BaseInsertionPointInterface) EncodedRequest
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:
- Find body offset
- Extract headers
- Parse status line for status code
- Extract Content-Type for StatedMimeType
- Infer MIME type from response body
- 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 ¶
NewService creates a new Service instance from protocol string.
func NewServiceSecure ¶
NewServiceSecure creates a new Service instance from boolean flag.
func ParseService ¶
ParseService parses a URL string and creates Service.
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.
Source Files
¶
- byte_search.go
- byte_utils.go
- carried_session.go
- declared_body.go
- doc.go
- encoded_request.go
- encoder.go
- encoding.go
- header.go
- header_parser.go
- headers.go
- helpers.go
- hex_builder.go
- hex_value.go
- http_request.go
- http_request_response.go
- http_response.go
- insertion_point.go
- insertion_point_header.go
- insertion_point_impl.go
- insertion_point_size.go
- json_parser.go
- multipart_parser.go
- nested_discovery.go
- param.go
- param_descriptor.go
- param_type.go
- parameter_format.go
- path_parser.go
- payload_wrapper.go
- query_parser.go
- redirect_status.go
- request_analyzer.go
- request_builder_cookies.go
- request_builder_core.go
- request_builder_headers.go
- request_builder_params.go
- request_builder_request.go
- request_builder_utils.go
- request_info.go
- response_analyzer.go
- service.go
- string_utils.go
- target_scheme.go
- url_parser.go
- urlencoded_parser.go
- useragent.go
- xml_parser.go