Coverage Report

Created: 2025-10-10 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libhtp/htp/htp_response_generic.c
Line
Count
Source
1
/***************************************************************************
2
 * Copyright (c) 2009-2010 Open Information Security Foundation
3
 * Copyright (c) 2010-2013 Qualys, Inc.
4
 * All rights reserved.
5
 * 
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are
8
 * met:
9
 * 
10
 * - Redistributions of source code must retain the above copyright
11
 *   notice, this list of conditions and the following disclaimer.
12
13
 * - Redistributions in binary form must reproduce the above copyright
14
 *   notice, this list of conditions and the following disclaimer in the
15
 *   documentation and/or other materials provided with the distribution.
16
17
 * - Neither the name of the Qualys, Inc. nor the names of its
18
 *   contributors may be used to endorse or promote products derived from
19
 *   this software without specific prior written permission.
20
 * 
21
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
 ***************************************************************************/
33
34
/**
35
 * @file
36
 * @author Ivan Ristic <ivanr@webkreator.com>
37
 */
38
39
#include "htp_config_auto.h"
40
41
#include "htp_private.h"
42
43
/**
44
 * Generic response line parser.
45
 * 
46
 * @param[in] connp
47
 * @return HTP status
48
 */
49
166k
htp_status_t htp_parse_response_line_generic(htp_connp_t *connp) {
50
166k
    htp_tx_t *tx = connp->out_tx;
51
166k
    unsigned char *data = bstr_ptr(tx->response_line);
52
166k
    size_t len = bstr_len(tx->response_line);
53
166k
    size_t pos = 0;
54
55
166k
    tx->response_protocol = NULL;
56
166k
    tx->response_protocol_number = HTP_PROTOCOL_INVALID;
57
166k
    tx->response_status = NULL;
58
166k
    tx->response_status_number = HTP_STATUS_INVALID;
59
166k
    tx->response_message = NULL;
60
61
    // Ignore whitespace at the beginning of the line.
62
167k
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;
63
64
166k
    size_t start = pos;
65
66
    // Find the end of the protocol string.
67
5.76M
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
68
166k
    if (pos - start == 0) return HTP_OK;
69
    
70
166k
    tx->response_protocol = bstr_dup_mem(data + start, pos - start);
71
166k
    if (tx->response_protocol == NULL) return HTP_ERROR;    
72
73
166k
    tx->response_protocol_number = htp_parse_protocol(tx->response_protocol);
74
75
    #ifdef HTP_DEBUG
76
    fprint_raw_data(stderr, "Response protocol", bstr_ptr(tx->response_protocol), bstr_len(tx->response_protocol));
77
    fprintf(stderr, "Response protocol number: %d\n", tx->response_protocol_number);
78
    #endif
79
80
    // Ignore whitespace after the response protocol.
81
339k
    while ((pos < len) && (htp_is_space(data[pos]))) pos++;
82
166k
    if (pos == len) return HTP_OK;
83
84
159k
    start = pos;
85
86
    // Find the next whitespace character.
87
4.03M
    while ((pos < len) && (!htp_is_space(data[pos]))) pos++;    
88
159k
    if (pos - start == 0) return HTP_OK;
89
    
90
159k
    tx->response_status = bstr_dup_mem(data + start, pos - start);
91
159k
    if (tx->response_status == NULL) return HTP_ERROR;
92
93
159k
    tx->response_status_number = htp_parse_status(tx->response_status);
94
95
    #ifdef HTP_DEBUG
96
    fprint_raw_data(stderr, "Response status (as text)", bstr_ptr(tx->response_status), bstr_len(tx->response_status));
97
    fprintf(stderr, "Response status number: %d\n", tx->response_status_number);
98
    #endif
99
100
    // Ignore whitespace that follows the status code.
101
293k
    while ((pos < len) && (isspace(data[pos]))) pos++;
102
159k
    if (pos == len) return HTP_OK;
103
104
    // Assume the message stretches until the end of the line.    
105
132k
    tx->response_message = bstr_dup_mem(data + pos, len - pos);
106
132k
    if (tx->response_message == NULL) return HTP_ERROR;    
107
108
    #ifdef HTP_DEBUG
109
    fprint_raw_data(stderr, "Response status message", bstr_ptr(tx->response_message), bstr_len(tx->response_message));
110
    #endif
111
112
132k
    return HTP_OK;
113
132k
}
114
115
/**
116
 * Generic response header parser.
117
 * 
118
 * @param[in] connp
119
 * @param[in] h
120
 * @param[in] data
121
 * @param[in] len
122
 * @return HTP status
123
 */
124
218k
htp_status_t htp_parse_response_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) {
125
218k
    size_t name_start, name_end;
126
218k
    size_t value_start, value_end;
127
218k
    size_t prev;
128
129
218k
    htp_chomp(data, &len);
130
131
218k
    name_start = 0;
132
133
    // Look for the first colon.
134
218k
    size_t colon_pos = 0;
135
7.70M
    while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;
136
137
218k
    if (colon_pos == len) {
138
        // Header line with a missing colon.
139
140
174k
        h->flags |= HTP_FIELD_UNPARSEABLE;
141
174k
        h->flags |= HTP_FIELD_INVALID;
142
143
174k
        if (!(connp->out_tx->flags & HTP_FIELD_UNPARSEABLE)) {
144
            // Only once per transaction.
145
6.26k
            connp->out_tx->flags |= HTP_FIELD_UNPARSEABLE;
146
6.26k
            connp->out_tx->flags |= HTP_FIELD_INVALID;
147
6.26k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: missing colon.");
148
6.26k
        }
149
       
150
        // Reset the position. We're going to treat this invalid header
151
        // as a header with an empty name. That will increase the probability
152
        // that the content will be inspected.
153
174k
        colon_pos = 0;
154
174k
        (void)colon_pos; // suppress scan-build warning
155
174k
        name_end = 0;
156
174k
        value_start = 0;
157
174k
    } else {
158
        // Header line with a colon.
159
        
160
43.9k
        if (colon_pos == 0) {
161
            // Empty header name.
162
163
3.63k
            h->flags |= HTP_FIELD_INVALID;
164
165
3.63k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
166
                // Only once per transaction.
167
631
                connp->out_tx->flags |= HTP_FIELD_INVALID;
168
631
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: empty name.");
169
631
            }
170
3.63k
        }
171
172
43.9k
        name_end = colon_pos;
173
174
        // Ignore unprintable after field-name.
175
43.9k
        prev = name_end;
176
45.8k
        while ((prev > name_start) && htp_is_space(data[prev - 1])) {
177
1.90k
            prev--;
178
1.90k
            name_end--;
179
180
1.90k
            h->flags |= HTP_FIELD_INVALID;
181
182
1.90k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
183
                // Only once per transaction.
184
361
                connp->out_tx->flags |= HTP_FIELD_INVALID;
185
361
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: LWS after name.");
186
361
            }
187
1.90k
        }
188
189
43.9k
        value_start = colon_pos + 1;
190
43.9k
    }
191
192
    // Header value.   
193
194
    // Ignore LWS before field-content.
195
222k
    while ((value_start < len) && (htp_is_lws(data[value_start]))) {
196
3.73k
        value_start++;
197
3.73k
    }
198
199
    // Look for the end of field-content.
200
218k
    value_end = len;    
201
    
202
    // Check that the header name is a token.
203
218k
    size_t i = name_start;
204
577k
    while (i < name_end) {
205
376k
        if (!htp_is_token(data[i])) {
206
17.7k
            h->flags |= HTP_FIELD_INVALID;
207
208
17.7k
            if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) {
209
2.97k
                connp->out_tx->flags |= HTP_FIELD_INVALID;
210
2.97k
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header name is not a token.");
211
2.97k
            }
212
213
17.7k
            break;
214
17.7k
        }
215
216
358k
        i++;
217
358k
    }
218
1.10M
    for (i = value_start; i < value_end; i++) {
219
896k
        if (data[i] == 0) {
220
13.9k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header value contains null.");
221
13.9k
            break;
222
13.9k
        }
223
896k
    }
224
    // Ignore LWS after field-content.
225
218k
    prev = value_end - 1;
226
220k
    while ((prev > value_start) && (htp_is_lws(data[prev]))) {
227
1.37k
        prev--;
228
1.37k
        value_end--;
229
1.37k
    }
230
231
    // Now extract the name and the value.
232
218k
    h->name = bstr_dup_mem(data + name_start, name_end - name_start);
233
218k
    h->value = bstr_dup_mem(data + value_start, value_end - value_start);
234
218k
    if ((h->name == NULL) || (h->value == NULL)) {
235
0
        bstr_free(h->name);
236
0
        bstr_free(h->value);
237
0
        return HTP_ERROR;
238
0
    }
239
240
218k
    return HTP_OK;
241
218k
}
242
243
/**
244
 * Generic response header line(s) processor, which assembles folded lines
245
 * into a single buffer before invoking the parsing function.
246
 * 
247
 * @param[in] connp
248
 * @param[in] data
249
 * @param[in] len
250
 * @return HTP status
251
 */
252
218k
htp_status_t htp_process_response_header_generic(htp_connp_t *connp, unsigned char *data, size_t len) {
253
    // Create a new header structure.
254
218k
    htp_header_t *h = calloc(1, sizeof (htp_header_t));
255
218k
    if (h == NULL) return HTP_ERROR;
256
257
218k
    if (htp_parse_response_header_generic(connp, h, data, len) != HTP_OK) {
258
0
        free(h);
259
0
        return HTP_ERROR;
260
0
    }
261
262
    #ifdef HTP_DEBUG
263
    fprint_bstr(stderr, "Header name", h->name);
264
    fprint_bstr(stderr, "Header value", h->value);
265
    #endif
266
267
    // Do we already have a header with the same name?
268
218k
    htp_header_t *h_existing = htp_table_get(connp->out_tx->response_headers, h->name);
269
218k
    if (h_existing != NULL) {
270
        // Keep track of repeated same-name headers.
271
166k
        if ((h_existing->flags & HTP_FIELD_REPEATED) == 0) {
272
            // This is the second occurence for this header.
273
5.05k
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Repetition for header");
274
161k
        } else {
275
            // For simplicity reasons, we count the repetitions of all headers
276
161k
            if (connp->out_tx->res_header_repetitions < HTP_MAX_HEADERS_REPETITIONS) {
277
36.9k
                connp->out_tx->res_header_repetitions++;
278
124k
            } else {
279
124k
                bstr_free(h->name);
280
124k
                bstr_free(h->value);
281
124k
                free(h);
282
124k
                return HTP_OK;
283
124k
            }
284
161k
        }
285
41.9k
        h_existing->flags |= HTP_FIELD_REPEATED;
286
287
        // Having multiple C-L headers is against the RFC but many
288
        // browsers ignore the subsequent headers if the values are the same.
289
41.9k
        if (bstr_cmp_c_nocase(h->name, "Content-Length") == 0) {
290
            // Don't use string comparison here because we want to
291
            // ignore small formatting differences.
292
293
0
            int64_t existing_cl, new_cl;
294
295
0
            existing_cl = htp_parse_content_length(h_existing->value, NULL);
296
0
            new_cl = htp_parse_content_length(h->value, NULL);
297
0
            if ((existing_cl == -1) || (new_cl == -1) || (existing_cl != new_cl)) {
298
                // Ambiguous response C-L value.
299
0
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Ambiguous response C-L value");
300
0
            }
301
302
            // Ignoring the new C-L header that has the same value as the previous ones.
303
41.9k
        } else {            
304
            // Add to the existing header.
305
306
41.9k
            bstr *new_value = bstr_expand(h_existing->value, bstr_len(h_existing->value) + 2 + bstr_len(h->value));
307
41.9k
            if (new_value == NULL) {
308
0
                bstr_free(h->name);
309
0
                bstr_free(h->value);
310
0
                free(h);
311
0
                return HTP_ERROR;
312
0
            }
313
314
41.9k
            h_existing->value = new_value;
315
41.9k
            bstr_add_mem_noex(h_existing->value, (unsigned char *) ", ", 2);
316
41.9k
            bstr_add_noex(h_existing->value, h->value);
317
41.9k
        }
318
319
        // The new header structure is no longer needed.
320
41.9k
        bstr_free(h->name);
321
41.9k
        bstr_free(h->value);
322
41.9k
        free(h);       
323
51.8k
    } else {
324
51.8k
        if (htp_table_size(connp->out_tx->response_headers) > connp->cfg->number_headers_limit) {
325
0
            if (!(connp->out_tx->flags & HTP_HEADERS_TOO_MANY)) {
326
0
                connp->out_tx->flags |= HTP_HEADERS_TOO_MANY;
327
0
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Too many response headers");
328
0
            }
329
0
            bstr_free(h->name);
330
0
            bstr_free(h->value);
331
0
            free(h);
332
0
            return HTP_ERROR;
333
0
        }
334
        // Add as a new header.
335
51.8k
        if (htp_table_add(connp->out_tx->response_headers, h->name, h) != HTP_OK) {
336
0
            bstr_free(h->name);
337
0
            bstr_free(h->value);
338
0
            free(h);
339
0
            return HTP_ERROR;
340
0
        }
341
51.8k
    }
342
   
343
93.8k
    return HTP_OK;
344
218k
}