• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

llnl / dftracer-utils / 27052412546

06 Jun 2026 04:20AM UTC coverage: 50.862% (+1.0%) from 49.905%
27052412546

Pull #73

github

web-flow
Merge 734572730 into 88a3c8457
Pull Request #73: add portable dependencies wheel support

31801 of 79859 branches covered (39.82%)

Branch coverage included in aggregate %.

32491 of 46545 relevant lines covered (69.81%)

9947.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.01
/src/dftracer/utils/server/http_parser.cpp
1
// HTTP/1.1 request parser.
2
//
3
// Inspired by picohttpparser by Kazuho Oku, Tokuhiro Matsuno,
4
// Daisuke Murase, Shigeo Mitsunari (MIT license).
5
// https://github.com/h2o/picohttpparser
6
//
7
// Simplified C++ rewrite: only parses HTTP/1.x requests (not
8
// responses or chunked encoding). Zero-copy — all string_views
9
// point into the caller's buffer.
10

11
#include <dftracer/utils/server/http_parser.h>
12

13
#include <cctype>
14
#include <cstddef>
15

16
namespace dftracer::utils::server::parser {
17

18
namespace {
19

20
// Characters valid in an HTTP token (RFC 9110 Section 5.6.2).
21
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" /
22
//         "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
23
constexpr bool is_token_char(unsigned char c) {
621✔
24
    // clang-format off
25
    constexpr bool table[256] = {
621✔
26
        // 0-31: control
27
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
28
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
29
        // 32-47: SP ! " # $ % & ' ( ) * + , - . /
30
        0,1,0,1,1,1,1,1, 0,0,1,1,0,1,1,0,
31
        // 48-63: 0-9 : ; < = > ?
32
        1,1,1,1,1,1,1,1, 1,1,0,0,0,0,0,0,
33
        // 64-79: @ A-O
34
        0,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
35
        // 80-95: P-Z [ \ ] ^ _
36
        1,1,1,1,1,1,1,1, 1,1,1,0,0,0,1,1,
37
        // 96-111: ` a-o
38
        1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
39
        // 112-127: p-z { | } ~ DEL
40
        1,1,1,1,1,1,1,1, 1,1,1,0,1,0,1,0,
41
        // 128-255: high bytes
42
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
43
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
44
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
45
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
46
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
47
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
48
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
49
        0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
50
    };
51
    // clang-format on
52
    return table[c];
621✔
53
}
54

55
constexpr bool is_printable(unsigned char c) { return c >= 0x20 && c != 0x7F; }
594!
56

57
struct Cursor {
58
    const char* buf;
59
    const char* end;
60

61
    bool eof() const { return buf >= end; }
3,050✔
62
    char peek() const { return *buf; }
4,822✔
63
    void advance() { ++buf; }
2,719✔
64

65
    // Advance past a run of spaces.
66
    void skip_spaces() {
76✔
67
        while (!eof() && peek() == ' ') advance();
76!
68
    }
76✔
69
};
70

71
// Read a token (method, header name) up to a delimiter.
72
// Returns false on EOF or bad character.
73
static bool read_token(Cursor& c, const char*& start, std::size_t& len,
109✔
74
                       char delim) {
75
    start = c.buf;
109✔
76
    while (!c.eof()) {
729!
77
        if (c.peek() == delim) {
729✔
78
            len = static_cast<std::size_t>(c.buf - start);
108✔
79
            return len > 0;
108✔
80
        }
81
        if (!is_token_char(static_cast<unsigned char>(c.peek()))) return false;
621✔
82
        c.advance();
620✔
83
    }
84
    return false;  // EOF before delimiter
×
85
}
109✔
86

87
// Read the request-target (path) up to a space.
88
static bool read_request_target(Cursor& c, const char*& start,
39✔
89
                                std::size_t& len) {
90
    start = c.buf;
39✔
91
    while (!c.eof()) {
1,047!
92
        if (c.peek() == ' ') {
1,047✔
93
            len = static_cast<std::size_t>(c.buf - start);
37✔
94
            return len > 0;
37✔
95
        }
96
        auto ch = static_cast<unsigned char>(c.peek());
1,010✔
97
        if (ch < 0x21 || ch == 0x7F) return false;
1,010!
98
        c.advance();
1,008✔
99
    }
100
    return false;
×
101
}
39✔
102

103
// Read a header value up to CRLF or LF.
104
// Returns false on EOF, sets start/len.
105
static bool read_header_value(Cursor& c, const char*& start, std::size_t& len) {
69✔
106
    start = c.buf;
69✔
107
    while (!c.eof()) {
663!
108
        auto ch = static_cast<unsigned char>(c.peek());
663✔
109
        if (ch == '\r') {
663✔
110
            len = static_cast<std::size_t>(c.buf - start);
68✔
111
            c.advance();
68✔
112
            if (c.eof() || c.peek() != '\n') return false;
68!
113
            c.advance();
68✔
114
            // Trim trailing whitespace
115
            while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t'))
139!
116
                --len;
3✔
117
            return true;
68✔
118
        }
119
        if (ch == '\n') {
595✔
120
            len = static_cast<std::size_t>(c.buf - start);
1✔
121
            c.advance();
1✔
122
            while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t'))
2!
123
                --len;
×
124
            return true;
1✔
125
        }
126
        // Allow printable ASCII + HT (0x09)
127
        if (ch != '\t' && !is_printable(ch)) return false;
594!
128
        c.advance();
594✔
129
    }
130
    return false;
×
131
}
69✔
132

133
// Consume CRLF or bare LF.  Returns false if neither found.
134
static bool consume_eol(Cursor& c) {
72✔
135
    if (c.eof()) return false;
72!
136
    if (c.peek() == '\r') {
72✔
137
        c.advance();
70✔
138
        if (c.eof() || c.peek() != '\n') return false;
70!
139
        c.advance();
70✔
140
        return true;
70✔
141
    }
142
    if (c.peek() == '\n') {
2!
143
        c.advance();
2✔
144
        return true;
2✔
145
    }
146
    return false;
×
147
}
72✔
148

149
// Check whether the buffer contains a complete request (double CRLF).
150
static bool has_complete_request(const char* buf, std::size_t len) {
44✔
151
    if (len < 4) return false;
44✔
152
    // Scan for \r\n\r\n or \n\n
153
    for (std::size_t i = 0; i + 1 < len; ++i) {
3,040✔
154
        if (buf[i] == '\n') {
3,037✔
155
            if (buf[i + 1] == '\n') return true;
111✔
156
            if (i + 2 < len && buf[i + 1] == '\r' && buf[i + 2] == '\n')
110!
157
                return true;
39✔
158
        }
71✔
159
    }
2,997✔
160
    return false;
3✔
161
}
44✔
162

163
}  // namespace
164

165
int parse_request(const char* buf, std::size_t len, ParsedRequest& out) {
44✔
166
    // Quick completeness check to avoid partial parsing work.
167
    if (!has_complete_request(buf, len)) return -2;
44✔
168

169
    Cursor c{buf, buf + len};
40✔
170

171
    // Skip optional leading CRLF (some clients send it after POST body).
172
    if (!c.eof() && c.peek() == '\r') {
40!
173
        c.advance();
1✔
174
        if (c.eof() || c.peek() != '\n') return -1;
1!
175
        c.advance();
1✔
176
    } else if (!c.eof() && c.peek() == '\n') {
40!
177
        c.advance();
×
178
    }
×
179

180
    // Method
181
    const char* method_start = nullptr;
40✔
182
    std::size_t method_len = 0;
40✔
183
    if (!read_token(c, method_start, method_len, ' ')) return -1;
40✔
184
    c.advance();  // consume space
39✔
185
    c.skip_spaces();
39✔
186

187
    // Request-target (path)
188
    const char* path_start = nullptr;
39✔
189
    std::size_t path_len = 0;
39✔
190
    if (!read_request_target(c, path_start, path_len)) return -1;
39✔
191
    c.advance();  // consume space
37✔
192
    c.skip_spaces();
37✔
193

194
    // HTTP version: "HTTP/1.X"
195
    if (c.end - c.buf < 8) return -1;
37!
196
    if (c.buf[0] != 'H' || c.buf[1] != 'T' || c.buf[2] != 'T' ||
73!
197
        c.buf[3] != 'P' || c.buf[4] != '/' || c.buf[5] != '1' ||
36!
198
        c.buf[6] != '.')
36✔
199
        return -1;
1✔
200
    char ver_digit = c.buf[7];
36✔
201
    if (ver_digit < '0' || ver_digit > '9') return -1;
36!
202
    int minor_ver = ver_digit - '0';
36✔
203
    c.buf += 8;
36✔
204

205
    if (!consume_eol(c)) return -1;
36!
206

207
    // Headers
208
    out.method = std::string_view(method_start, method_len);
36✔
209
    out.path = std::string_view(path_start, path_len);
36✔
210
    out.minor_version = minor_ver;
36✔
211
    out.headers.clear();
36✔
212

213
    constexpr std::size_t MAX_HEADERS = 64;
36✔
214

215
    while (!c.eof()) {
105!
216
        // Empty line = end of headers
217
        if (c.peek() == '\r' || c.peek() == '\n') {
105✔
218
            if (!consume_eol(c)) return -1;
36!
219
            break;
36✔
220
        }
221

222
        if (out.headers.size() >= MAX_HEADERS) return -1;
69!
223

224
        // Header name
225
        const char* name_start = nullptr;
69✔
226
        std::size_t name_len = 0;
69✔
227
        if (!read_token(c, name_start, name_len, ':')) return -1;
69!
228
        c.advance();  // consume ':'
69✔
229

230
        // Skip OWS after colon
231
        while (!c.eof() && (c.peek() == ' ' || c.peek() == '\t')) c.advance();
209!
232

233
        // Header value
234
        const char* val_start = nullptr;
69✔
235
        std::size_t val_len = 0;
69✔
236
        if (!read_header_value(c, val_start, val_len)) return -1;
69!
237

238
        out.headers.push_back({std::string_view(name_start, name_len),
138✔
239
                               std::string_view(val_start, val_len)});
69✔
240
    }
241

242
    return static_cast<int>(c.buf - buf);
36✔
243
}
44✔
244

245
}  // namespace dftracer::utils::server::parser
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc