// Package parser fournit les parseurs pour les protocoles HTTP/1.x, HTTP/2 et TLS. package parser import ( "bytes" "strings" ) // HTTP1Request représente une requête HTTP/1.x parsée depuis le flux déchiffré. type HTTP1Request struct { Method string // méthode HTTP (GET, POST, …) Path string // chemin (sans query string) Query string // query string (sans le '?') Protocol string // "HTTP/1.0" ou "HTTP/1.1" Headers []string // noms des en-têtes dans l'ordre exact d'arrivée HeaderSig string // signature : noms joints par ";" HeaderKV map[string]string // valeurs des en-têtes clés (Host, User-Agent, etc.) } // HTTP1Response représente le début d'une réponse HTTP/1.x (status line). type HTTP1Response struct { StatusCode int } // knownMethods est la liste des méthodes HTTP/1.x reconnues. var knownMethods = []string{ "GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH", "CONNECT", "TRACE", } // capturedHeaders est la liste des en-têtes dont on capture la valeur. var capturedHeaders = []string{ "Host", "User-Agent", "Accept", "Accept-Encoding", "Accept-Language", "Content-Type", "X-Request-Id", "X-Trace-Id", "X-Forwarded-For", "Sec-CH-UA", "Sec-CH-UA-Mobile", "Sec-CH-UA-Platform", "Sec-Fetch-Dest", "Sec-Fetch-Mode", "Sec-Fetch-Site", } // IsHTTP1Request retourne true si les premiers octets ressemblent à une // requête HTTP/1.x (commence par une méthode reconnue suivi d'un espace). func IsHTTP1Request(data []byte) bool { for _, m := range knownMethods { if bytes.HasPrefix(data, []byte(m+" ")) { return true } } return false } // IsHTTP1Response retourne true si les premiers octets ressemblent à une // réponse HTTP/1.x ("HTTP/1."). func IsHTTP1Response(data []byte) bool { return bytes.HasPrefix(data, []byte("HTTP/1.")) } // ParseHTTP1Request extrait les champs d'une requête HTTP/1.x depuis un buffer brut. // Retourne nil sans erreur si le buffer ne contient pas de requête complète. func ParseHTTP1Request(data []byte) *HTTP1Request { // Localiser la fin de la request-line + headers (double CRLF) headerEnd := bytes.Index(data, []byte("\r\n\r\n")) if headerEnd < 0 { headerEnd = len(data) } text := string(data[:headerEnd]) lines := strings.Split(text, "\r\n") if len(lines) == 0 { return nil } // Parser la request-line : "METHOD path HTTP/x.y" requestLine := lines[0] parts := strings.SplitN(requestLine, " ", 3) if len(parts) < 2 { return nil } method := parts[0] rawPath := parts[1] protocol := "HTTP/1.1" if len(parts) == 3 { protocol = parts[2] } // Valider la méthode validMethod := false for _, m := range knownMethods { if method == m { validMethod = true break } } if !validMethod { return nil } // Séparer path et query string path := rawPath query := "" if idx := strings.Index(rawPath, "?"); idx >= 0 { path = rawPath[:idx] query = rawPath[idx+1:] } // Extraire les noms d'en-têtes dans l'ordre + capturer les valeurs clés headers := make([]string, 0, len(lines)-1) headerKV := make(map[string]string, len(capturedHeaders)) for _, line := range lines[1:] { if line == "" { break } if colon := strings.Index(line, ":"); colon > 0 { name := strings.TrimSpace(line[:colon]) if name != "" { headers = append(headers, name) // Capturer la valeur si c'est un header d'intérêt for _, key := range capturedHeaders { if strings.EqualFold(name, key) { headerKV[key] = strings.TrimSpace(line[colon+1:]) break } } } } } sig := strings.Join(headers, ";") return &HTTP1Request{ Method: method, Path: path, Query: query, Protocol: protocol, Headers: headers, HeaderSig: sig, HeaderKV: headerKV, } } // ParseHTTP1Response extrait le code de statut d'une réponse HTTP/1.x. // Retourne nil si le buffer n'est pas une réponse HTTP/1.x reconnaissable. func ParseHTTP1Response(data []byte) *HTTP1Response { if !IsHTTP1Response(data) { return nil } // Status-line : "HTTP/1.1 200 OK\r\n..." line := data if idx := bytes.IndexByte(data, '\n'); idx >= 0 { line = data[:idx] } parts := strings.SplitN(strings.TrimRight(string(line), "\r\n"), " ", 3) if len(parts) < 2 { return nil } code := 0 for _, c := range parts[1] { if c < '0' || c > '9' { break } code = code*10 + int(c-'0') } if code < 100 || code > 599 { return nil } return &HTTP1Response{StatusCode: code} }