/src/pjsip/pjlib-util/src/pjlib-util/http_client.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
3 | | * |
4 | | * This program is free software; you can redistribute it and/or modify |
5 | | * it under the terms of the GNU General Public License as published by |
6 | | * the Free Software Foundation; either version 2 of the License, or |
7 | | * (at your option) any later version. |
8 | | * |
9 | | * This program is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU General Public License |
15 | | * along with this program; if not, write to the Free Software |
16 | | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
17 | | */ |
18 | | |
19 | | #include <pjlib-util/http_client.h> |
20 | | #include <pj/activesock.h> |
21 | | #include <pj/assert.h> |
22 | | #include <pj/ctype.h> |
23 | | #include <pj/errno.h> |
24 | | #include <pj/except.h> |
25 | | #include <pj/pool.h> |
26 | | #include <pj/string.h> |
27 | | #include <pj/timer.h> |
28 | | #include <pj/rand.h> |
29 | | #include <pjlib-util/base64.h> |
30 | | #include <pjlib-util/errno.h> |
31 | | #include <pjlib-util/md5.h> |
32 | | #include <pjlib-util/scanner.h> |
33 | | #include <pjlib-util/string.h> |
34 | | |
35 | | #define THIS_FILE "http_client.c" |
36 | | |
37 | | #if 0 |
38 | | /* Enable some tracing */ |
39 | | #define TRACE_(arg) PJ_LOG(3,arg) |
40 | | #else |
41 | | #define TRACE_(arg) |
42 | | #endif |
43 | | |
44 | 43 | #define NUM_PROTOCOL 2 |
45 | 0 | #define HTTP_1_0 "1.0" |
46 | | #define HTTP_1_1 "1.1" |
47 | 563 | #define CONTENT_LENGTH "Content-Length" |
48 | | /* Buffer size for sending/receiving messages. */ |
49 | 0 | #define BUF_SIZE 2048 |
50 | | /* Initial data buffer size to store the data in case content- |
51 | | * length is not specified in the server's response. |
52 | | */ |
53 | 0 | #define INITIAL_DATA_BUF_SIZE 2048 |
54 | 0 | #define INITIAL_POOL_SIZE 1024 |
55 | 0 | #define POOL_INCREMENT_SIZE 512 |
56 | | |
57 | | enum http_protocol |
58 | | { |
59 | | PROTOCOL_HTTP, |
60 | | PROTOCOL_HTTPS |
61 | | }; |
62 | | |
63 | | static const char *http_protocol_names[NUM_PROTOCOL] = |
64 | | { |
65 | | "HTTP", |
66 | | "HTTPS" |
67 | | }; |
68 | | |
69 | | static const unsigned int http_default_port[NUM_PROTOCOL] = |
70 | | { |
71 | | 80, |
72 | | 443 |
73 | | }; |
74 | | |
75 | | enum http_method |
76 | | { |
77 | | HTTP_GET, |
78 | | HTTP_PUT, |
79 | | HTTP_DELETE |
80 | | }; |
81 | | |
82 | | static const char *http_method_names[3] = |
83 | | { |
84 | | "GET", |
85 | | "PUT", |
86 | | "DELETE" |
87 | | }; |
88 | | |
89 | | enum http_state |
90 | | { |
91 | | IDLE, |
92 | | CONNECTING, |
93 | | SENDING_REQUEST, |
94 | | SENDING_REQUEST_BODY, |
95 | | REQUEST_SENT, |
96 | | READING_RESPONSE, |
97 | | READING_DATA, |
98 | | READING_COMPLETE, |
99 | | ABORTING, |
100 | | }; |
101 | | |
102 | | enum auth_state |
103 | | { |
104 | | AUTH_NONE, /* Not authenticating */ |
105 | | AUTH_RETRYING, /* New request with auth has been submitted */ |
106 | | AUTH_DONE /* Done retrying the request with auth. */ |
107 | | }; |
108 | | |
109 | | struct pj_http_req |
110 | | { |
111 | | pj_str_t url; /* Request URL */ |
112 | | pj_http_url hurl; /* Parsed request URL */ |
113 | | pj_sockaddr addr; /* The host's socket address */ |
114 | | pj_http_req_param param; /* HTTP request parameters */ |
115 | | pj_pool_t *pool; /* Pool to allocate memory from */ |
116 | | pj_timer_heap_t *timer; /* Timer for timeout management */ |
117 | | pj_ioqueue_t *ioqueue; /* Ioqueue to use */ |
118 | | pj_http_req_callback cb; /* Callbacks */ |
119 | | pj_activesock_t *asock; /* Active socket */ |
120 | | pj_status_t error; /* Error status */ |
121 | | pj_str_t buffer; /* Buffer to send/receive msgs */ |
122 | | enum http_state state; /* State of the HTTP request */ |
123 | | enum auth_state auth_state; /* Authentication state */ |
124 | | pj_timer_entry timer_entry;/* Timer entry */ |
125 | | pj_bool_t resolved; /* Whether URL's host is resolved */ |
126 | | pj_http_resp response; /* HTTP response */ |
127 | | pj_ioqueue_op_key_t op_key; |
128 | | struct tcp_state |
129 | | { |
130 | | /* Total data sent so far if the data is sent in segments (i.e. |
131 | | * if on_send_data() is not NULL and if param.reqdata.total_size > 0) |
132 | | */ |
133 | | pj_size_t tot_chunk_size; |
134 | | /* Size of data to be sent (in a single activesock operation).*/ |
135 | | pj_size_t send_size; |
136 | | /* Data size sent so far. */ |
137 | | pj_size_t current_send_size; |
138 | | /* Total data received so far. */ |
139 | | pj_size_t current_read_size; |
140 | | } tcp_state; |
141 | | }; |
142 | | |
143 | | /* Start sending the request */ |
144 | | static pj_status_t http_req_start_sending(pj_http_req *hreq); |
145 | | /* Start reading the response */ |
146 | | static pj_status_t http_req_start_reading(pj_http_req *hreq); |
147 | | /* End the request */ |
148 | | static pj_status_t http_req_end_request(pj_http_req *hreq); |
149 | | /* Parse the header data and populate the header fields with the result. */ |
150 | | static pj_status_t http_headers_parse(char *hdata, pj_size_t size, |
151 | | pj_http_headers *headers); |
152 | | /* Parse the response */ |
153 | | static pj_status_t http_response_parse(pj_pool_t *pool, |
154 | | pj_http_resp *response, |
155 | | void *data, pj_size_t size, |
156 | | pj_size_t *remainder); |
157 | | /* Restart the request with authentication */ |
158 | | static void restart_req_with_auth(pj_http_req *hreq); |
159 | | /* Parse authentication challenge */ |
160 | | static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, |
161 | | pj_http_auth_chal *chal); |
162 | | |
163 | | static pj_uint16_t get_http_default_port(const pj_str_t *protocol) |
164 | 42 | { |
165 | 42 | int i; |
166 | | |
167 | 43 | for (i = 0; i < NUM_PROTOCOL; i++) { |
168 | 43 | if (!pj_stricmp2(protocol, http_protocol_names[i])) { |
169 | 42 | return (pj_uint16_t)http_default_port[i]; |
170 | 42 | } |
171 | 43 | } |
172 | 0 | return 0; |
173 | 42 | } http_client.c:get_http_default_port Line | Count | Source | 164 | 42 | { | 165 | 42 | int i; | 166 | | | 167 | 43 | for (i = 0; i < NUM_PROTOCOL; i++) { | 168 | 43 | if (!pj_stricmp2(protocol, http_protocol_names[i])) { | 169 | 42 | return (pj_uint16_t)http_default_port[i]; | 170 | 42 | } | 171 | 43 | } | 172 | 0 | return 0; | 173 | 42 | } |
Unexecuted instantiation: fuzz-http.c:get_http_default_port |
174 | | |
175 | | static const char * get_protocol(const pj_str_t *protocol) |
176 | 0 | { |
177 | 0 | int i; |
178 | |
|
179 | 0 | for (i = 0; i < NUM_PROTOCOL; i++) { |
180 | 0 | if (!pj_stricmp2(protocol, http_protocol_names[i])) { |
181 | 0 | return http_protocol_names[i]; |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | /* Should not happen */ |
186 | 0 | pj_assert(0); |
187 | 0 | return NULL; |
188 | 0 | } Unexecuted instantiation: http_client.c:get_protocol Unexecuted instantiation: fuzz-http.c:get_protocol |
189 | | |
190 | | |
191 | | /* Syntax error handler for parser. */ |
192 | | static void on_syntax_error(pj_scanner *scanner) |
193 | 392 | { |
194 | 392 | PJ_UNUSED_ARG(scanner); |
195 | 392 | PJ_THROW(PJ_EINVAL); // syntax error |
196 | 392 | } http_client.c:on_syntax_error Line | Count | Source | 193 | 21 | { | 194 | 21 | PJ_UNUSED_ARG(scanner); | 195 | 21 | PJ_THROW(PJ_EINVAL); // syntax error | 196 | 21 | } |
fuzz-http.c:on_syntax_error Line | Count | Source | 193 | 371 | { | 194 | 371 | PJ_UNUSED_ARG(scanner); | 195 | 371 | PJ_THROW(PJ_EINVAL); // syntax error | 196 | 371 | } |
|
197 | | |
198 | | /* Callback when connection is established to the server */ |
199 | | static pj_bool_t http_on_connect(pj_activesock_t *asock, |
200 | | pj_status_t status) |
201 | 0 | { |
202 | 0 | pj_http_req *hreq = (pj_http_req*) pj_activesock_get_user_data(asock); |
203 | |
|
204 | 0 | if (hreq->state == ABORTING || hreq->state == IDLE) |
205 | 0 | return PJ_FALSE; |
206 | | |
207 | 0 | if (status != PJ_SUCCESS) { |
208 | 0 | hreq->error = status; |
209 | 0 | pj_http_req_cancel(hreq, PJ_TRUE); |
210 | 0 | return PJ_FALSE; |
211 | 0 | } |
212 | | |
213 | | /* OK, we are connected. Start sending the request */ |
214 | 0 | hreq->state = SENDING_REQUEST; |
215 | 0 | http_req_start_sending(hreq); |
216 | 0 | return PJ_TRUE; |
217 | 0 | } Unexecuted instantiation: http_client.c:http_on_connect Unexecuted instantiation: fuzz-http.c:http_on_connect |
218 | | |
219 | | static pj_bool_t http_on_data_sent(pj_activesock_t *asock, |
220 | | pj_ioqueue_op_key_t *op_key, |
221 | | pj_ssize_t sent) |
222 | 0 | { |
223 | 0 | pj_http_req *hreq = (pj_http_req*) pj_activesock_get_user_data(asock); |
224 | |
|
225 | 0 | PJ_UNUSED_ARG(op_key); |
226 | |
|
227 | 0 | if (hreq->state == ABORTING || hreq->state == IDLE) |
228 | 0 | return PJ_FALSE; |
229 | | |
230 | 0 | if (sent <= 0) { |
231 | 0 | hreq->error = (sent < 0 ? (pj_status_t)-sent : PJLIB_UTIL_EHTTPLOST); |
232 | 0 | pj_http_req_cancel(hreq, PJ_TRUE); |
233 | 0 | return PJ_FALSE; |
234 | 0 | } |
235 | | |
236 | 0 | hreq->tcp_state.current_send_size += sent; |
237 | 0 | TRACE_((THIS_FILE, "\nData sent: %d out of %d bytes", |
238 | 0 | hreq->tcp_state.current_send_size, hreq->tcp_state.send_size)); |
239 | 0 | if (hreq->tcp_state.current_send_size == hreq->tcp_state.send_size) { |
240 | | /* Find out whether there is a request body to send. */ |
241 | 0 | if (hreq->param.reqdata.total_size > 0 || |
242 | 0 | hreq->param.reqdata.size > 0) |
243 | 0 | { |
244 | 0 | if (hreq->state == SENDING_REQUEST) { |
245 | | /* Start sending the request body */ |
246 | 0 | hreq->state = SENDING_REQUEST_BODY; |
247 | 0 | hreq->tcp_state.tot_chunk_size = 0; |
248 | 0 | pj_assert(hreq->param.reqdata.total_size == 0 || |
249 | 0 | (hreq->param.reqdata.total_size > 0 && |
250 | 0 | hreq->param.reqdata.size == 0)); |
251 | 0 | } else { |
252 | | /* Continue sending the next chunk of the request body */ |
253 | 0 | hreq->tcp_state.tot_chunk_size += hreq->tcp_state.send_size; |
254 | 0 | if (hreq->tcp_state.tot_chunk_size == |
255 | 0 | hreq->param.reqdata.total_size || |
256 | 0 | hreq->param.reqdata.total_size == 0) |
257 | 0 | { |
258 | | /* Finish sending all the chunks, start reading |
259 | | * the response. |
260 | | */ |
261 | 0 | hreq->state = REQUEST_SENT; |
262 | 0 | http_req_start_reading(hreq); |
263 | 0 | return PJ_TRUE; |
264 | 0 | } |
265 | 0 | } |
266 | 0 | if (hreq->param.reqdata.total_size > 0 && |
267 | 0 | hreq->cb.on_send_data) |
268 | 0 | { |
269 | | /* Call the callback for the application to provide |
270 | | * the next chunk of data to be sent. |
271 | | */ |
272 | 0 | (*hreq->cb.on_send_data)(hreq, &hreq->param.reqdata.data, |
273 | 0 | &hreq->param.reqdata.size); |
274 | | /* Make sure the total data size given by the user does not |
275 | | * exceed what the user originally said. |
276 | | */ |
277 | 0 | pj_assert(hreq->tcp_state.tot_chunk_size + |
278 | 0 | hreq->param.reqdata.size <= |
279 | 0 | hreq->param.reqdata.total_size); |
280 | 0 | } |
281 | 0 | http_req_start_sending(hreq); |
282 | 0 | } else { |
283 | | /* No request body, proceed to reading the server's response. */ |
284 | 0 | hreq->state = REQUEST_SENT; |
285 | 0 | http_req_start_reading(hreq); |
286 | 0 | } |
287 | 0 | } |
288 | 0 | return PJ_TRUE; |
289 | 0 | } Unexecuted instantiation: http_client.c:http_on_data_sent Unexecuted instantiation: fuzz-http.c:http_on_data_sent |
290 | | |
291 | | static pj_bool_t http_on_data_read(pj_activesock_t *asock, |
292 | | void *data, |
293 | | pj_size_t size, |
294 | | pj_status_t status, |
295 | | pj_size_t *remainder) |
296 | 0 | { |
297 | 0 | pj_http_req *hreq = (pj_http_req*) pj_activesock_get_user_data(asock); |
298 | |
|
299 | 0 | TRACE_((THIS_FILE, "\nData received: %d bytes", size)); |
300 | |
|
301 | 0 | if (data == NULL) |
302 | 0 | return PJ_FALSE; |
303 | 0 | if (hreq->state == ABORTING || hreq->state == IDLE) |
304 | 0 | return PJ_FALSE; |
305 | | |
306 | 0 | if (hreq->state == READING_RESPONSE) { |
307 | 0 | pj_status_t st; |
308 | 0 | pj_size_t rem; |
309 | |
|
310 | 0 | if (status != PJ_SUCCESS && status != PJ_EPENDING) { |
311 | 0 | hreq->error = status; |
312 | 0 | pj_http_req_cancel(hreq, PJ_TRUE); |
313 | 0 | return PJ_FALSE; |
314 | 0 | } |
315 | | |
316 | | /* Parse the response. */ |
317 | 0 | st = http_response_parse(hreq->pool, &hreq->response, |
318 | 0 | data, size, &rem); |
319 | 0 | if (st == PJLIB_UTIL_EHTTPINCHDR) { |
320 | | /* If we already use up all our buffer and still |
321 | | * hasn't received the whole header, return error |
322 | | */ |
323 | 0 | if (size == BUF_SIZE) { |
324 | 0 | hreq->error = PJ_ETOOBIG; // response header size is too big |
325 | 0 | pj_http_req_cancel(hreq, PJ_TRUE); |
326 | 0 | return PJ_FALSE; |
327 | 0 | } |
328 | | /* Keep the data if we do not get the whole response header */ |
329 | 0 | if (remainder) *remainder = size; |
330 | 0 | } else { |
331 | 0 | hreq->state = READING_DATA; |
332 | 0 | if (st != PJ_SUCCESS) { |
333 | | /* Server replied with an invalid (or unknown) response |
334 | | * format. We'll just pass the whole (unparsed) response |
335 | | * to the user. |
336 | | */ |
337 | 0 | hreq->response.data = data; |
338 | 0 | hreq->response.size = size - rem; |
339 | 0 | } |
340 | | |
341 | | /* If code is 401 or 407, find and parse WWW-Authenticate or |
342 | | * Proxy-Authenticate header |
343 | | */ |
344 | 0 | if (hreq->response.status_code == 401 || |
345 | 0 | hreq->response.status_code == 407) |
346 | 0 | { |
347 | 0 | const pj_str_t STR_WWW_AUTH = { "WWW-Authenticate", 16 }; |
348 | 0 | const pj_str_t STR_PROXY_AUTH = { "Proxy-Authenticate", 18 }; |
349 | 0 | pj_http_resp *response = &hreq->response; |
350 | 0 | pj_http_headers *hdrs = &response->headers; |
351 | 0 | unsigned i; |
352 | |
|
353 | 0 | status = PJ_ENOTFOUND; |
354 | 0 | for (i = 0; i < hdrs->count; i++) { |
355 | 0 | if (!pj_stricmp(&hdrs->header[i].name, &STR_WWW_AUTH) || |
356 | 0 | !pj_stricmp(&hdrs->header[i].name, &STR_PROXY_AUTH)) |
357 | 0 | { |
358 | 0 | status = parse_auth_chal(hreq->pool, |
359 | 0 | &hdrs->header[i].value, |
360 | 0 | &response->auth_chal); |
361 | 0 | break; |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | | /* Check if we should perform authentication */ |
366 | 0 | if (status == PJ_SUCCESS && |
367 | 0 | hreq->auth_state == AUTH_NONE && |
368 | 0 | hreq->response.auth_chal.scheme.slen && |
369 | 0 | hreq->param.auth_cred.username.slen && |
370 | 0 | (hreq->param.auth_cred.scheme.slen == 0 || |
371 | 0 | !pj_stricmp(&hreq->response.auth_chal.scheme, |
372 | 0 | &hreq->param.auth_cred.scheme)) && |
373 | 0 | (hreq->param.auth_cred.realm.slen == 0 || |
374 | 0 | !pj_stricmp(&hreq->response.auth_chal.realm, |
375 | 0 | &hreq->param.auth_cred.realm)) |
376 | 0 | ) |
377 | 0 | { |
378 | | /* Yes, authentication is required and we have been |
379 | | * configured with credential. |
380 | | */ |
381 | 0 | restart_req_with_auth(hreq); |
382 | 0 | if (hreq->auth_state == AUTH_RETRYING) { |
383 | | /* We'll be resending the request with auth. This |
384 | | * connection has been closed. |
385 | | */ |
386 | 0 | return PJ_FALSE; |
387 | 0 | } |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | | /* We already received the response header, call the |
392 | | * appropriate callback. |
393 | | */ |
394 | 0 | if (hreq->cb.on_response) |
395 | 0 | (*hreq->cb.on_response)(hreq, &hreq->response); |
396 | 0 | hreq->response.data = NULL; |
397 | 0 | hreq->response.size = 0; |
398 | |
|
399 | 0 | if (rem > 0 || hreq->response.content_length == 0) |
400 | 0 | return http_on_data_read(asock, (rem == 0 ? NULL: |
401 | 0 | (char *)data + size - rem), |
402 | 0 | rem, PJ_SUCCESS, NULL); |
403 | 0 | } |
404 | | |
405 | 0 | return PJ_TRUE; |
406 | 0 | } |
407 | | |
408 | 0 | if (hreq->state != READING_DATA) |
409 | 0 | return PJ_FALSE; |
410 | 0 | if (hreq->cb.on_data_read) { |
411 | | /* If application wishes to receive the data once available, call |
412 | | * its callback. |
413 | | */ |
414 | 0 | if (size > 0) |
415 | 0 | (*hreq->cb.on_data_read)(hreq, data, size); |
416 | 0 | } else { |
417 | 0 | if (hreq->response.size == 0) { |
418 | | /* If we know the content length, allocate the data based |
419 | | * on that, otherwise we'll use initial buffer size and grow |
420 | | * it later if necessary. |
421 | | */ |
422 | 0 | hreq->response.size = (hreq->response.content_length == -1 ? |
423 | 0 | INITIAL_DATA_BUF_SIZE : |
424 | 0 | hreq->response.content_length); |
425 | 0 | hreq->response.data = pj_pool_alloc(hreq->pool, |
426 | 0 | hreq->response.size); |
427 | 0 | } |
428 | | |
429 | | /* If the size of data received exceeds its current size, |
430 | | * grow the buffer by a factor of 2. |
431 | | */ |
432 | 0 | if (hreq->tcp_state.current_read_size + size > |
433 | 0 | hreq->response.size) |
434 | 0 | { |
435 | 0 | void *olddata = hreq->response.data; |
436 | |
|
437 | 0 | hreq->response.data = pj_pool_alloc(hreq->pool, |
438 | 0 | hreq->response.size << 1); |
439 | 0 | pj_memcpy(hreq->response.data, olddata, hreq->response.size); |
440 | 0 | hreq->response.size <<= 1; |
441 | 0 | } |
442 | | |
443 | | /* Append the response data. */ |
444 | 0 | pj_memcpy((char *)hreq->response.data + |
445 | 0 | hreq->tcp_state.current_read_size, data, size); |
446 | 0 | } |
447 | 0 | hreq->tcp_state.current_read_size += size; |
448 | | |
449 | | /* If the total data received so far is equal to the content length |
450 | | * or if it's already EOF. |
451 | | */ |
452 | 0 | if ((hreq->response.content_length >=0 && |
453 | 0 | (pj_ssize_t)hreq->tcp_state.current_read_size >= |
454 | 0 | hreq->response.content_length) || |
455 | 0 | (status == PJ_EEOF && hreq->response.content_length == -1)) |
456 | 0 | { |
457 | | /* Finish reading */ |
458 | 0 | http_req_end_request(hreq); |
459 | 0 | hreq->response.size = hreq->tcp_state.current_read_size; |
460 | | |
461 | | /* HTTP request is completed, call the callback. */ |
462 | 0 | if (hreq->cb.on_complete) { |
463 | 0 | (*hreq->cb.on_complete)(hreq, PJ_SUCCESS, &hreq->response); |
464 | 0 | } |
465 | |
|
466 | 0 | return PJ_FALSE; |
467 | 0 | } |
468 | | |
469 | | /* Error status or premature EOF. */ |
470 | 0 | if ((status != PJ_SUCCESS && status != PJ_EPENDING && status != PJ_EEOF) |
471 | 0 | || (status == PJ_EEOF && hreq->response.content_length > -1)) |
472 | 0 | { |
473 | 0 | hreq->error = status; |
474 | 0 | pj_http_req_cancel(hreq, PJ_TRUE); |
475 | 0 | return PJ_FALSE; |
476 | 0 | } |
477 | | |
478 | 0 | return PJ_TRUE; |
479 | 0 | } Unexecuted instantiation: http_client.c:http_on_data_read Unexecuted instantiation: fuzz-http.c:http_on_data_read |
480 | | |
481 | | /* Callback to be called when query has timed out */ |
482 | | static void on_timeout( pj_timer_heap_t *timer_heap, |
483 | | struct pj_timer_entry *entry) |
484 | 0 | { |
485 | 0 | pj_http_req *hreq = (pj_http_req *) entry->user_data; |
486 | |
|
487 | 0 | PJ_UNUSED_ARG(timer_heap); |
488 | | |
489 | | /* Recheck that the request is still not completed, since there is a |
490 | | * slight possibility of race condition (timer elapsed while at the |
491 | | * same time response arrives). |
492 | | */ |
493 | 0 | if (hreq->state == READING_COMPLETE) { |
494 | | /* Yeah, we finish on time */ |
495 | 0 | return; |
496 | 0 | } |
497 | | |
498 | | /* Invalidate id. */ |
499 | 0 | hreq->timer_entry.id = 0; |
500 | | |
501 | | /* Request timed out. */ |
502 | 0 | hreq->error = PJ_ETIMEDOUT; |
503 | 0 | pj_http_req_cancel(hreq, PJ_TRUE); |
504 | 0 | } Unexecuted instantiation: http_client.c:on_timeout Unexecuted instantiation: fuzz-http.c:on_timeout |
505 | | |
506 | | /* Parse authentication challenge */ |
507 | | static pj_status_t parse_auth_chal(pj_pool_t *pool, pj_str_t *input, |
508 | | pj_http_auth_chal *chal) |
509 | 0 | { |
510 | 0 | pj_scanner scanner; |
511 | 0 | const pj_str_t REALM_STR = { "realm", 5}, |
512 | 0 | NONCE_STR = { "nonce", 5}, |
513 | 0 | ALGORITHM_STR = { "algorithm", 9 }, |
514 | 0 | STALE_STR = { "stale", 5}, |
515 | 0 | QOP_STR = { "qop", 3}, |
516 | 0 | OPAQUE_STR = { "opaque", 6}; |
517 | 0 | pj_status_t status = PJ_SUCCESS; |
518 | 0 | PJ_USE_EXCEPTION ; |
519 | |
|
520 | 0 | pj_scan_init(&scanner, input->ptr, input->slen, PJ_SCAN_AUTOSKIP_WS, |
521 | 0 | &on_syntax_error); |
522 | 0 | PJ_TRY { |
523 | | /* Get auth scheme */ |
524 | 0 | if (*scanner.curptr == '"') { |
525 | 0 | pj_scan_get_quote(&scanner, '"', '"', &chal->scheme); |
526 | 0 | chal->scheme.ptr++; |
527 | 0 | chal->scheme.slen -= 2; |
528 | 0 | } else { |
529 | 0 | pj_scan_get_until_chr(&scanner, " \t\r\n", &chal->scheme); |
530 | 0 | } |
531 | | |
532 | | /* Loop parsing all parameters */ |
533 | 0 | for (;;) { |
534 | 0 | const char *end_param = ", \t\r\n;"; |
535 | 0 | pj_str_t name, value; |
536 | | |
537 | | /* Get pair of parameter name and value */ |
538 | 0 | value.ptr = NULL; |
539 | 0 | value.slen = 0; |
540 | 0 | pj_scan_get_until_chr(&scanner, "=, \t\r\n", &name); |
541 | 0 | if (*scanner.curptr == '=') { |
542 | 0 | pj_scan_get_char(&scanner); |
543 | 0 | if (!pj_scan_is_eof(&scanner)) { |
544 | 0 | if (*scanner.curptr == '"' || *scanner.curptr == '\'') { |
545 | 0 | int quote_char = *scanner.curptr; |
546 | 0 | pj_scan_get_quote(&scanner, quote_char, quote_char, |
547 | 0 | &value); |
548 | 0 | value.ptr++; |
549 | 0 | value.slen -= 2; |
550 | 0 | } else if (!strchr(end_param, *scanner.curptr)) { |
551 | 0 | pj_scan_get_until_chr(&scanner, end_param, &value); |
552 | 0 | } |
553 | 0 | } |
554 | 0 | value = pj_str_unescape(pool, &value); |
555 | 0 | } |
556 | |
|
557 | 0 | if (!pj_stricmp(&name, &REALM_STR)) { |
558 | 0 | chal->realm = value; |
559 | |
|
560 | 0 | } else if (!pj_stricmp(&name, &NONCE_STR)) { |
561 | 0 | chal->nonce = value; |
562 | |
|
563 | 0 | } else if (!pj_stricmp(&name, &ALGORITHM_STR)) { |
564 | 0 | chal->algorithm = value; |
565 | |
|
566 | 0 | } else if (!pj_stricmp(&name, &OPAQUE_STR)) { |
567 | 0 | chal->opaque = value; |
568 | |
|
569 | 0 | } else if (!pj_stricmp(&name, &QOP_STR)) { |
570 | 0 | chal->qop = value; |
571 | |
|
572 | 0 | } else if (!pj_stricmp(&name, &STALE_STR)) { |
573 | 0 | chal->stale = value.slen && |
574 | 0 | (*value.ptr != '0') && |
575 | 0 | (*value.ptr != 'f') && |
576 | 0 | (*value.ptr != 'F'); |
577 | |
|
578 | 0 | } |
579 | | |
580 | | /* Eat comma */ |
581 | 0 | if (!pj_scan_is_eof(&scanner) && *scanner.curptr == ',') |
582 | 0 | pj_scan_get_char(&scanner); |
583 | 0 | else |
584 | 0 | break; |
585 | 0 | } |
586 | |
|
587 | 0 | } |
588 | 0 | PJ_CATCH_ANY { |
589 | 0 | status = PJ_GET_EXCEPTION(); |
590 | 0 | pj_bzero(chal, sizeof(*chal)); |
591 | 0 | TRACE_((THIS_FILE, "Error: parsing of auth header failed")); |
592 | 0 | } |
593 | 0 | PJ_END; |
594 | 0 | pj_scan_fini(&scanner); |
595 | 0 | return status; |
596 | 0 | } Unexecuted instantiation: http_client.c:parse_auth_chal Unexecuted instantiation: fuzz-http.c:parse_auth_chal |
597 | | |
598 | | /* The same as #pj_http_headers_add_elmt() with char * as |
599 | | * its parameters. |
600 | | */ |
601 | | PJ_DEF(pj_status_t) pj_http_headers_add_elmt2(pj_http_headers *headers, |
602 | | char *name, char *val) |
603 | 0 | { |
604 | 0 | pj_str_t f, v; |
605 | 0 | pj_cstr(&f, name); |
606 | 0 | pj_cstr(&v, val); |
607 | 0 | return pj_http_headers_add_elmt(headers, &f, &v); |
608 | 0 | } |
609 | | |
610 | | PJ_DEF(pj_status_t) pj_http_headers_add_elmt(pj_http_headers *headers, |
611 | | pj_str_t *name, |
612 | | pj_str_t *val) |
613 | 1.73k | { |
614 | 1.73k | PJ_ASSERT_RETURN(headers && name && val, PJ_FALSE); |
615 | 1.73k | if (headers->count >= PJ_HTTP_HEADER_SIZE) |
616 | 3 | return PJ_ETOOMANY; |
617 | 1.73k | pj_strassign(&headers->header[headers->count].name, name); |
618 | 1.73k | pj_strassign(&headers->header[headers->count++].value, val); |
619 | 1.73k | return PJ_SUCCESS; |
620 | 1.73k | } |
621 | | |
622 | | static pj_status_t http_response_parse(pj_pool_t *pool, |
623 | | pj_http_resp *response, |
624 | | void *data, pj_size_t size, |
625 | | pj_size_t *remainder) |
626 | 563 | { |
627 | 563 | pj_size_t i; |
628 | 563 | char *cptr; |
629 | 563 | char *end_status, *newdata; |
630 | 563 | pj_scanner scanner; |
631 | 563 | pj_str_t s; |
632 | 563 | const pj_str_t STR_CONTENT_LENGTH = { CONTENT_LENGTH, 14 }; |
633 | 563 | pj_status_t status; |
634 | | |
635 | 563 | PJ_USE_EXCEPTION; |
636 | | |
637 | 563 | PJ_ASSERT_RETURN(response, PJ_EINVAL); |
638 | 563 | if (size < 2) |
639 | 0 | return PJLIB_UTIL_EHTTPINCHDR; |
640 | | /* Detect whether we already receive the response's status-line |
641 | | * and its headers. We're looking for a pair of CRLFs. A pair of |
642 | | * LFs is also supported although it is not RFC standard. |
643 | | */ |
644 | 563 | cptr = (char *)data; |
645 | 65.3k | for (i = 1, cptr++; i < size; i++, cptr++) { |
646 | 65.2k | if (*cptr == '\n') { |
647 | 7.93k | if (*(cptr - 1) == '\n') |
648 | 420 | break; |
649 | 7.51k | if (*(cptr - 1) == '\r') { |
650 | 2.06k | if (i >= 3 && *(cptr - 2) == '\n' && *(cptr - 3) == '\r') |
651 | 120 | break; |
652 | 2.06k | } |
653 | 7.51k | } |
654 | 65.2k | } |
655 | 563 | if (i == size) |
656 | 23 | return PJLIB_UTIL_EHTTPINCHDR; |
657 | 540 | *remainder = size - 1 - i; |
658 | | |
659 | 540 | pj_bzero(response, sizeof(*response)); |
660 | 540 | response->content_length = -1; |
661 | | |
662 | 540 | newdata = (char*) pj_pool_alloc(pool, i); |
663 | 540 | pj_memcpy(newdata, data, i); |
664 | | |
665 | | /* Parse the status-line. */ |
666 | 540 | pj_scan_init(&scanner, newdata, i, 0, &on_syntax_error); |
667 | 540 | PJ_TRY { |
668 | 540 | pj_scan_get_until_ch(&scanner, ' ', &response->version); |
669 | 540 | pj_scan_advance_n(&scanner, 1, PJ_FALSE); |
670 | 540 | pj_scan_get_until_ch(&scanner, ' ', &s); |
671 | 540 | response->status_code = (pj_uint16_t)pj_strtoul(&s); |
672 | 540 | pj_scan_advance_n(&scanner, 1, PJ_FALSE); |
673 | 540 | pj_scan_get_until_ch(&scanner, '\n', &response->reason); |
674 | 540 | if (response->reason.ptr[response->reason.slen-1] == '\r') |
675 | 22 | response->reason.slen--; |
676 | 540 | } |
677 | 0 | PJ_CATCH_ANY { |
678 | 0 | pj_scan_fini(&scanner); |
679 | 0 | return PJ_GET_EXCEPTION(); |
680 | 0 | } |
681 | 540 | PJ_END; |
682 | | |
683 | 540 | end_status = scanner.curptr; |
684 | 540 | pj_scan_fini(&scanner); |
685 | | |
686 | | /* Parse the response headers. */ |
687 | 540 | size = (i < 2)? 0: i - 2 - (end_status - newdata); |
688 | 540 | if (size > 0) { |
689 | 502 | status = http_headers_parse(end_status + 1, size, |
690 | 502 | &response->headers); |
691 | 502 | } else { |
692 | 38 | status = PJ_SUCCESS; |
693 | 38 | } |
694 | | |
695 | | /* Find content-length header field. */ |
696 | 2.11k | for (i = 0; i < response->headers.count; i++) { |
697 | 1.70k | if (!pj_stricmp(&response->headers.header[i].name, |
698 | 1.70k | &STR_CONTENT_LENGTH)) |
699 | 131 | { |
700 | 131 | response->content_length = |
701 | 131 | pj_strtoul(&response->headers.header[i].value); |
702 | | /* If content length is zero, make sure that it is because the |
703 | | * header value is really zero and not due to parsing error. |
704 | | */ |
705 | 131 | if (response->content_length == 0) { |
706 | 66 | if (pj_strcmp2(&response->headers.header[i].value, "0")) { |
707 | 64 | response->content_length = -1; |
708 | 64 | } |
709 | 66 | } |
710 | 131 | break; |
711 | 131 | } |
712 | 1.70k | } |
713 | | |
714 | 540 | return status; |
715 | 540 | } Unexecuted instantiation: http_client.c:http_response_parse fuzz-http.c:http_response_parse Line | Count | Source | 626 | 563 | { | 627 | 563 | pj_size_t i; | 628 | 563 | char *cptr; | 629 | 563 | char *end_status, *newdata; | 630 | 563 | pj_scanner scanner; | 631 | 563 | pj_str_t s; | 632 | 563 | const pj_str_t STR_CONTENT_LENGTH = { CONTENT_LENGTH, 14 }; | 633 | 563 | pj_status_t status; | 634 | | | 635 | 563 | PJ_USE_EXCEPTION; | 636 | | | 637 | 563 | PJ_ASSERT_RETURN(response, PJ_EINVAL); | 638 | 563 | if (size < 2) | 639 | 0 | return PJLIB_UTIL_EHTTPINCHDR; | 640 | | /* Detect whether we already receive the response's status-line | 641 | | * and its headers. We're looking for a pair of CRLFs. A pair of | 642 | | * LFs is also supported although it is not RFC standard. | 643 | | */ | 644 | 563 | cptr = (char *)data; | 645 | 65.3k | for (i = 1, cptr++; i < size; i++, cptr++) { | 646 | 65.2k | if (*cptr == '\n') { | 647 | 7.93k | if (*(cptr - 1) == '\n') | 648 | 420 | break; | 649 | 7.51k | if (*(cptr - 1) == '\r') { | 650 | 2.06k | if (i >= 3 && *(cptr - 2) == '\n' && *(cptr - 3) == '\r') | 651 | 120 | break; | 652 | 2.06k | } | 653 | 7.51k | } | 654 | 65.2k | } | 655 | 563 | if (i == size) | 656 | 23 | return PJLIB_UTIL_EHTTPINCHDR; | 657 | 540 | *remainder = size - 1 - i; | 658 | | | 659 | 540 | pj_bzero(response, sizeof(*response)); | 660 | 540 | response->content_length = -1; | 661 | | | 662 | 540 | newdata = (char*) pj_pool_alloc(pool, i); | 663 | 540 | pj_memcpy(newdata, data, i); | 664 | | | 665 | | /* Parse the status-line. */ | 666 | 540 | pj_scan_init(&scanner, newdata, i, 0, &on_syntax_error); | 667 | 540 | PJ_TRY { | 668 | 540 | pj_scan_get_until_ch(&scanner, ' ', &response->version); | 669 | 540 | pj_scan_advance_n(&scanner, 1, PJ_FALSE); | 670 | 540 | pj_scan_get_until_ch(&scanner, ' ', &s); | 671 | 540 | response->status_code = (pj_uint16_t)pj_strtoul(&s); | 672 | 540 | pj_scan_advance_n(&scanner, 1, PJ_FALSE); | 673 | 540 | pj_scan_get_until_ch(&scanner, '\n', &response->reason); | 674 | 540 | if (response->reason.ptr[response->reason.slen-1] == '\r') | 675 | 22 | response->reason.slen--; | 676 | 540 | } | 677 | 0 | PJ_CATCH_ANY { | 678 | 0 | pj_scan_fini(&scanner); | 679 | 0 | return PJ_GET_EXCEPTION(); | 680 | 0 | } | 681 | 540 | PJ_END; | 682 | | | 683 | 540 | end_status = scanner.curptr; | 684 | 540 | pj_scan_fini(&scanner); | 685 | | | 686 | | /* Parse the response headers. */ | 687 | 540 | size = (i < 2)? 0: i - 2 - (end_status - newdata); | 688 | 540 | if (size > 0) { | 689 | 502 | status = http_headers_parse(end_status + 1, size, | 690 | 502 | &response->headers); | 691 | 502 | } else { | 692 | 38 | status = PJ_SUCCESS; | 693 | 38 | } | 694 | | | 695 | | /* Find content-length header field. */ | 696 | 2.11k | for (i = 0; i < response->headers.count; i++) { | 697 | 1.70k | if (!pj_stricmp(&response->headers.header[i].name, | 698 | 1.70k | &STR_CONTENT_LENGTH)) | 699 | 131 | { | 700 | 131 | response->content_length = | 701 | 131 | pj_strtoul(&response->headers.header[i].value); | 702 | | /* If content length is zero, make sure that it is because the | 703 | | * header value is really zero and not due to parsing error. | 704 | | */ | 705 | 131 | if (response->content_length == 0) { | 706 | 66 | if (pj_strcmp2(&response->headers.header[i].value, "0")) { | 707 | 64 | response->content_length = -1; | 708 | 64 | } | 709 | 66 | } | 710 | 131 | break; | 711 | 131 | } | 712 | 1.70k | } | 713 | | | 714 | 540 | return status; | 715 | 540 | } |
|
716 | | |
717 | | static pj_status_t http_headers_parse(char *hdata, pj_size_t size, |
718 | | pj_http_headers *headers) |
719 | 502 | { |
720 | 502 | pj_scanner scanner; |
721 | 502 | pj_str_t s, s2; |
722 | 502 | pj_status_t status; |
723 | 502 | PJ_USE_EXCEPTION; |
724 | | |
725 | 502 | PJ_ASSERT_RETURN(headers, PJ_EINVAL); |
726 | | |
727 | 502 | pj_scan_init(&scanner, hdata, size, 0, &on_syntax_error); |
728 | | |
729 | | /* Parse each line of header field consisting of header field name and |
730 | | * value, separated by ":" and any number of white spaces. |
731 | | */ |
732 | 502 | PJ_TRY { |
733 | 5.81k | do { |
734 | 5.81k | pj_scan_get_until_chr(&scanner, ":\n", &s); |
735 | 5.81k | if (*scanner.curptr == ':') { |
736 | 1.75k | pj_scan_advance_n(&scanner, 1, PJ_TRUE); |
737 | 1.75k | pj_scan_get_until_ch(&scanner, '\n', &s2); |
738 | 1.75k | if (s2.ptr[s2.slen-1] == '\r') |
739 | 251 | s2.slen--; |
740 | 1.75k | status = pj_http_headers_add_elmt(headers, &s, &s2); |
741 | 1.75k | if (status != PJ_SUCCESS) |
742 | 3 | PJ_THROW(status); |
743 | 1.75k | } |
744 | 5.81k | pj_scan_advance_n(&scanner, 1, PJ_TRUE); |
745 | | /* Finish parsing */ |
746 | 5.81k | if (pj_scan_is_eof(&scanner)) |
747 | 150 | break; |
748 | 5.81k | } while (1); |
749 | 502 | } |
750 | 0 | PJ_CATCH_ANY { |
751 | 0 | pj_scan_fini(&scanner); |
752 | 0 | return PJ_GET_EXCEPTION(); |
753 | 0 | } |
754 | 499 | PJ_END; |
755 | | |
756 | 499 | pj_scan_fini(&scanner); |
757 | | |
758 | 499 | return PJ_SUCCESS; |
759 | 502 | } Unexecuted instantiation: http_client.c:http_headers_parse fuzz-http.c:http_headers_parse Line | Count | Source | 719 | 502 | { | 720 | 502 | pj_scanner scanner; | 721 | 502 | pj_str_t s, s2; | 722 | 502 | pj_status_t status; | 723 | 502 | PJ_USE_EXCEPTION; | 724 | | | 725 | 502 | PJ_ASSERT_RETURN(headers, PJ_EINVAL); | 726 | | | 727 | 502 | pj_scan_init(&scanner, hdata, size, 0, &on_syntax_error); | 728 | | | 729 | | /* Parse each line of header field consisting of header field name and | 730 | | * value, separated by ":" and any number of white spaces. | 731 | | */ | 732 | 502 | PJ_TRY { | 733 | 5.81k | do { | 734 | 5.81k | pj_scan_get_until_chr(&scanner, ":\n", &s); | 735 | 5.81k | if (*scanner.curptr == ':') { | 736 | 1.75k | pj_scan_advance_n(&scanner, 1, PJ_TRUE); | 737 | 1.75k | pj_scan_get_until_ch(&scanner, '\n', &s2); | 738 | 1.75k | if (s2.ptr[s2.slen-1] == '\r') | 739 | 251 | s2.slen--; | 740 | 1.75k | status = pj_http_headers_add_elmt(headers, &s, &s2); | 741 | 1.75k | if (status != PJ_SUCCESS) | 742 | 3 | PJ_THROW(status); | 743 | 1.75k | } | 744 | 5.81k | pj_scan_advance_n(&scanner, 1, PJ_TRUE); | 745 | | /* Finish parsing */ | 746 | 5.81k | if (pj_scan_is_eof(&scanner)) | 747 | 150 | break; | 748 | 5.81k | } while (1); | 749 | 502 | } | 750 | 0 | PJ_CATCH_ANY { | 751 | 0 | pj_scan_fini(&scanner); | 752 | 0 | return PJ_GET_EXCEPTION(); | 753 | 0 | } | 754 | 499 | PJ_END; | 755 | | | 756 | 499 | pj_scan_fini(&scanner); | 757 | | | 758 | 499 | return PJ_SUCCESS; | 759 | 502 | } |
|
760 | | |
761 | | PJ_DEF(void) pj_http_req_param_default(pj_http_req_param *param) |
762 | 0 | { |
763 | 0 | pj_assert(param); |
764 | 0 | pj_bzero(param, sizeof(*param)); |
765 | 0 | param->addr_family = pj_AF_INET(); |
766 | 0 | pj_strset2(¶m->method, (char*)http_method_names[HTTP_GET]); |
767 | 0 | pj_strset2(¶m->version, (char*)HTTP_1_0); |
768 | 0 | param->timeout.msec = PJ_HTTP_DEFAULT_TIMEOUT; |
769 | 0 | pj_time_val_normalize(¶m->timeout); |
770 | 0 | param->max_retries = 3; |
771 | 0 | } |
772 | | |
773 | | /* Get the location of '@' character to indicate the end of |
774 | | * user:passwd part of an URI. If user:passwd part is not |
775 | | * present, NULL will be returned. |
776 | | */ |
777 | | static char *get_url_at_pos(const char *str, pj_size_t len) |
778 | 103 | { |
779 | 103 | const char *end = str + len; |
780 | 103 | const char *p = str; |
781 | | |
782 | | /* skip scheme: */ |
783 | 1.97k | while (p!=end && *p!='/') ++p; |
784 | 103 | if (p!=end && *p=='/') ++p; |
785 | 103 | if (p!=end && *p=='/') ++p; |
786 | 103 | if (p==end) return NULL; |
787 | | |
788 | 6.17k | for (; p!=end; ++p) { |
789 | 6.11k | switch (*p) { |
790 | 8 | case '/': |
791 | 8 | return NULL; |
792 | 22 | case '@': |
793 | 22 | return (char*)p; |
794 | 6.11k | } |
795 | 6.11k | } |
796 | | |
797 | 62 | return NULL; |
798 | 92 | } http_client.c:get_url_at_pos Line | Count | Source | 778 | 103 | { | 779 | 103 | const char *end = str + len; | 780 | 103 | const char *p = str; | 781 | | | 782 | | /* skip scheme: */ | 783 | 1.97k | while (p!=end && *p!='/') ++p; | 784 | 103 | if (p!=end && *p=='/') ++p; | 785 | 103 | if (p!=end && *p=='/') ++p; | 786 | 103 | if (p==end) return NULL; | 787 | | | 788 | 6.17k | for (; p!=end; ++p) { | 789 | 6.11k | switch (*p) { | 790 | 8 | case '/': | 791 | 8 | return NULL; | 792 | 22 | case '@': | 793 | 22 | return (char*)p; | 794 | 6.11k | } | 795 | 6.11k | } | 796 | | | 797 | 62 | return NULL; | 798 | 92 | } |
Unexecuted instantiation: fuzz-http.c:get_url_at_pos |
799 | | |
800 | | |
801 | | PJ_DEF(pj_status_t) pj_http_req_parse_url(const pj_str_t *url, |
802 | | pj_http_url *hurl) |
803 | 233 | { |
804 | 233 | pj_scanner scanner; |
805 | 233 | pj_size_t len = url->slen; |
806 | 233 | PJ_USE_EXCEPTION; |
807 | | |
808 | 233 | if (!len) return -1; |
809 | | |
810 | 233 | pj_bzero(hurl, sizeof(*hurl)); |
811 | 233 | pj_scan_init(&scanner, url->ptr, url->slen, 0, &on_syntax_error); |
812 | | |
813 | 233 | PJ_TRY { |
814 | 233 | pj_str_t s; |
815 | | |
816 | | /* Exhaust any whitespaces. */ |
817 | 233 | pj_scan_skip_whitespace(&scanner); |
818 | | |
819 | | /* Parse the protocol */ |
820 | 233 | pj_scan_get_until_ch(&scanner, ':', &s); |
821 | 233 | if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTP])) { |
822 | 121 | pj_strset2(&hurl->protocol, |
823 | 121 | (char*)http_protocol_names[PROTOCOL_HTTP]); |
824 | 121 | } else if (!pj_stricmp2(&s, http_protocol_names[PROTOCOL_HTTPS])) { |
825 | 1 | pj_strset2(&hurl->protocol, |
826 | 1 | (char*)http_protocol_names[PROTOCOL_HTTPS]); |
827 | 111 | } else { |
828 | 111 | PJ_THROW(PJ_ENOTSUP); // unsupported protocol |
829 | 111 | } |
830 | | |
831 | 122 | if (pj_scan_strcmp(&scanner, "://", 3)) { |
832 | 18 | PJ_THROW(PJLIB_UTIL_EHTTPINURL); // no "://" after protocol name |
833 | 18 | } |
834 | 104 | pj_scan_advance_n(&scanner, 3, PJ_FALSE); |
835 | | |
836 | 104 | if (get_url_at_pos(url->ptr, url->slen)) { |
837 | | /* Parse username and password */ |
838 | 22 | pj_scan_get_until_chr(&scanner, ":@", &hurl->username); |
839 | 22 | if (*scanner.curptr == ':') { |
840 | 10 | pj_scan_get_char(&scanner); |
841 | 10 | pj_scan_get_until_chr(&scanner, "@", &hurl->passwd); |
842 | 12 | } else { |
843 | 12 | hurl->passwd.slen = 0; |
844 | 12 | } |
845 | 22 | pj_scan_get_char(&scanner); |
846 | 22 | } |
847 | | |
848 | | /* Parse the host and port number (if any) */ |
849 | 104 | pj_scan_get_until_chr(&scanner, ":/", &s); |
850 | 104 | pj_strassign(&hurl->host, &s); |
851 | 104 | if (hurl->host.slen==0) |
852 | 2 | PJ_THROW(PJ_EINVAL); |
853 | 102 | if (pj_scan_is_eof(&scanner) || *scanner.curptr == '/') { |
854 | | /* No port number specified */ |
855 | | /* Assume default http/https port number */ |
856 | 42 | hurl->port = get_http_default_port(&hurl->protocol); |
857 | 42 | pj_assert(hurl->port > 0); |
858 | 60 | } else { |
859 | 60 | pj_scan_advance_n(&scanner, 1, PJ_FALSE); |
860 | 60 | pj_scan_get_until_ch(&scanner, '/', &s); |
861 | | /* Parse the port number */ |
862 | 60 | hurl->port = (pj_uint16_t)pj_strtoul(&s); |
863 | 60 | if (!hurl->port) |
864 | 3 | PJ_THROW(PJLIB_UTIL_EHTTPINPORT); // invalid port number |
865 | 60 | } |
866 | | |
867 | 99 | if (!pj_scan_is_eof(&scanner)) { |
868 | 9 | hurl->path.ptr = scanner.curptr; |
869 | 9 | hurl->path.slen = scanner.end - scanner.curptr; |
870 | 90 | } else { |
871 | | /* no path, append '/' */ |
872 | 90 | pj_cstr(&hurl->path, "/"); |
873 | 90 | } |
874 | 99 | } |
875 | 0 | PJ_CATCH_ANY { |
876 | 0 | pj_scan_fini(&scanner); |
877 | 0 | return PJ_GET_EXCEPTION(); |
878 | 0 | } |
879 | 99 | PJ_END; |
880 | | |
881 | 99 | pj_scan_fini(&scanner); |
882 | 99 | return PJ_SUCCESS; |
883 | 233 | } |
884 | | |
885 | | PJ_DEF(void) pj_http_req_set_timeout(pj_http_req *http_req, |
886 | | const pj_time_val* timeout) |
887 | 0 | { |
888 | 0 | pj_memcpy(&http_req->param.timeout, timeout, sizeof(*timeout)); |
889 | 0 | } |
890 | | |
891 | | PJ_DEF(pj_status_t) pj_http_req_create(pj_pool_t *pool, |
892 | | const pj_str_t *url, |
893 | | pj_timer_heap_t *timer, |
894 | | pj_ioqueue_t *ioqueue, |
895 | | const pj_http_req_param *param, |
896 | | const pj_http_req_callback *hcb, |
897 | | pj_http_req **http_req) |
898 | 0 | { |
899 | 0 | pj_pool_t *own_pool; |
900 | 0 | pj_http_req *hreq; |
901 | 0 | char *at_pos; |
902 | 0 | pj_status_t status; |
903 | |
|
904 | 0 | PJ_ASSERT_RETURN(pool && url && timer && ioqueue && |
905 | 0 | hcb && http_req, PJ_EINVAL); |
906 | | |
907 | 0 | *http_req = NULL; |
908 | 0 | own_pool = pj_pool_create(pool->factory, NULL, INITIAL_POOL_SIZE, |
909 | 0 | POOL_INCREMENT_SIZE, NULL); |
910 | 0 | hreq = PJ_POOL_ZALLOC_T(own_pool, struct pj_http_req); |
911 | 0 | if (!hreq) |
912 | 0 | return PJ_ENOMEM; |
913 | | |
914 | | /* Initialization */ |
915 | 0 | hreq->pool = own_pool; |
916 | 0 | hreq->ioqueue = ioqueue; |
917 | 0 | hreq->timer = timer; |
918 | 0 | hreq->asock = NULL; |
919 | 0 | pj_memcpy(&hreq->cb, hcb, sizeof(*hcb)); |
920 | 0 | hreq->state = IDLE; |
921 | 0 | hreq->resolved = PJ_FALSE; |
922 | 0 | hreq->buffer.ptr = NULL; |
923 | 0 | pj_timer_entry_init(&hreq->timer_entry, 0, hreq, &on_timeout); |
924 | | |
925 | | /* Initialize parameter */ |
926 | 0 | if (param) { |
927 | 0 | pj_memcpy(&hreq->param, param, sizeof(*param)); |
928 | | /* TODO: validate the param here |
929 | | * Should we validate the method as well? If yes, based on all HTTP |
930 | | * methods or based on supported methods only? For the later, one |
931 | | * drawback would be that you can't use this if the method is not |
932 | | * officially supported |
933 | | */ |
934 | 0 | PJ_ASSERT_RETURN(hreq->param.addr_family==pj_AF_UNSPEC() || |
935 | 0 | hreq->param.addr_family==pj_AF_INET() || |
936 | 0 | hreq->param.addr_family==pj_AF_INET6(), PJ_EAFNOTSUP); |
937 | 0 | PJ_ASSERT_RETURN(!pj_strcmp2(&hreq->param.version, HTTP_1_0) || |
938 | 0 | !pj_strcmp2(&hreq->param.version, HTTP_1_1), |
939 | 0 | PJ_ENOTSUP); |
940 | 0 | pj_time_val_normalize(&hreq->param.timeout); |
941 | 0 | } else { |
942 | 0 | pj_http_req_param_default(&hreq->param); |
943 | 0 | } |
944 | | |
945 | | /* Parse the URL */ |
946 | 0 | if (!pj_strdup_with_null(hreq->pool, &hreq->url, url)) { |
947 | 0 | pj_pool_release(hreq->pool); |
948 | 0 | return PJ_ENOMEM; |
949 | 0 | } |
950 | 0 | status = pj_http_req_parse_url(&hreq->url, &hreq->hurl); |
951 | 0 | if (status != PJ_SUCCESS) { |
952 | 0 | pj_pool_release(hreq->pool); |
953 | 0 | return status; // Invalid URL supplied |
954 | 0 | } |
955 | | |
956 | | /* If URL contains username/password, move them to credential and |
957 | | * remove them from the URL. |
958 | | */ |
959 | 0 | if ((at_pos=get_url_at_pos(hreq->url.ptr, hreq->url.slen)) != NULL) { |
960 | 0 | pj_str_t tmp; |
961 | 0 | char *user_pos = pj_strchr(&hreq->url, '/'); |
962 | 0 | int removed_len; |
963 | | |
964 | | /* Save credential first, unescape the string */ |
965 | 0 | tmp = pj_str_unescape(hreq->pool, &hreq->hurl.username);; |
966 | 0 | pj_strdup(hreq->pool, &hreq->param.auth_cred.username, &tmp); |
967 | |
|
968 | 0 | tmp = pj_str_unescape(hreq->pool, &hreq->hurl.passwd); |
969 | 0 | pj_strdup(hreq->pool, &hreq->param.auth_cred.data, &tmp); |
970 | |
|
971 | 0 | hreq->hurl.username.ptr = hreq->hurl.passwd.ptr = NULL; |
972 | 0 | hreq->hurl.username.slen = hreq->hurl.passwd.slen = 0; |
973 | | |
974 | | /* Remove "username:password@" from the URL */ |
975 | 0 | pj_assert(user_pos != 0 && user_pos < at_pos); |
976 | 0 | user_pos += 2; |
977 | 0 | removed_len = (int)(at_pos + 1 - user_pos); |
978 | 0 | pj_memmove(user_pos, at_pos+1, hreq->url.ptr+hreq->url.slen-at_pos-1); |
979 | 0 | hreq->url.slen -= removed_len; |
980 | | |
981 | | /* Need to adjust hostname and path pointers due to memmove*/ |
982 | 0 | if (hreq->hurl.host.ptr > user_pos && |
983 | 0 | hreq->hurl.host.ptr < user_pos + hreq->url.slen) |
984 | 0 | { |
985 | 0 | hreq->hurl.host.ptr -= removed_len; |
986 | 0 | } |
987 | | /* path may come from a string constant, don't shift it if so */ |
988 | 0 | if (hreq->hurl.path.ptr > user_pos && |
989 | 0 | hreq->hurl.path.ptr < user_pos + hreq->url.slen) |
990 | 0 | { |
991 | 0 | hreq->hurl.path.ptr -= removed_len; |
992 | 0 | } |
993 | 0 | } |
994 | | |
995 | 0 | *http_req = hreq; |
996 | 0 | return PJ_SUCCESS; |
997 | 0 | } |
998 | | |
999 | | PJ_DEF(pj_bool_t) pj_http_req_is_running(const pj_http_req *http_req) |
1000 | 0 | { |
1001 | 0 | PJ_ASSERT_RETURN(http_req, PJ_FALSE); |
1002 | 0 | return (http_req->state != IDLE); |
1003 | 0 | } |
1004 | | |
1005 | | PJ_DEF(void*) pj_http_req_get_user_data(pj_http_req *http_req) |
1006 | 0 | { |
1007 | 0 | PJ_ASSERT_RETURN(http_req, NULL); |
1008 | 0 | return http_req->param.user_data; |
1009 | 0 | } |
1010 | | |
1011 | | static pj_status_t start_http_req(pj_http_req *http_req, |
1012 | | pj_bool_t notify_on_fail) |
1013 | 0 | { |
1014 | 0 | pj_sock_t sock = PJ_INVALID_SOCKET; |
1015 | 0 | pj_status_t status; |
1016 | 0 | pj_activesock_cb asock_cb; |
1017 | 0 | int retry = 0; |
1018 | |
|
1019 | 0 | PJ_ASSERT_RETURN(http_req, PJ_EINVAL); |
1020 | | /* Http request is not idle, a request was initiated before and |
1021 | | * is still in progress |
1022 | | */ |
1023 | 0 | PJ_ASSERT_RETURN(http_req->state == IDLE, PJ_EBUSY); |
1024 | | |
1025 | | /* Reset few things to make sure restarting works */ |
1026 | 0 | http_req->error = 0; |
1027 | 0 | http_req->response.headers.count = 0; |
1028 | 0 | pj_bzero(&http_req->tcp_state, sizeof(http_req->tcp_state)); |
1029 | |
|
1030 | 0 | if (!http_req->resolved) { |
1031 | | /* Resolve the Internet address of the host */ |
1032 | 0 | status = pj_sockaddr_init(http_req->param.addr_family, |
1033 | 0 | &http_req->addr, &http_req->hurl.host, |
1034 | 0 | http_req->hurl.port); |
1035 | 0 | if (status != PJ_SUCCESS || |
1036 | 0 | !pj_sockaddr_has_addr(&http_req->addr) || |
1037 | 0 | (http_req->param.addr_family==pj_AF_INET() && |
1038 | 0 | http_req->addr.ipv4.sin_addr.s_addr==PJ_INADDR_NONE)) |
1039 | 0 | { |
1040 | 0 | goto on_return; |
1041 | 0 | } |
1042 | 0 | http_req->resolved = PJ_TRUE; |
1043 | 0 | } |
1044 | | |
1045 | 0 | status = pj_sock_socket(http_req->param.addr_family, |
1046 | 0 | pj_SOCK_STREAM() | pj_SOCK_CLOEXEC(), |
1047 | 0 | 0, &sock); |
1048 | 0 | if (status != PJ_SUCCESS) |
1049 | 0 | goto on_return; // error creating socket |
1050 | | |
1051 | 0 | pj_bzero(&asock_cb, sizeof(asock_cb)); |
1052 | 0 | asock_cb.on_data_read = &http_on_data_read; |
1053 | 0 | asock_cb.on_data_sent = &http_on_data_sent; |
1054 | 0 | asock_cb.on_connect_complete = &http_on_connect; |
1055 | | |
1056 | 0 | do |
1057 | 0 | { |
1058 | 0 | pj_sockaddr_in bound_addr; |
1059 | 0 | pj_uint16_t port = 0; |
1060 | | |
1061 | | /* If we are using port restriction. |
1062 | | * Get a random port within the range |
1063 | | */ |
1064 | 0 | if (http_req->param.source_port_range_start != 0) { |
1065 | 0 | port = (pj_uint16_t) |
1066 | 0 | (http_req->param.source_port_range_start + |
1067 | 0 | (pj_rand() % http_req->param.source_port_range_size)); |
1068 | 0 | } |
1069 | |
|
1070 | 0 | pj_sockaddr_in_init(&bound_addr, NULL, port); |
1071 | 0 | status = pj_sock_bind(sock, &bound_addr, sizeof(bound_addr)); |
1072 | |
|
1073 | 0 | } while (status != PJ_SUCCESS && (retry++ < http_req->param.max_retries)); |
1074 | |
|
1075 | 0 | if (status != PJ_SUCCESS) { |
1076 | 0 | PJ_PERROR(1,(THIS_FILE, status, |
1077 | 0 | "Unable to bind to the requested port")); |
1078 | 0 | pj_sock_close(sock); |
1079 | 0 | goto on_return; |
1080 | 0 | } |
1081 | | |
1082 | | // TODO: should we set whole data to 0 by default? |
1083 | | // or add it in the param? |
1084 | 0 | status = pj_activesock_create(http_req->pool, sock, pj_SOCK_STREAM(), |
1085 | 0 | NULL, http_req->ioqueue, |
1086 | 0 | &asock_cb, http_req, &http_req->asock); |
1087 | 0 | if (status != PJ_SUCCESS) { |
1088 | 0 | pj_sock_close(sock); |
1089 | 0 | goto on_return; // error creating activesock |
1090 | 0 | } |
1091 | | |
1092 | | /* Schedule timeout timer for the request */ |
1093 | 0 | pj_assert(http_req->timer_entry.id == 0); |
1094 | 0 | http_req->timer_entry.id = 1; |
1095 | 0 | status = pj_timer_heap_schedule(http_req->timer, &http_req->timer_entry, |
1096 | 0 | &http_req->param.timeout); |
1097 | 0 | if (status != PJ_SUCCESS) { |
1098 | 0 | http_req->timer_entry.id = 0; |
1099 | 0 | goto on_return; // error scheduling timer |
1100 | 0 | } |
1101 | | |
1102 | | /* Connect to host */ |
1103 | 0 | http_req->state = CONNECTING; |
1104 | 0 | status = pj_activesock_start_connect(http_req->asock, http_req->pool, |
1105 | 0 | (pj_sockaddr_t *)&(http_req->addr), |
1106 | 0 | pj_sockaddr_get_len(&http_req->addr)); |
1107 | 0 | if (status == PJ_SUCCESS) { |
1108 | 0 | http_req->state = SENDING_REQUEST; |
1109 | 0 | status = http_req_start_sending(http_req); |
1110 | 0 | if (status != PJ_SUCCESS) |
1111 | 0 | goto on_return; |
1112 | 0 | } else if (status != PJ_EPENDING) { |
1113 | 0 | goto on_return; // error connecting |
1114 | 0 | } |
1115 | | |
1116 | 0 | return PJ_SUCCESS; |
1117 | | |
1118 | 0 | on_return: |
1119 | 0 | http_req->error = status; |
1120 | 0 | if (notify_on_fail) |
1121 | 0 | pj_http_req_cancel(http_req, PJ_TRUE); |
1122 | 0 | else |
1123 | 0 | http_req_end_request(http_req); |
1124 | |
|
1125 | 0 | return status; |
1126 | 0 | } Unexecuted instantiation: http_client.c:start_http_req Unexecuted instantiation: fuzz-http.c:start_http_req |
1127 | | |
1128 | | /* Starts an asynchronous HTTP request to the URL specified. */ |
1129 | | PJ_DEF(pj_status_t) pj_http_req_start(pj_http_req *http_req) |
1130 | 0 | { |
1131 | 0 | return start_http_req(http_req, PJ_FALSE); |
1132 | 0 | } |
1133 | | |
1134 | | /* Respond to basic authentication challenge */ |
1135 | | static pj_status_t auth_respond_basic(pj_http_req *hreq) |
1136 | 0 | { |
1137 | | /* Basic authentication: |
1138 | | * credentials = "Basic" basic-credentials |
1139 | | * basic-credentials = base64-user-pass |
1140 | | * base64-user-pass = <base64 [4] encoding of user-pass> |
1141 | | * user-pass = userid ":" password |
1142 | | * |
1143 | | * Sample: |
1144 | | * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
1145 | | */ |
1146 | 0 | pj_str_t user_pass; |
1147 | 0 | pj_http_header_elmt *phdr; |
1148 | 0 | pj_status_t status; |
1149 | 0 | int len; |
1150 | | |
1151 | | /* Use send buffer to store userid ":" password */ |
1152 | 0 | user_pass.ptr = hreq->buffer.ptr; |
1153 | 0 | pj_strcpy(&user_pass, &hreq->param.auth_cred.username); |
1154 | 0 | pj_strcat2(&user_pass, ":"); |
1155 | 0 | pj_strcat(&user_pass, &hreq->param.auth_cred.data); |
1156 | | |
1157 | | /* Create Authorization header */ |
1158 | 0 | phdr = &hreq->param.headers.header[hreq->param.headers.count++]; |
1159 | 0 | pj_bzero(phdr, sizeof(*phdr)); |
1160 | 0 | if (hreq->response.status_code == 401) |
1161 | 0 | phdr->name = pj_str("Authorization"); |
1162 | 0 | else |
1163 | 0 | phdr->name = pj_str("Proxy-Authorization"); |
1164 | |
|
1165 | 0 | len = (int)(PJ_BASE256_TO_BASE64_LEN(user_pass.slen) + 10); |
1166 | 0 | phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len); |
1167 | 0 | phdr->value.slen = 0; |
1168 | |
|
1169 | 0 | pj_strcpy2(&phdr->value, "Basic "); |
1170 | 0 | len -= (int)phdr->value.slen; |
1171 | 0 | status = pj_base64_encode((pj_uint8_t*)user_pass.ptr, (int)user_pass.slen, |
1172 | 0 | phdr->value.ptr + phdr->value.slen, &len); |
1173 | 0 | if (status != PJ_SUCCESS) |
1174 | 0 | return status; |
1175 | 0 | phdr->value.slen += len; |
1176 | |
|
1177 | 0 | return PJ_SUCCESS; |
1178 | 0 | } Unexecuted instantiation: http_client.c:auth_respond_basic Unexecuted instantiation: fuzz-http.c:auth_respond_basic |
1179 | | |
1180 | | /** Length of digest string. */ |
1181 | 0 | #define MD5_STRLEN 32 |
1182 | | /* A macro just to get rid of type mismatch between char and unsigned char */ |
1183 | 0 | #define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, \ |
1184 | 0 | (unsigned)len) |
1185 | | |
1186 | | /* Transform digest to string. |
1187 | | * output must be at least PJSIP_MD5STRLEN+1 bytes. |
1188 | | * |
1189 | | * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED! |
1190 | | */ |
1191 | | static void digest2str(const unsigned char digest[], char *output) |
1192 | 0 | { |
1193 | 0 | int i; |
1194 | 0 | for (i = 0; i<16; ++i) { |
1195 | 0 | pj_val_to_hex_digit(digest[i], output); |
1196 | 0 | output += 2; |
1197 | 0 | } |
1198 | 0 | } Unexecuted instantiation: http_client.c:digest2str Unexecuted instantiation: fuzz-http.c:digest2str |
1199 | | |
1200 | | static void auth_create_digest_response(pj_str_t *result, |
1201 | | const pj_http_auth_cred *cred, |
1202 | | const pj_str_t *nonce, |
1203 | | const pj_str_t *nc, |
1204 | | const pj_str_t *cnonce, |
1205 | | const pj_str_t *qop, |
1206 | | const pj_str_t *uri, |
1207 | | const pj_str_t *realm, |
1208 | | const pj_str_t *method) |
1209 | 0 | { |
1210 | 0 | char ha1[MD5_STRLEN]; |
1211 | 0 | char ha2[MD5_STRLEN]; |
1212 | 0 | unsigned char digest[16]; |
1213 | 0 | pj_md5_context pms; |
1214 | |
|
1215 | 0 | pj_assert(result->slen >= MD5_STRLEN); |
1216 | | |
1217 | 0 | TRACE_((THIS_FILE, "Begin creating digest")); |
1218 | |
|
1219 | 0 | if (cred->data_type == 0) { |
1220 | | /*** |
1221 | | *** ha1 = MD5(username ":" realm ":" password) |
1222 | | ***/ |
1223 | 0 | pj_md5_init(&pms); |
1224 | 0 | MD5_APPEND( &pms, cred->username.ptr, cred->username.slen); |
1225 | 0 | MD5_APPEND( &pms, ":", 1); |
1226 | 0 | MD5_APPEND( &pms, realm->ptr, realm->slen); |
1227 | 0 | MD5_APPEND( &pms, ":", 1); |
1228 | 0 | MD5_APPEND( &pms, cred->data.ptr, cred->data.slen); |
1229 | 0 | pj_md5_final(&pms, digest); |
1230 | |
|
1231 | 0 | digest2str(digest, ha1); |
1232 | |
|
1233 | 0 | } else if (cred->data_type == 1) { |
1234 | 0 | pj_assert(cred->data.slen == 32); |
1235 | 0 | pj_memcpy( ha1, cred->data.ptr, cred->data.slen ); |
1236 | 0 | } else { |
1237 | 0 | pj_assert(!"Invalid data_type"); |
1238 | 0 | } |
1239 | | |
1240 | 0 | TRACE_((THIS_FILE, " ha1=%.32s", ha1)); |
1241 | | |
1242 | | /*** |
1243 | | *** ha2 = MD5(method ":" req_uri) |
1244 | | ***/ |
1245 | 0 | pj_md5_init(&pms); |
1246 | 0 | MD5_APPEND( &pms, method->ptr, method->slen); |
1247 | 0 | MD5_APPEND( &pms, ":", 1); |
1248 | 0 | MD5_APPEND( &pms, uri->ptr, uri->slen); |
1249 | 0 | pj_md5_final(&pms, digest); |
1250 | 0 | digest2str(digest, ha2); |
1251 | |
|
1252 | 0 | TRACE_((THIS_FILE, " ha2=%.32s", ha2)); |
1253 | | |
1254 | | /*** |
1255 | | *** When qop is not used: |
1256 | | *** response = MD5(ha1 ":" nonce ":" ha2) |
1257 | | *** |
1258 | | *** When qop=auth is used: |
1259 | | *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) |
1260 | | ***/ |
1261 | 0 | pj_md5_init(&pms); |
1262 | 0 | MD5_APPEND( &pms, ha1, MD5_STRLEN); |
1263 | 0 | MD5_APPEND( &pms, ":", 1); |
1264 | 0 | MD5_APPEND( &pms, nonce->ptr, nonce->slen); |
1265 | 0 | if (qop && qop->slen != 0) { |
1266 | 0 | MD5_APPEND( &pms, ":", 1); |
1267 | 0 | MD5_APPEND( &pms, nc->ptr, nc->slen); |
1268 | 0 | MD5_APPEND( &pms, ":", 1); |
1269 | 0 | MD5_APPEND( &pms, cnonce->ptr, cnonce->slen); |
1270 | 0 | MD5_APPEND( &pms, ":", 1); |
1271 | 0 | MD5_APPEND( &pms, qop->ptr, qop->slen); |
1272 | 0 | } |
1273 | 0 | MD5_APPEND( &pms, ":", 1); |
1274 | 0 | MD5_APPEND( &pms, ha2, MD5_STRLEN); |
1275 | | |
1276 | | /* This is the final response digest. */ |
1277 | 0 | pj_md5_final(&pms, digest); |
1278 | | |
1279 | | /* Convert digest to string and store in chal->response. */ |
1280 | 0 | result->slen = MD5_STRLEN; |
1281 | 0 | digest2str(digest, result->ptr); |
1282 | |
|
1283 | 0 | TRACE_((THIS_FILE, " digest=%.32s", result->ptr)); |
1284 | 0 | TRACE_((THIS_FILE, "Digest created")); |
1285 | 0 | } Unexecuted instantiation: http_client.c:auth_create_digest_response Unexecuted instantiation: fuzz-http.c:auth_create_digest_response |
1286 | | |
1287 | | /* Find out if qop offer contains "auth" token */ |
1288 | | static pj_bool_t auth_has_qop( pj_pool_t *pool, const pj_str_t *qop_offer) |
1289 | 0 | { |
1290 | 0 | pj_str_t qop; |
1291 | 0 | char *p; |
1292 | |
|
1293 | 0 | pj_strdup_with_null( pool, &qop, qop_offer); |
1294 | 0 | p = qop.ptr; |
1295 | 0 | while (*p) { |
1296 | 0 | *p = (char)pj_tolower(*p); |
1297 | 0 | ++p; |
1298 | 0 | } |
1299 | |
|
1300 | 0 | p = qop.ptr; |
1301 | 0 | while (*p) { |
1302 | 0 | if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') { |
1303 | 0 | int e = *(p+4); |
1304 | 0 | if (e=='"' || e==',' || e==0) |
1305 | 0 | return PJ_TRUE; |
1306 | 0 | else |
1307 | 0 | p += 4; |
1308 | 0 | } else { |
1309 | 0 | ++p; |
1310 | 0 | } |
1311 | 0 | } |
1312 | | |
1313 | 0 | return PJ_FALSE; |
1314 | 0 | } Unexecuted instantiation: http_client.c:auth_has_qop Unexecuted instantiation: fuzz-http.c:auth_has_qop |
1315 | | |
1316 | 0 | #define STR_PREC(s) (int)(s).slen, (s).ptr |
1317 | | |
1318 | | /* Respond to digest authentication */ |
1319 | | static pj_status_t auth_respond_digest(pj_http_req *hreq) |
1320 | 0 | { |
1321 | 0 | const pj_http_auth_chal *chal = &hreq->response.auth_chal; |
1322 | 0 | const pj_http_auth_cred *cred = &hreq->param.auth_cred; |
1323 | 0 | pj_http_header_elmt *phdr; |
1324 | 0 | char digest_response_buf[MD5_STRLEN]; |
1325 | 0 | int len; |
1326 | 0 | pj_str_t digest_response; |
1327 | | |
1328 | | /* Check algorithm is supported. We only support MD5 */ |
1329 | 0 | if (chal->algorithm.slen!=0 && |
1330 | 0 | pj_stricmp2(&chal->algorithm, "MD5")) |
1331 | 0 | { |
1332 | 0 | TRACE_((THIS_FILE, "Error: Unsupported digest algorithm \"%.*s\"", |
1333 | 0 | chal->algorithm.slen, chal->algorithm.ptr)); |
1334 | 0 | return PJ_ENOTSUP; |
1335 | 0 | } |
1336 | | |
1337 | | /* Add Authorization header */ |
1338 | 0 | phdr = &hreq->param.headers.header[hreq->param.headers.count++]; |
1339 | 0 | pj_bzero(phdr, sizeof(*phdr)); |
1340 | 0 | if (hreq->response.status_code == 401) |
1341 | 0 | phdr->name = pj_str("Authorization"); |
1342 | 0 | else |
1343 | 0 | phdr->name = pj_str("Proxy-Authorization"); |
1344 | | |
1345 | | /* Allocate space for the header */ |
1346 | 0 | len = (int)(8 + /* Digest */ |
1347 | 0 | 16 + hreq->param.auth_cred.username.slen + /* username= */ |
1348 | 0 | 12 + chal->realm.slen + /* realm= */ |
1349 | 0 | 12 + chal->nonce.slen + /* nonce= */ |
1350 | 0 | 8 + hreq->hurl.path.slen + /* uri= */ |
1351 | 0 | 16 + /* algorithm=MD5 */ |
1352 | 0 | 16 + MD5_STRLEN + /* response= */ |
1353 | 0 | 12 + /* qop=auth */ |
1354 | 0 | 8 + /* nc=.. */ |
1355 | 0 | 30 + /* cnonce= */ |
1356 | 0 | 12 + chal->opaque.slen + /* opaque=".." */ |
1357 | 0 | 0); |
1358 | 0 | phdr->value.ptr = (char*)pj_pool_alloc(hreq->pool, len); |
1359 | | |
1360 | | /* Configure buffer to temporarily store the digest */ |
1361 | 0 | digest_response.ptr = digest_response_buf; |
1362 | 0 | digest_response.slen = MD5_STRLEN; |
1363 | |
|
1364 | 0 | if (chal->qop.slen == 0) { |
1365 | 0 | const pj_str_t STR_MD5 = { "MD5", 3 }; |
1366 | 0 | int max_len; |
1367 | | |
1368 | | /* Server doesn't require quality of protection. */ |
1369 | 0 | auth_create_digest_response(&digest_response, cred, |
1370 | 0 | &chal->nonce, NULL, NULL, NULL, |
1371 | 0 | &hreq->hurl.path, &chal->realm, |
1372 | 0 | &hreq->param.method); |
1373 | |
|
1374 | 0 | max_len = len; |
1375 | 0 | len = pj_ansi_snprintf( |
1376 | 0 | phdr->value.ptr, max_len, |
1377 | 0 | "Digest username=\"%.*s\", " |
1378 | 0 | "realm=\"%.*s\", " |
1379 | 0 | "nonce=\"%.*s\", " |
1380 | 0 | "uri=\"%.*s\", " |
1381 | 0 | "algorithm=%.*s, " |
1382 | 0 | "response=\"%.*s\"", |
1383 | 0 | STR_PREC(cred->username), |
1384 | 0 | STR_PREC(chal->realm), |
1385 | 0 | STR_PREC(chal->nonce), |
1386 | 0 | STR_PREC(hreq->hurl.path), |
1387 | 0 | STR_PREC(STR_MD5), |
1388 | 0 | STR_PREC(digest_response)); |
1389 | 0 | if (len < 0 || len >= max_len) |
1390 | 0 | return PJ_ETOOSMALL; |
1391 | 0 | phdr->value.slen = len; |
1392 | |
|
1393 | 0 | } else if (auth_has_qop(hreq->pool, &chal->qop)) { |
1394 | | /* Server requires quality of protection. |
1395 | | * We respond with selecting "qop=auth" protection. |
1396 | | */ |
1397 | 0 | const pj_str_t STR_MD5 = { "MD5", 3 }; |
1398 | 0 | const pj_str_t qop = pj_str("auth"); |
1399 | 0 | const pj_str_t nc = pj_str("00000001"); |
1400 | 0 | const pj_str_t cnonce = pj_str("b39971"); |
1401 | 0 | int max_len; |
1402 | |
|
1403 | 0 | auth_create_digest_response(&digest_response, cred, |
1404 | 0 | &chal->nonce, &nc, &cnonce, &qop, |
1405 | 0 | &hreq->hurl.path, &chal->realm, |
1406 | 0 | &hreq->param.method); |
1407 | 0 | max_len = len; |
1408 | 0 | len = pj_ansi_snprintf( |
1409 | 0 | phdr->value.ptr, max_len, |
1410 | 0 | "Digest username=\"%.*s\", " |
1411 | 0 | "realm=\"%.*s\", " |
1412 | 0 | "nonce=\"%.*s\", " |
1413 | 0 | "uri=\"%.*s\", " |
1414 | 0 | "algorithm=%.*s, " |
1415 | 0 | "response=\"%.*s\", " |
1416 | 0 | "qop=%.*s, " |
1417 | 0 | "nc=%.*s, " |
1418 | 0 | "cnonce=\"%.*s\"", |
1419 | 0 | STR_PREC(cred->username), |
1420 | 0 | STR_PREC(chal->realm), |
1421 | 0 | STR_PREC(chal->nonce), |
1422 | 0 | STR_PREC(hreq->hurl.path), |
1423 | 0 | STR_PREC(STR_MD5), |
1424 | 0 | STR_PREC(digest_response), |
1425 | 0 | STR_PREC(qop), |
1426 | 0 | STR_PREC(nc), |
1427 | 0 | STR_PREC(cnonce)); |
1428 | 0 | if (len < 0 || len >= max_len) |
1429 | 0 | return PJ_ETOOSMALL; |
1430 | 0 | phdr->value.slen = len; |
1431 | |
|
1432 | 0 | if (chal->opaque.slen) { |
1433 | 0 | pj_strcat2(&phdr->value, ", opaque=\""); |
1434 | 0 | pj_strcat(&phdr->value, &chal->opaque); |
1435 | 0 | pj_strcat2(&phdr->value, "\""); |
1436 | 0 | } |
1437 | |
|
1438 | 0 | } else { |
1439 | | /* Server requires quality protection that we don't support. */ |
1440 | 0 | TRACE_((THIS_FILE, "Error: Unsupported qop offer %.*s", |
1441 | 0 | chal->qop.slen, chal->qop.ptr)); |
1442 | 0 | return PJ_ENOTSUP; |
1443 | 0 | } |
1444 | | |
1445 | 0 | return PJ_SUCCESS; |
1446 | 0 | } Unexecuted instantiation: http_client.c:auth_respond_digest Unexecuted instantiation: fuzz-http.c:auth_respond_digest |
1447 | | |
1448 | | |
1449 | | static void restart_req_with_auth(pj_http_req *hreq) |
1450 | 0 | { |
1451 | 0 | pj_http_auth_chal *chal = &hreq->response.auth_chal; |
1452 | 0 | pj_http_auth_cred *cred = &hreq->param.auth_cred; |
1453 | 0 | pj_status_t status; |
1454 | |
|
1455 | 0 | if (hreq->param.headers.count >= PJ_HTTP_HEADER_SIZE) { |
1456 | 0 | TRACE_((THIS_FILE, "Error: no place to put Authorization header")); |
1457 | 0 | hreq->auth_state = AUTH_DONE; |
1458 | 0 | return; |
1459 | 0 | } |
1460 | | |
1461 | | /* If credential specifies specific scheme, make sure they match */ |
1462 | 0 | if (cred->scheme.slen && pj_stricmp(&chal->scheme, &cred->scheme)) { |
1463 | 0 | TRACE_((THIS_FILE, "Error: auth schemes mismatch")); |
1464 | 0 | goto on_error; |
1465 | 0 | } |
1466 | | |
1467 | | /* If credential specifies specific realm, make sure they match */ |
1468 | 0 | if (cred->realm.slen && pj_stricmp(&chal->realm, &cred->realm)) { |
1469 | 0 | TRACE_((THIS_FILE, "Error: auth realms mismatch")); |
1470 | 0 | goto on_error; |
1471 | 0 | } |
1472 | | |
1473 | 0 | if (!pj_stricmp2(&chal->scheme, "basic")) { |
1474 | 0 | status = auth_respond_basic(hreq); |
1475 | 0 | } else if (!pj_stricmp2(&chal->scheme, "digest")) { |
1476 | 0 | status = auth_respond_digest(hreq); |
1477 | 0 | } else { |
1478 | 0 | TRACE_((THIS_FILE, "Error: unsupported HTTP auth scheme")); |
1479 | 0 | status = PJ_ENOTSUP; |
1480 | 0 | } |
1481 | |
|
1482 | 0 | if (status != PJ_SUCCESS) |
1483 | 0 | goto on_error; |
1484 | | |
1485 | 0 | http_req_end_request(hreq); |
1486 | |
|
1487 | 0 | status = start_http_req(hreq, PJ_TRUE); |
1488 | 0 | if (status != PJ_SUCCESS) |
1489 | 0 | goto on_error; |
1490 | | |
1491 | 0 | hreq->auth_state = AUTH_RETRYING; |
1492 | 0 | return; |
1493 | | |
1494 | 0 | on_error: |
1495 | 0 | hreq->auth_state = AUTH_DONE; |
1496 | 0 | } Unexecuted instantiation: http_client.c:restart_req_with_auth Unexecuted instantiation: fuzz-http.c:restart_req_with_auth |
1497 | | |
1498 | | |
1499 | | /* snprintf() to a pj_str_t struct with an option to append the |
1500 | | * result at the back of the string. |
1501 | | */ |
1502 | | static void str_snprintf(pj_str_t *s, size_t size, |
1503 | | pj_bool_t append, const char *format, ...) |
1504 | 0 | { |
1505 | 0 | va_list arg; |
1506 | 0 | int retval; |
1507 | |
|
1508 | 0 | va_start(arg, format); |
1509 | 0 | if (!append) |
1510 | 0 | s->slen = 0; |
1511 | 0 | size -= s->slen; |
1512 | 0 | retval = pj_ansi_vsnprintf(s->ptr + s->slen, |
1513 | 0 | size, format, arg); |
1514 | 0 | if (retval < 0) { |
1515 | 0 | pj_assert(retval >= 0); |
1516 | 0 | retval = 0; |
1517 | 0 | } |
1518 | 0 | s->slen += (((size_t)retval < size) ? (size_t)retval : size - 1); |
1519 | 0 | va_end(arg); |
1520 | 0 | } Unexecuted instantiation: http_client.c:str_snprintf Unexecuted instantiation: fuzz-http.c:str_snprintf |
1521 | | |
1522 | | static pj_status_t http_req_start_sending(pj_http_req *hreq) |
1523 | 0 | { |
1524 | 0 | pj_status_t status; |
1525 | 0 | pj_str_t pkt; |
1526 | 0 | pj_ssize_t len; |
1527 | 0 | pj_size_t i; |
1528 | |
|
1529 | 0 | PJ_ASSERT_RETURN(hreq->state == SENDING_REQUEST || |
1530 | 0 | hreq->state == SENDING_REQUEST_BODY, PJ_EBUG); |
1531 | | |
1532 | 0 | if (hreq->state == SENDING_REQUEST) { |
1533 | | /* Prepare the request data */ |
1534 | 0 | if (!hreq->buffer.ptr) |
1535 | 0 | hreq->buffer.ptr = (char*)pj_pool_alloc(hreq->pool, BUF_SIZE); |
1536 | 0 | pj_strassign(&pkt, &hreq->buffer); |
1537 | 0 | pkt.slen = 0; |
1538 | | /* Start-line */ |
1539 | 0 | str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s %.*s %s/%.*s\r\n", |
1540 | 0 | STR_PREC(hreq->param.method), |
1541 | 0 | STR_PREC(hreq->hurl.path), |
1542 | 0 | get_protocol(&hreq->hurl.protocol), |
1543 | 0 | STR_PREC(hreq->param.version)); |
1544 | | /* Header field "Host" */ |
1545 | 0 | str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "Host: %.*s:%d\r\n", |
1546 | 0 | STR_PREC(hreq->hurl.host), hreq->hurl.port); |
1547 | 0 | if (!pj_strcmp2(&hreq->param.method, http_method_names[HTTP_PUT])) { |
1548 | 0 | char buf[16]; |
1549 | | |
1550 | | /* Header field "Content-Length" */ |
1551 | 0 | pj_utoa(hreq->param.reqdata.total_size ? |
1552 | 0 | (unsigned long)hreq->param.reqdata.total_size: |
1553 | 0 | (unsigned long)hreq->param.reqdata.size, buf); |
1554 | 0 | str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%s: %s\r\n", |
1555 | 0 | CONTENT_LENGTH, buf); |
1556 | 0 | } |
1557 | | |
1558 | | /* Append user-specified headers */ |
1559 | 0 | for (i = 0; i < hreq->param.headers.count; i++) { |
1560 | 0 | str_snprintf(&pkt, BUF_SIZE, PJ_TRUE, "%.*s: %.*s\r\n", |
1561 | 0 | STR_PREC(hreq->param.headers.header[i].name), |
1562 | 0 | STR_PREC(hreq->param.headers.header[i].value)); |
1563 | 0 | } |
1564 | 0 | if (pkt.slen >= BUF_SIZE - 1) { |
1565 | 0 | status = PJLIB_UTIL_EHTTPINSBUF; |
1566 | 0 | goto on_return; |
1567 | 0 | } |
1568 | | |
1569 | 0 | pj_strcat2(&pkt, "\r\n"); |
1570 | 0 | pkt.ptr[pkt.slen] = 0; |
1571 | 0 | TRACE_((THIS_FILE, "%s", pkt.ptr)); |
1572 | 0 | } else { |
1573 | 0 | pkt.ptr = (char*)hreq->param.reqdata.data; |
1574 | 0 | pkt.slen = hreq->param.reqdata.size; |
1575 | 0 | } |
1576 | | |
1577 | | /* Send the request */ |
1578 | 0 | len = pj_strlen(&pkt); |
1579 | 0 | pj_ioqueue_op_key_init(&hreq->op_key, sizeof(hreq->op_key)); |
1580 | 0 | hreq->tcp_state.send_size = len; |
1581 | 0 | hreq->tcp_state.current_send_size = 0; |
1582 | 0 | status = pj_activesock_send(hreq->asock, &hreq->op_key, |
1583 | 0 | pkt.ptr, &len, 0); |
1584 | |
|
1585 | 0 | if (status == PJ_SUCCESS) { |
1586 | 0 | http_on_data_sent(hreq->asock, &hreq->op_key, len); |
1587 | 0 | } else if (status != PJ_EPENDING) { |
1588 | 0 | goto on_return; // error sending data |
1589 | 0 | } |
1590 | | |
1591 | 0 | return PJ_SUCCESS; |
1592 | | |
1593 | 0 | on_return: |
1594 | 0 | http_req_end_request(hreq); |
1595 | 0 | return status; |
1596 | 0 | } Unexecuted instantiation: http_client.c:http_req_start_sending Unexecuted instantiation: fuzz-http.c:http_req_start_sending |
1597 | | |
1598 | | static pj_status_t http_req_start_reading(pj_http_req *hreq) |
1599 | 0 | { |
1600 | 0 | pj_status_t status; |
1601 | |
|
1602 | 0 | PJ_ASSERT_RETURN(hreq->state == REQUEST_SENT, PJ_EBUG); |
1603 | | |
1604 | | /* Receive the response */ |
1605 | 0 | hreq->state = READING_RESPONSE; |
1606 | 0 | hreq->tcp_state.current_read_size = 0; |
1607 | 0 | pj_assert(hreq->buffer.ptr); |
1608 | 0 | status = pj_activesock_start_read2(hreq->asock, hreq->pool, BUF_SIZE, |
1609 | 0 | (void**)&hreq->buffer.ptr, 0); |
1610 | 0 | if (status != PJ_SUCCESS) { |
1611 | | /* Error reading */ |
1612 | 0 | http_req_end_request(hreq); |
1613 | 0 | return status; |
1614 | 0 | } |
1615 | | |
1616 | 0 | return PJ_SUCCESS; |
1617 | 0 | } Unexecuted instantiation: http_client.c:http_req_start_reading Unexecuted instantiation: fuzz-http.c:http_req_start_reading |
1618 | | |
1619 | | static pj_status_t http_req_end_request(pj_http_req *hreq) |
1620 | 0 | { |
1621 | 0 | if (hreq->asock) { |
1622 | 0 | pj_activesock_close(hreq->asock); |
1623 | 0 | hreq->asock = NULL; |
1624 | 0 | } |
1625 | | |
1626 | | /* Cancel query timeout timer. */ |
1627 | 0 | if (hreq->timer_entry.id != 0) { |
1628 | 0 | pj_timer_heap_cancel(hreq->timer, &hreq->timer_entry); |
1629 | | /* Invalidate id. */ |
1630 | 0 | hreq->timer_entry.id = 0; |
1631 | 0 | } |
1632 | |
|
1633 | 0 | hreq->state = IDLE; |
1634 | |
|
1635 | 0 | return PJ_SUCCESS; |
1636 | 0 | } Unexecuted instantiation: http_client.c:http_req_end_request Unexecuted instantiation: fuzz-http.c:http_req_end_request |
1637 | | |
1638 | | PJ_DEF(pj_status_t) pj_http_req_cancel(pj_http_req *http_req, |
1639 | | pj_bool_t notify) |
1640 | 0 | { |
1641 | 0 | http_req->state = ABORTING; |
1642 | |
|
1643 | 0 | http_req_end_request(http_req); |
1644 | |
|
1645 | 0 | if (notify && http_req->cb.on_complete) { |
1646 | 0 | (*http_req->cb.on_complete)(http_req, (!http_req->error? |
1647 | 0 | PJ_ECANCELLED: http_req->error), NULL); |
1648 | 0 | } |
1649 | |
|
1650 | 0 | return PJ_SUCCESS; |
1651 | 0 | } |
1652 | | |
1653 | | |
1654 | | PJ_DEF(pj_status_t) pj_http_req_destroy(pj_http_req *http_req) |
1655 | 0 | { |
1656 | 0 | PJ_ASSERT_RETURN(http_req, PJ_EINVAL); |
1657 | | |
1658 | | /* If there is any pending request, cancel it */ |
1659 | 0 | if (http_req->state != IDLE) { |
1660 | 0 | pj_http_req_cancel(http_req, PJ_FALSE); |
1661 | 0 | } |
1662 | |
|
1663 | 0 | pj_pool_release(http_req->pool); |
1664 | |
|
1665 | 0 | return PJ_SUCCESS; |
1666 | 0 | } |