Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
diff src/http_request.c @ 0:d39e1d0d75b6
initial add
author | paulo@hit-nxdomain.opendns.com |
---|---|
date | Sat, 20 Feb 2010 21:18:28 -0800 |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/http_request.c Sat Feb 20 21:18:28 2010 -0800 1.3 @@ -0,0 +1,687 @@ 1.4 +/* 1.5 + * $Id: http_request.c,v 1.25 2005/01/04 15:03:41 mkern Exp $ 1.6 + * 1.7 + * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) 1.8 + * 1.9 + * This program is free software; you can redistribute it and/or modify it 1.10 + * under the terms of the GNU General Public License as published by the 1.11 + * Free Software Foundation; either version 2, or (at your option) any 1.12 + * later version. 1.13 + * 1.14 + * This program is distributed in the hope that it will be useful, but 1.15 + * WITHOUT ANY WARRANTY; without even the implied warranty of 1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1.17 + * General Public License for more details. 1.18 + */ 1.19 + 1.20 +#include "gt_gnutella.h" 1.21 +#include "gt_version.h" 1.22 + 1.23 +#include "gt_accept.h" 1.24 +#include "gt_web_cache.h" 1.25 + 1.26 +#include "http_request.h" 1.27 + 1.28 +/*****************************************************************************/ 1.29 + 1.30 +#define MAX_REDIRECTS (5) 1.31 + 1.32 +/*****************************************************************************/ 1.33 + 1.34 +static void decode_chunked_data (int fd, input_id id, TCPC *c); 1.35 +static void read_chunked_header (int fd, input_id id, TCPC *c); 1.36 + 1.37 +/*****************************************************************************/ 1.38 + 1.39 +/* 1.40 + * Dummy callbacks 1.41 + */ 1.42 + 1.43 +static void dummy_close (HttpRequest *r, int code) 1.44 +{ 1.45 + return; 1.46 +} 1.47 + 1.48 +static BOOL dummy_recv (HttpRequest *r, char *d, size_t l) 1.49 +{ 1.50 + return TRUE; 1.51 +} 1.52 + 1.53 +static BOOL dummy_add_header (HttpRequest *r, Dataset **d) 1.54 +{ 1.55 + return TRUE; 1.56 +} 1.57 + 1.58 +static BOOL dummy_redirect (HttpRequest *r, const char *h, const char *p) 1.59 +{ 1.60 + return TRUE; 1.61 +} 1.62 + 1.63 +/*****************************************************************************/ 1.64 + 1.65 +BOOL gt_http_url_parse (char *value, char **r_host, char **r_path) 1.66 +{ 1.67 + char *host_name; 1.68 + 1.69 + if (r_host) 1.70 + *r_host = NULL; 1.71 + if (r_path) 1.72 + *r_path = NULL; 1.73 + 1.74 + string_sep (&value, "http://"); 1.75 + 1.76 + /* divide the url in two parts */ 1.77 + host_name = string_sep (&value, "/"); 1.78 + 1.79 + if (r_host) 1.80 + *r_host = host_name; 1.81 + 1.82 + if (r_path) 1.83 + *r_path = STRING_NOTNULL (value); 1.84 + 1.85 + if (!host_name || host_name[0] == 0) 1.86 + return FALSE; 1.87 + 1.88 + return TRUE; 1.89 +} 1.90 + 1.91 +static void setup_dummy_functbl (HttpRequest *r) 1.92 +{ 1.93 + r->close_req_func = dummy_close; 1.94 + r->recv_func = dummy_recv; 1.95 + r->add_header_func = dummy_add_header; 1.96 + r->redirect_func = dummy_redirect; 1.97 +} 1.98 + 1.99 +HttpRequest *gt_http_request_new (const char *url, const char *request) 1.100 +{ 1.101 + HttpRequest *req; 1.102 + char *dup; 1.103 + char *host; 1.104 + char *path; 1.105 + 1.106 + if (!(dup = STRDUP (url))) 1.107 + return NULL; 1.108 + 1.109 + if (!gt_http_url_parse (dup, &host, &path) || 1.110 + !(req = MALLOC (sizeof (HttpRequest)))) 1.111 + { 1.112 + free (dup); 1.113 + return NULL; 1.114 + } 1.115 + 1.116 + req->host = STRDUP (host); 1.117 + req->path = STRDUP (path); 1.118 + req->request = STRDUP (request); 1.119 + req->timeout = 0; 1.120 + req->redirects = 0; 1.121 + req->headers = NULL; 1.122 + 1.123 + /* setup functbl */ 1.124 + setup_dummy_functbl (req); 1.125 + 1.126 + free (dup); 1.127 + 1.128 + return req; 1.129 +} 1.130 + 1.131 +static void gt_http_request_free (HttpRequest *req) 1.132 +{ 1.133 + if (!req) 1.134 + return; 1.135 + 1.136 + dataset_clear (req->headers); 1.137 + 1.138 + free (req->host); 1.139 + free (req->path); 1.140 + free (req->request); 1.141 + 1.142 + free (req); 1.143 +} 1.144 + 1.145 +void gt_http_request_close (HttpRequest *req, int error_code) 1.146 +{ 1.147 + /* notify the callback */ 1.148 + req->close_req_func (req, error_code); 1.149 + 1.150 + if (req->c) 1.151 + tcp_close (req->c); 1.152 + 1.153 + timer_remove_zero (&req->timeout); 1.154 + 1.155 + gt_http_request_free (req); 1.156 +} 1.157 + 1.158 +/*****************************************************************************/ 1.159 + 1.160 +static BOOL request_timeout (HttpRequest *req) 1.161 +{ 1.162 + GT->DBGFN (GT, "request to %s timed out", req->host); 1.163 + gt_http_request_close (req, -1); 1.164 + return FALSE; 1.165 +} 1.166 + 1.167 +void gt_http_request_set_timeout (HttpRequest *req, time_t time) 1.168 +{ 1.169 + if (!req) 1.170 + return; 1.171 + 1.172 + if (req->timeout) 1.173 + timer_remove (req->timeout); 1.174 + 1.175 + req->timeout = timer_add (time, (TimerCallback)request_timeout, req); 1.176 +} 1.177 + 1.178 +void gt_http_request_set_proxy (HttpRequest *req, const char *proxy) 1.179 +{ 1.180 + free (req->proxy); 1.181 + req->proxy = NULL; 1.182 + 1.183 + if (!proxy) 1.184 + return; 1.185 + 1.186 + req->proxy = STRDUP (proxy); 1.187 +} 1.188 + 1.189 +void gt_http_request_set_conn (HttpRequest *req, TCPC *c) 1.190 +{ 1.191 + assert (c->udata == NULL); 1.192 + assert (req->c == NULL); 1.193 + 1.194 + req->c = c; 1.195 + c->udata = req; 1.196 +} 1.197 + 1.198 +void gt_http_request_set_max_len (HttpRequest *req, size_t max_len) 1.199 +{ 1.200 + req->max_len = max_len; 1.201 +} 1.202 + 1.203 +/*****************************************************************************/ 1.204 + 1.205 +static BOOL write_data (HttpRequest *req, char *data, size_t len) 1.206 +{ 1.207 + if (!req) 1.208 + return FALSE; 1.209 + 1.210 + req->recvd_len += len; 1.211 + 1.212 + /* check if we overflowed the max length the user wants to receive */ 1.213 + if (req->max_len > 0 && req->recvd_len > req->max_len) 1.214 + { 1.215 + GT->DBGFN (GT, "%s sent %lu bytes overflowing max length of %lu", 1.216 + req->host, req->recvd_len, req->max_len); 1.217 + gt_http_request_close (req, -1); 1.218 + return FALSE; 1.219 + } 1.220 + 1.221 + /* send the data to the listener */ 1.222 + if (req->recv_func (req, data, len) == FALSE) 1.223 + { 1.224 + gt_http_request_close (req, -1); 1.225 + return FALSE; 1.226 + } 1.227 + 1.228 + return TRUE; 1.229 +} 1.230 + 1.231 +/*****************************************************************************/ 1.232 + 1.233 +static void write_header (ds_data_t *key, ds_data_t *value, String *s) 1.234 +{ 1.235 + char *header = key->data; 1.236 + char *field = value->data; 1.237 + 1.238 + string_appendf (s, "%s: %s\r\n", header, field); 1.239 +} 1.240 + 1.241 +static int http_send (TCPC *c, char *command, char *request, 1.242 + Dataset *headers) 1.243 +{ 1.244 + String *s; 1.245 + int ret; 1.246 + 1.247 + if (!command || !request) 1.248 + return -1; 1.249 + 1.250 + if (!(s = string_new (NULL, 0, 0, TRUE))) 1.251 + return -1; 1.252 + 1.253 + string_appendf (s, "%s %s HTTP/1.1\r\n", command, request); 1.254 + 1.255 + dataset_foreach (headers, DS_FOREACH(write_header), s); 1.256 + string_append (s, "\r\n"); 1.257 + 1.258 + GT->DBGSOCK (GT, c, "<http_request.c> sending:\n%s", s->str); 1.259 + 1.260 + ret = tcp_send (c, s->str, s->len); 1.261 + string_free (s); 1.262 + 1.263 + return ret; 1.264 +} 1.265 + 1.266 +static HttpRequest *get_request (TCPC *c) 1.267 +{ 1.268 + return c->udata; 1.269 +} 1.270 + 1.271 +/*****************************************************************************/ 1.272 + 1.273 +static void decode_chunked_data (int fd, input_id id, TCPC *c) 1.274 +{ 1.275 + HttpRequest *req; 1.276 + FDBuf *buf; 1.277 + char *data; 1.278 + int data_len = 0; 1.279 + int n; 1.280 + 1.281 + req = get_request (c); 1.282 + 1.283 + if (!req->size) 1.284 + { 1.285 + gt_http_request_close (req, 200); 1.286 + return; 1.287 + } 1.288 + 1.289 + buf = tcp_readbuf (c); 1.290 + 1.291 + if ((n = fdbuf_fill (buf, req->size)) < 0) 1.292 + { 1.293 + GT->DBGFN (GT, "error on host %s: %s", req->host, GIFT_NETERROR ()); 1.294 + gt_http_request_close (req, -1); 1.295 + return; 1.296 + } 1.297 + 1.298 + if (gt_fdbuf_full (buf)) 1.299 + { 1.300 + gt_http_request_close (req, -1); 1.301 + return; 1.302 + } 1.303 + 1.304 + if (n > 0) 1.305 + return; 1.306 + 1.307 + data = fdbuf_data (buf, &data_len); 1.308 + fdbuf_release (buf); 1.309 + 1.310 + if (!write_data (req, data, data_len)) 1.311 + return; 1.312 + 1.313 + input_remove (id); 1.314 + input_add (fd, c, INPUT_READ, 1.315 + (InputCallback)read_chunked_header, TIMEOUT_DEF); 1.316 +} 1.317 + 1.318 +static void read_chunked_header (int fd, input_id id, TCPC *c) 1.319 +{ 1.320 + HttpRequest *req; 1.321 + FDBuf *buf; 1.322 + char *response; 1.323 + int n; 1.324 + 1.325 + req = get_request (c); 1.326 + buf = tcp_readbuf (c); 1.327 + 1.328 + if ((n = fdbuf_delim (buf, "\n")) < 0) 1.329 + { 1.330 + GT->DBGFN (GT, "error on %s: %s", req->host, GIFT_NETERROR ()); 1.331 + gt_http_request_close (req, -1); 1.332 + return; 1.333 + } 1.334 + 1.335 + if (gt_fdbuf_full (buf)) 1.336 + { 1.337 + gt_http_request_close (req, -1); 1.338 + return; 1.339 + } 1.340 + 1.341 + if (n > 0) 1.342 + return; 1.343 + 1.344 + response = fdbuf_data (buf, NULL); 1.345 + fdbuf_release (buf); 1.346 + 1.347 + /* read the chunk size, its a hexadecimal integer */ 1.348 + req->size = strtoul (response, NULL, 16); 1.349 + GT->DBGFN (GT, "server sent chunk size of %lu", req->size); 1.350 + 1.351 + if (req->size == ULONG_MAX) 1.352 + { 1.353 + GT->DBGFN (GT, "overflow reading chunk size: %s", GIFT_STRERROR ()); 1.354 + gt_http_request_close (req, -1); 1.355 + return; 1.356 + } 1.357 + 1.358 + if (req->size == 0) 1.359 + { 1.360 + /* ok, done */ 1.361 + if (!write_data (req, NULL, 0)) 1.362 + return; 1.363 + 1.364 + /* there could be a CRLF at the end. should we read it? 1.365 + * To avoid screwing up persistent http, yes.. */ 1.366 + gt_http_request_close (req, 200); 1.367 + return; 1.368 + } 1.369 + 1.370 + input_remove (id); 1.371 + input_add (fd, c, INPUT_READ, 1.372 + (InputCallback)decode_chunked_data, TIMEOUT_DEF); 1.373 +} 1.374 + 1.375 +/* read the amount of data specified by Content-Length: */ 1.376 +static void read_file (int fd, input_id id, TCPC *c) 1.377 +{ 1.378 + HttpRequest *req; 1.379 + FDBuf *buf; 1.380 + int n; 1.381 + size_t len; 1.382 + unsigned char *data; 1.383 + 1.384 + req = get_request (c); 1.385 + 1.386 + if (!req->size) 1.387 + { 1.388 + gt_http_request_close (req, 200); 1.389 + return; 1.390 + } 1.391 + 1.392 + buf = tcp_readbuf (c); 1.393 + 1.394 + if ((n = fdbuf_fill (buf, req->size)) < 0) 1.395 + { 1.396 + GT->DBGFN (GT, "error from %s: %s", req->host, GIFT_NETERROR ()); 1.397 + gt_http_request_close (req, -1); 1.398 + return; 1.399 + } 1.400 + 1.401 + if (n > 0) 1.402 + return; 1.403 + 1.404 + data = fdbuf_data (buf, &len); 1.405 + fdbuf_release (buf); 1.406 + 1.407 + if (!write_data (req, data, len)) 1.408 + return; 1.409 + 1.410 + /* 1.411 + * We've read all the data, the total length of the request being provided 1.412 + * by fdbuf_fill(). Now send the closing notification to our callback. 1.413 + */ 1.414 + if (!write_data (req, NULL, 0)) 1.415 + return; 1.416 + 1.417 + /* success */ 1.418 + gt_http_request_close (req, 200); 1.419 +} 1.420 + 1.421 +/* callback to read when no Content-Length: header is provided */ 1.422 +static void read_until_eof (int fd, input_id id, TCPC *c) 1.423 +{ 1.424 + char data[RW_BUFFER]; 1.425 + int n; 1.426 + HttpRequest *req; 1.427 + 1.428 + req = get_request (c); 1.429 + 1.430 + if ((n = tcp_recv (c, data, sizeof (data) - 1)) < 0) 1.431 + { 1.432 + GT->DBGFN (GT, "error from %s: %s", req->host, GIFT_NETERROR()); 1.433 + gt_http_request_close (req, -1); 1.434 + return; 1.435 + } 1.436 + 1.437 + /* terminate the buffer */ 1.438 + data[n] = 0; 1.439 + 1.440 + if (n == 0) 1.441 + { 1.442 + /* signal to the listener that EOF was reached */ 1.443 + if (!write_data (req, NULL, 0)) 1.444 + return; 1.445 + 1.446 + gt_http_request_close (req, 200); 1.447 + return; 1.448 + } 1.449 + 1.450 + if (!write_data (req, data, n)) 1.451 + return; 1.452 +} 1.453 + 1.454 +static void reset_request (HttpRequest *req, const char *host, 1.455 + const char *path) 1.456 +{ 1.457 + free (req->host); 1.458 + free (req->path); 1.459 + req->host = STRDUP (host); 1.460 + req->path = STRDUP (path); 1.461 + 1.462 + dataset_clear (req->headers); 1.463 + req->headers = NULL; 1.464 +} 1.465 + 1.466 +/* 1.467 + * This will do a limited redirect on the same connection. 1.468 + * One bug is it doesn't care if the Location header posts a different port, 1.469 + */ 1.470 +static void handle_redirect (HttpRequest *req, int code) 1.471 +{ 1.472 + char *new_host; 1.473 + char *new_path; 1.474 + char *location; 1.475 + 1.476 + /* make sure the Location: header points to the same host */ 1.477 + location = dataset_lookupstr (req->headers, "location"); 1.478 + 1.479 + /* butchers Location header, but it will be freed soon anyway */ 1.480 + if (!location || 1.481 + !gt_http_url_parse (location, &new_host, &new_path)) 1.482 + { 1.483 + gt_http_request_close (req, code); 1.484 + return; 1.485 + } 1.486 + 1.487 + assert (new_host != NULL); 1.488 + 1.489 + if (++req->redirects >= MAX_REDIRECTS) 1.490 + { 1.491 + GT->DBGSOCK (GT, req->c, "Too many redirects"); 1.492 + gt_http_request_close (req, code); 1.493 + return; 1.494 + } 1.495 + 1.496 + /* 1.497 + * Let the caller know we're redirecting so it can reset it's ancilliary 1.498 + * data. 1.499 + */ 1.500 + if (req->redirect_func (req, new_host, new_path) == FALSE) 1.501 + { 1.502 + gt_http_request_close (req, code); 1.503 + return; 1.504 + } 1.505 + 1.506 + /* setup the new request */ 1.507 + reset_request (req, new_host, new_path); 1.508 + 1.509 + /* restart the request */ 1.510 + input_remove_all (req->c->fd); 1.511 + input_add (req->c->fd, req->c, INPUT_WRITE, 1.512 + (InputCallback)gt_http_request_handle, TIMEOUT_DEF); 1.513 +} 1.514 + 1.515 +static BOOL parse_server_response (char *reply, HttpRequest *req) 1.516 +{ 1.517 + char *response; 1.518 + int code; /* 200, 404, ... */ 1.519 + 1.520 + response = string_sep (&reply, "\r\n"); 1.521 + 1.522 + if (!response) 1.523 + return FALSE; 1.524 + 1.525 + /* */ string_sep (&response, " "); /* shift past HTTP/1.1 */ 1.526 + code = ATOI (string_sep (&response, " ")); /* shift past 200 */ 1.527 + 1.528 + /* parse the headers */ 1.529 + gt_http_header_parse (reply, &req->headers); 1.530 + 1.531 + if (code >= 200 && code <= 299) 1.532 + return TRUE; 1.533 + 1.534 + /* redirection */ 1.535 + if (code >= 300 && code <= 399) 1.536 + { 1.537 + handle_redirect (req, code); 1.538 + return FALSE; /* stop this request */ 1.539 + } 1.540 + 1.541 + /* request error: could blacklist the server in recv_callback */ 1.542 + GT->DBGFN (GT, "error parsing response from %s, closing", req->host); 1.543 + gt_http_request_close (req, code); 1.544 + 1.545 + return FALSE; 1.546 +} 1.547 + 1.548 +static void read_headers (int fd, input_id id, TCPC *c) 1.549 +{ 1.550 + HttpRequest *req; 1.551 + FDBuf *buf; 1.552 + char *response; 1.553 + size_t response_len = 0; 1.554 + char *encoding; 1.555 + char *len_str; 1.556 + int n; 1.557 + 1.558 + req = get_request (c); 1.559 + buf = tcp_readbuf (c); 1.560 + 1.561 + if ((n = fdbuf_delim (buf, "\n")) < 0) 1.562 + { 1.563 + GT->DBGFN (GT, "error reading from %s: %s", net_peer_ip (c->fd), 1.564 + GIFT_NETERROR ()); 1.565 + gt_http_request_close (req, -1); 1.566 + return; 1.567 + } 1.568 + 1.569 + if (gt_fdbuf_full (buf)) 1.570 + { 1.571 + gt_http_request_close (req, -1); 1.572 + return; 1.573 + } 1.574 + 1.575 + if (n > 0) 1.576 + return; 1.577 + 1.578 + response = fdbuf_data (buf, &response_len); 1.579 + 1.580 + if (response_len >= req->max_len) 1.581 + { 1.582 + GT->DBGFN (GT, "headers too large(%lu)", (long)response_len); 1.583 + gt_http_request_close (req, -1); 1.584 + } 1.585 + 1.586 + if (!gt_http_header_terminated (response, response_len)) 1.587 + return; 1.588 + 1.589 + fdbuf_release (buf); 1.590 + GT->DBGFN (GT, "response=\n%s", response); 1.591 + 1.592 + if (!parse_server_response (response, req)) 1.593 + return; 1.594 + 1.595 + input_remove (id); 1.596 + 1.597 + encoding = dataset_lookupstr (req->headers, "transfer-encoding"); 1.598 + 1.599 + if (encoding && !strcasecmp (encoding, "chunked")) 1.600 + { 1.601 + input_add (fd, c, INPUT_READ, 1.602 + (InputCallback)read_chunked_header, TIMEOUT_DEF); 1.603 + return; 1.604 + } 1.605 + 1.606 + if (!(len_str = dataset_lookupstr (req->headers, "content-length"))) 1.607 + { 1.608 + GT->warn (GT, "no Content-Length header from %s", req->host); 1.609 + input_add (fd, c, INPUT_READ, 1.610 + (InputCallback)read_until_eof, TIMEOUT_DEF); 1.611 + return; 1.612 + } 1.613 + 1.614 + req->size = ATOUL (len_str); 1.615 + 1.616 + if (req->max_len > 0 && req->size >= req->max_len) 1.617 + { 1.618 + GT->DBGFN (GT, "bad size (%s) in content length field for %s", 1.619 + len_str, req->host); 1.620 + gt_http_request_close (req, -1); 1.621 + return; 1.622 + } 1.623 + 1.624 + input_add (fd, c, INPUT_READ, 1.625 + (InputCallback)read_file, TIMEOUT_DEF); 1.626 +} 1.627 + 1.628 +/* 1.629 + * Determine the part after the GET. If proxied, this need to be a complete 1.630 + * URL, and otherwise should be a simple path. 1.631 + */ 1.632 +static void append_request_line (String *s, HttpRequest *req) 1.633 +{ 1.634 + if (req->proxy) 1.635 + string_appendf (s, "http://%s", req->host); 1.636 + 1.637 + string_appendf (s, "/%s", STRING_NOTNULL(req->path)); 1.638 +} 1.639 + 1.640 +static int send_request (HttpRequest *req) 1.641 +{ 1.642 + Dataset *headers = NULL; 1.643 + String *s; 1.644 + int ret; 1.645 + 1.646 + if (!(s = string_new (NULL, 0, 0, TRUE))) 1.647 + return -1; 1.648 + 1.649 + append_request_line (s, req); 1.650 + 1.651 + if (!string_isempty (req->request)) 1.652 + string_appendf (s, "?%s", req->request); 1.653 + 1.654 + dataset_insertstr (&headers, "Host", req->host); /* required by HTTP/1.1 */ 1.655 + dataset_insertstr (&headers, "User-Agent", gt_version ()); 1.656 + 1.657 + if (req->add_header_func (req, &headers) == FALSE) 1.658 + { 1.659 + /* Hmm, this is our error, what should the error code be */ 1.660 + gt_http_request_close (req, -1); 1.661 + dataset_clear (headers); 1.662 + string_free (s); 1.663 + return -1; 1.664 + } 1.665 + 1.666 + ret = http_send (req->c, "GET", s->str, headers); 1.667 + 1.668 + dataset_clear (headers); 1.669 + string_free (s); 1.670 + 1.671 + return ret; 1.672 +} 1.673 + 1.674 +void gt_http_request_handle (int fd, input_id id, TCPC *c) 1.675 +{ 1.676 + HttpRequest *req; 1.677 + 1.678 + req = get_request (c); 1.679 + 1.680 + if (send_request (req) <= 0) 1.681 + { 1.682 + GT->DBGFN (GT, "send failed: %s", GIFT_NETERROR()); 1.683 + gt_http_request_close (req, -1); 1.684 + return; 1.685 + } 1.686 + 1.687 + input_remove (id); 1.688 + input_add (fd, c, INPUT_READ, 1.689 + (InputCallback)read_headers, TIMEOUT_DEF); 1.690 +}