extractHeaderOrderFromReader extracts HTTP header order from a bufio.Reader by peeking at the raw bytes before http.ReadRequest consumes them. This is needed for JA4H fingerprinting which requires header order preservation.
(b *bufio.Reader)
| 528 | // by peeking at the raw bytes before http.ReadRequest consumes them. |
| 529 | // This is needed for JA4H fingerprinting which requires header order preservation. |
| 530 | func extractHeaderOrderFromReader(b *bufio.Reader) (headerOrder []string, cookieFields []string, acceptLang string) { |
| 531 | // Try to peek enough bytes to see the headers |
| 532 | // HTTP headers typically end with \r\n\r\n |
| 533 | // We'll peek progressively larger amounts until we find the header end |
| 534 | |
| 535 | peekSizes := []int{1024, 4096, 8192, 16384, 32768} |
| 536 | var peeked []byte |
| 537 | |
| 538 | for _, size := range peekSizes { |
| 539 | data, err := b.Peek(size) |
| 540 | if err != nil && len(data) == 0 { |
| 541 | // Can't peek, return empty |
| 542 | return nil, nil, "" |
| 543 | } |
| 544 | peeked = data |
| 545 | |
| 546 | // Check if we have the complete headers (ends with \r\n\r\n or \n\n) |
| 547 | if bytes.Contains(peeked, []byte("\r\n\r\n")) || bytes.Contains(peeked, []byte("\n\n")) { |
| 548 | break |
| 549 | } |
| 550 | |
| 551 | // If we got less than requested, we've read all available data |
| 552 | if len(data) < size { |
| 553 | break |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | if len(peeked) == 0 { |
| 558 | return nil, nil, "" |
| 559 | } |
| 560 | |
| 561 | // Find the end of headers |
| 562 | headerEnd := bytes.Index(peeked, []byte("\r\n\r\n")) |
| 563 | if headerEnd == -1 { |
| 564 | headerEnd = bytes.Index(peeked, []byte("\n\n")) |
| 565 | if headerEnd == -1 { |
| 566 | headerEnd = len(peeked) |
| 567 | } |
| 568 | } |
| 569 | |
| 570 | headerBytes := peeked[:headerEnd] |
| 571 | lines := bytes.Split(headerBytes, []byte("\n")) |
| 572 | |
| 573 | // Skip the request line (first line) |
| 574 | for i := 1; i < len(lines); i++ { |
| 575 | line := bytes.TrimRight(lines[i], "\r") |
| 576 | if len(line) == 0 { |
| 577 | continue |
| 578 | } |
| 579 | |
| 580 | colonIdx := bytes.Index(line, []byte(":")) |
| 581 | if colonIdx <= 0 { |
| 582 | continue |
| 583 | } |
| 584 | |
| 585 | headerName := string(bytes.TrimSpace(line[:colonIdx])) |
| 586 | headerValue := string(bytes.TrimSpace(line[colonIdx+1:])) |
| 587 |
no test coverage detected