paulo@0: /* paulo@0: * $Id: gt_http_client.c,v 1.57 2006/02/03 20:11:40 mkern Exp $ paulo@0: * paulo@0: * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) paulo@0: * paulo@0: * This program is free software; you can redistribute it and/or modify it paulo@0: * under the terms of the GNU General Public License as published by the paulo@0: * Free Software Foundation; either version 2, or (at your option) any paulo@0: * later version. paulo@0: * paulo@0: * This program is distributed in the hope that it will be useful, but paulo@0: * WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU paulo@0: * General Public License for more details. paulo@0: */ paulo@0: paulo@0: #include "gt_gnutella.h" paulo@0: #include "gt_version.h" paulo@0: paulo@0: #include "gt_xfer_obj.h" paulo@0: #include "gt_xfer.h" paulo@0: paulo@0: #include "gt_http_client.h" paulo@0: #include "gt_http_server.h" paulo@0: paulo@0: #include "gt_accept.h" paulo@0: paulo@0: #include "transfer/source.h" paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* prototyping this function effectively provides the non-blocking flow of the paulo@0: * program and helps to self-document this file */ paulo@0: static void get_server_reply (int fd, input_id id, GtTransfer *xfer); paulo@0: paulo@0: /*****************************************************************************/ paulo@0: /* CLIENT HELPERS */ paulo@0: paulo@0: static int gt_http_client_send (TCPC *c, char *command, char *request, ...) paulo@0: { paulo@0: char *key; paulo@0: char *value; paulo@0: String *s; paulo@0: int ret; paulo@0: va_list args; paulo@0: paulo@0: if (!command || !request) paulo@0: return -1; paulo@0: paulo@0: if (!(s = string_new (NULL, 0, 0, TRUE))) paulo@0: return -1; paulo@0: paulo@0: string_appendf (s, "%s %s HTTP/1.1\r\n", command, request); paulo@0: paulo@0: va_start (args, request); paulo@0: paulo@0: for (;;) paulo@0: { paulo@0: /* if we receive the sentinel, bail out */ paulo@0: if (!(key = va_arg (args, char *))) paulo@0: break; paulo@0: paulo@0: if (!(value = va_arg (args, char *))) paulo@0: continue; paulo@0: paulo@0: string_appendf (s, "%s: %s\r\n", key, value); paulo@0: } paulo@0: paulo@0: va_end (args); paulo@0: paulo@0: /* append final message terminator */ paulo@0: string_append (s, "\r\n"); paulo@0: paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "sending client request:\n%s", s->str); paulo@0: paulo@0: ret = tcp_send (c, s->str, s->len); paulo@0: string_free (s); paulo@0: paulo@0: return ret; paulo@0: } paulo@0: paulo@0: /* parse an HTTP server reply */ paulo@0: static BOOL parse_server_reply (GtTransfer *xfer, TCPC *c, char *reply) paulo@0: { paulo@0: char *response; /* HTTP/1.1 200 OK */ paulo@0: char *version; paulo@0: int code; /* 200, 404, ... */ paulo@0: paulo@0: if (!xfer || !reply) paulo@0: return FALSE; paulo@0: paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "reply:\n%s", reply); paulo@0: paulo@0: response = string_sep_set (&reply, "\r\n"); paulo@0: paulo@0: if (!response) paulo@0: return FALSE; paulo@0: paulo@0: version = string_sep (&response, " "); /* shift past HTTP/1.1 */ paulo@0: code = ATOI (string_sep (&response, " ")); /* shift past 200 */ paulo@0: paulo@0: /* parse the rest of the key/value fields */ paulo@0: gt_http_header_parse (reply, &xfer->header); paulo@0: paulo@0: xfer->code = code; paulo@0: xfer->version = STRDUP (version); paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Complete interface to the standard HTTP GET / with a server. These routines paulo@0: * require non of OpenFT's extensions and communicate perfectly valid paulo@0: * HTTP/1.1 (to my knowledge). You could use this to transfer non-OpenFT, paulo@0: * hopefully. ;) paulo@0: * paulo@0: * NOTE: paulo@0: * I need to add more text here so this stands out better as one of the two paulo@0: * core subsystems within this file. So here it is. :P paulo@0: */ paulo@0: void gt_http_client_get (Chunk *chunk, GtTransfer *xfer) paulo@0: { paulo@0: TCPC *c; paulo@0: paulo@0: if (!chunk || !xfer) paulo@0: { paulo@0: GT->DBGFN (GT, "uhm."); paulo@0: return; paulo@0: } paulo@0: paulo@0: xfer->command = STRDUP ("GET"); paulo@0: paulo@0: if (!(c = gt_http_connection_open (GT_TRANSFER_DOWNLOAD, xfer->ip, paulo@0: xfer->port))) paulo@0: { paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* pass along the connection with the xfer */ paulo@0: gt_transfer_set_tcpc (xfer, c); paulo@0: assert (xfer->chunk == chunk); paulo@0: assert (chunk->udata == xfer); paulo@0: paulo@0: gt_transfer_status (xfer, SOURCE_WAITING, "Connecting"); paulo@0: paulo@0: /* be a little more aggressive timing out HTTP connections (TIMEOUT_DEF / paulo@0: * 2 + 5), so that useless sources don't occupy Chunks so often */ paulo@0: input_add (c->fd, xfer, INPUT_WRITE, paulo@0: (InputCallback)gt_http_client_start, TIMEOUT_DEF / 2 + 5); paulo@0: } paulo@0: paulo@0: static int client_get_request (GtTransfer *xfer) paulo@0: { paulo@0: TCPC *c; paulo@0: char host[128]; paulo@0: char range_hdr[64]; paulo@0: int ret; paulo@0: paulo@0: if (!xfer) paulo@0: return FALSE; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: paulo@0: snprintf (range_hdr, sizeof (range_hdr) - 1, "bytes=%i-%i", paulo@0: (int) xfer->start, (int) xfer->stop - 1); paulo@0: paulo@0: snprintf (host, sizeof (host) - 1, "%s:%hu", net_ip_str (xfer->ip), paulo@0: xfer->port); paulo@0: paulo@0: /* always send the Range request just because we always know the full size paulo@0: * we want */ paulo@0: ret = gt_http_client_send (c, "GET", xfer->request, paulo@0: "Range", range_hdr, paulo@0: "Host", host, paulo@0: "User-Agent", gt_version(), paulo@0: "X-Queue", "0.1", paulo@0: NULL); paulo@0: paulo@0: return ret; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Verify connection status and Send the GET request to the server. paulo@0: */ paulo@0: void gt_http_client_start (int fd, input_id id, GtTransfer *xfer) paulo@0: { paulo@0: Chunk *chunk; paulo@0: TCPC *c; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: GtSource *gt; paulo@0: paulo@0: gt = gt_transfer_get_source (xfer); paulo@0: paulo@0: /* set the connection as having failed, and retry w/ a push */ paulo@0: gt->connect_failed = TRUE; paulo@0: paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, (fd == -1 ? paulo@0: "Connect timeout" : paulo@0: "Connect failed")); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Update the length of the chunk in the GtTransfer. We do this paulo@0: * because giftd may change the range of the chunk while we are paulo@0: * playing with it, and this is the last point where we can update paulo@0: * the range. paulo@0: * paulo@0: * If giftd changes the range after this point, we'll be forced to break paulo@0: * this connection :( paulo@0: */ paulo@0: gt_transfer_set_length (xfer, chunk); paulo@0: paulo@0: /* send the GET / request to the server */ paulo@0: if (client_get_request (xfer) <= 0) paulo@0: { paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, "GET send failed"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: gt_transfer_status (xfer, SOURCE_WAITING, "Sent HTTP request"); paulo@0: paulo@0: /* do not remove all fds associated with this socket until we destroy it */ paulo@0: input_remove (id); paulo@0: paulo@0: /* wait for the server response */ paulo@0: input_add (fd, xfer, INPUT_READ, paulo@0: (InputCallback)get_server_reply, TIMEOUT_DEF); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Read the response body, if any, so persistent HTTP will work. paulo@0: * Note that the GtTransfer can still timeout, in which case paulo@0: * the connection will get closed and so will the xfer. paulo@0: */ paulo@0: static void read_response_body (int fd, input_id id, GtTransfer *xfer) paulo@0: { paulo@0: Chunk *chunk; paulo@0: TCPC *c; paulo@0: FDBuf *buf; paulo@0: char *response; paulo@0: int n; paulo@0: int len; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: len = xfer->stop - xfer->start; paulo@0: paulo@0: /* since the body isnt important, close if its too large */ paulo@0: if (len >= 16384) paulo@0: { paulo@0: GT->DBGFN (GT, "[%s:%hu] response body too large (%d)", paulo@0: net_ip_str (xfer->ip), xfer->port); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: buf = tcp_readbuf (c); paulo@0: paulo@0: if ((n = fdbuf_fill (buf, len)) < 0) paulo@0: { paulo@0: GT->DBGFN (GT, "error [%s:%hu]: %s", paulo@0: net_ip_str (xfer->ip), xfer->port, GIFT_NETERROR ()); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (n > 0) paulo@0: return; paulo@0: paulo@0: /* paulo@0: * Set the body as having been completely read. paulo@0: * This allows the connection to be cached. paulo@0: */ paulo@0: xfer->remaining_len -= len; paulo@0: assert (xfer->remaining_len == 0); paulo@0: paulo@0: response = fdbuf_data (buf, NULL); paulo@0: fdbuf_release (buf); paulo@0: paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "body:\n%s", response); paulo@0: paulo@0: input_remove (id); paulo@0: paulo@0: /* perform an orderly close */ paulo@0: gt_transfer_close (xfer, FALSE); paulo@0: } paulo@0: paulo@0: static time_t queue_interval (time_t interval) paulo@0: { paulo@0: /* paulo@0: * HACK: giftd will retry the queued download every 49 seconds, paulo@0: * so round the next retry time to coincide with that interval. paulo@0: */ paulo@0: if (interval > 49) paulo@0: interval = (interval / 49 + 1) * 49; paulo@0: paulo@0: return interval; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* set the next time to retry from the Retry-After: header */ paulo@0: static void set_retry_after (GtTransfer *xfer) paulo@0: { paulo@0: int seconds; paulo@0: char *retry_after; paulo@0: GtSource *gt; paulo@0: #if 0 paulo@0: char *msg; paulo@0: struct tm *tm; paulo@0: #endif paulo@0: paulo@0: if (!(retry_after = dataset_lookupstr (xfer->header, "retry-after"))) paulo@0: return; paulo@0: paulo@0: /* paulo@0: * This can be either a HTTP date or a number of seconds. We only paulo@0: * support the number of seconds right now. paulo@0: */ paulo@0: seconds = ATOI (retry_after); paulo@0: paulo@0: if (seconds <= 0) paulo@0: return; paulo@0: paulo@0: if (!(gt = gt_transfer_get_source (xfer))) paulo@0: return; paulo@0: paulo@0: /* set the retry time for the next download */ paulo@0: gt->retry_time = time (NULL) + queue_interval (seconds); paulo@0: paulo@0: #if 0 paulo@0: /* get the absolute time */ paulo@0: tm = localtime (>->retry_time); paulo@0: paulo@0: /* let the user know when we are going to retry */ paulo@0: msg = stringf_dup ("Queued (retry at %d:%02d:%02d)", tm->tm_hour, paulo@0: tm->tm_min, tm->tm_sec); paulo@0: paulo@0: gt_transfer_status (xfer, SOURCE_QUEUED_REMOTE, msg); paulo@0: free (msg); paulo@0: #endif paulo@0: } paulo@0: paulo@0: /* paulo@0: * Check for both the active-queueing style "X-Queue:" and PARQ-style paulo@0: * "X-Queued:". paulo@0: * paulo@0: * We avoid having to parse the X-Queue: line in the PARQ case (which would paulo@0: * be "1.0") by allowing X-Queued to override X-Queue. paulo@0: */ paulo@0: static size_t find_queue_key (Dataset *header, const char *key) paulo@0: { paulo@0: size_t pos = 0; paulo@0: char *val; paulo@0: char *line0, *line; paulo@0: char *active_queue_line; paulo@0: char *parq_queue_line; paulo@0: char *sep; paulo@0: paulo@0: active_queue_line = dataset_lookupstr (header, "x-queue"); paulo@0: parq_queue_line = dataset_lookupstr (header, "x-queued"); paulo@0: paulo@0: if (!active_queue_line && !parq_queue_line) paulo@0: return 0; paulo@0: paulo@0: line = active_queue_line; paulo@0: sep = ", "; paulo@0: paulo@0: if (parq_queue_line) paulo@0: { paulo@0: line = parq_queue_line; paulo@0: sep = "; "; paulo@0: } paulo@0: paulo@0: line0 = line = STRDUP (line); paulo@0: paulo@0: while ((val = string_sep_set (&line, sep))) paulo@0: { paulo@0: char *str; paulo@0: paulo@0: str = string_sep_set (&val, "= "); paulo@0: paulo@0: if (!str || !val) paulo@0: continue; paulo@0: paulo@0: if (!strcasecmp (str, key)) paulo@0: pos = ATOI (val); paulo@0: } paulo@0: paulo@0: free (line0); paulo@0: return pos; paulo@0: } paulo@0: paulo@0: /* Create a message describing our position in the remote node's paulo@0: * upload queue. */ paulo@0: static char *get_queue_status (GtTransfer *xfer, char *msg) paulo@0: { paulo@0: size_t len = 0; paulo@0: size_t pos = 0; paulo@0: paulo@0: pos = find_queue_key (xfer->header, "position"); paulo@0: len = find_queue_key (xfer->header, "length"); paulo@0: paulo@0: msg = STRDUP (msg); paulo@0: paulo@0: if (pos != 0) paulo@0: { paulo@0: free (msg); paulo@0: paulo@0: if (len != 0) paulo@0: msg = stringf_dup ("Queued (%u/%u)", pos, len); paulo@0: else paulo@0: msg = stringf_dup ("Queued (position %u)", pos); paulo@0: } paulo@0: paulo@0: return msg; paulo@0: } paulo@0: paulo@0: /* set the next time to retry the download based on the X-Queue: field */ paulo@0: static void set_queue_status (GtTransfer *xfer) paulo@0: { paulo@0: GtSource *gt; paulo@0: char *queue_line; paulo@0: int poll_min; paulo@0: paulo@0: if (!(gt = gt_transfer_get_source (xfer))) paulo@0: return; paulo@0: paulo@0: if (!(queue_line = dataset_lookupstr (xfer->header, "x-queue"))) paulo@0: return; paulo@0: paulo@0: if ((poll_min = find_queue_key (xfer->header, "pollmin")) <= 0) paulo@0: return; paulo@0: paulo@0: gt->retry_time = time (NULL) + queue_interval (poll_min); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Try to read any content-body in the response, so that persistent paulo@0: * HTTP can work correctly, which is really important for push downloads. paulo@0: */ paulo@0: static void handle_http_error (GtTransfer *xfer, SourceStatus status, paulo@0: char *status_txt) paulo@0: { paulo@0: TCPC *c; paulo@0: Chunk *chunk; paulo@0: int len = 0; paulo@0: char *content_len; paulo@0: char *conn_hdr; paulo@0: paulo@0: /* update the interface protocol with the error status of this xfer */ paulo@0: status_txt = get_queue_status (xfer, status_txt); paulo@0: gt_transfer_status (xfer, status, status_txt); paulo@0: paulo@0: free (status_txt); paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: /* paulo@0: * Check for a Content-Length: field, and use that for the paulo@0: * length of the response body, if any. paulo@0: */ paulo@0: content_len = dataset_lookupstr (xfer->header, "content-length"); paulo@0: conn_hdr = dataset_lookupstr (xfer->header, "connection"); paulo@0: paulo@0: /* look at the Retry-After: header and set download retry time */ paulo@0: set_retry_after (xfer); paulo@0: paulo@0: /* parse the X-Queue: and X-Queued: headers for the queue position/ paulo@0: * retry time */ paulo@0: set_queue_status (xfer); paulo@0: paulo@0: /* paulo@0: * Don't read the body if they supplied "Connection: Close". paulo@0: * Close if the HTTP version is 1.0 (TODO: this should paulo@0: * not close 1.0 connections if they supplied Connection: paulo@0: * Keep-Alive, I guess) paulo@0: */ paulo@0: if (!STRCASECMP (xfer->version, "HTTP/1.0") || paulo@0: !STRCASECMP (xfer->version, "HTTP") || paulo@0: !STRCASECMP (conn_hdr, "close")) paulo@0: { paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (content_len) paulo@0: len = ATOUL (content_len); paulo@0: paulo@0: /* abuse xfer->{start,stop} fields for the length of the response body */ paulo@0: xfer->start = 0; paulo@0: xfer->stop = len; paulo@0: paulo@0: /* set this flag to let gt_transfer_close() know the headers paulo@0: * have been parsed */ paulo@0: xfer->transmitted_hdrs = TRUE; paulo@0: paulo@0: /* set the length so that if the connection times out, it will be paulo@0: * force closed when we havent read the entire body */ paulo@0: xfer->remaining_len = len; paulo@0: paulo@0: /* if there is no length to read, we are done */ paulo@0: if (len == 0) paulo@0: { paulo@0: gt_transfer_close (xfer, FALSE); paulo@0: return; paulo@0: } paulo@0: paulo@0: input_remove_all (c->fd); paulo@0: input_add (c->fd, xfer, INPUT_READ, paulo@0: (InputCallback)read_response_body, TIMEOUT_DEF); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Process an HTTP return code (either client or server [push]) and attempt paulo@0: * to appropriately handle/expunge the transfer structure accordingly. The paulo@0: * return value indicates whether or not we may continue after the code paulo@0: * has been processed. Some error codes (404) are considered fatal here paulo@0: * and you should abort after this call. paulo@0: * paulo@0: * NOTE: paulo@0: * If this function returns FALSE, the calling argument is free'd and you paulo@0: * should not access it further. paulo@0: */ paulo@0: int gt_http_handle_code (GtTransfer *xfer, int code) paulo@0: { paulo@0: TCPC *c; paulo@0: Chunk *chunk; paulo@0: GtSource *gt; paulo@0: paulo@0: /* successful code, do nothing */ paulo@0: if (code >= 200 && code <= 299) paulo@0: return TRUE; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: gt = gt_transfer_get_source (xfer); paulo@0: assert (gt != NULL); paulo@0: paulo@0: /* paulo@0: * We have to be careful not to access the transfer or any paulo@0: * data related to it after calling p->source_abort, which paulo@0: * will cancel the transfer. paulo@0: */ paulo@0: switch (code) paulo@0: { paulo@0: default: paulo@0: case 404: /* not found */ paulo@0: { paulo@0: /* paulo@0: * If this was a uri-res request, retry with an index request. paulo@0: * Otherwise, remove the source. paulo@0: * paulo@0: * TODO: perhaps this should retry with url-encoded index paulo@0: * request, before removing? paulo@0: */ paulo@0: if (gt->uri_res_failed) paulo@0: { paulo@0: GT->source_abort (GT, chunk->transfer, xfer->source); paulo@0: } paulo@0: else paulo@0: { paulo@0: handle_http_error (xfer, SOURCE_QUEUED_REMOTE, paulo@0: "File not found"); paulo@0: gt->uri_res_failed = TRUE; paulo@0: } paulo@0: } paulo@0: break; paulo@0: case 401: /* unauthorized */ paulo@0: handle_http_error (xfer, SOURCE_CANCELLED, "Access denied"); paulo@0: break; paulo@0: case 503: /* remotely queued */ paulo@0: handle_http_error (xfer, SOURCE_QUEUED_REMOTE, "Queued (Remotely)"); paulo@0: break; paulo@0: case 500: /* source may not match, check later */ paulo@0: /* paulo@0: * The remote node has reported that this file has changed paulo@0: * since the last time we received results for it. This more paulo@0: * than likely indicates a hash change, in which case we should paulo@0: * not keep this associated with this transfer. Unfortunately, paulo@0: * giFT fails to provide any sort of abstraction for us paulo@0: * to remove sources ourselves, so we need this hopefully paulo@0: * temporary hack to remove the source. paulo@0: */ paulo@0: GT->source_abort (GT, chunk->transfer, xfer->source); paulo@0: break; paulo@0: } paulo@0: paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: static int parse_content_range (char *range, off_t *r_start, off_t *r_end, paulo@0: off_t *r_size) paulo@0: { paulo@0: char *start, *end, *size; paulo@0: paulo@0: string_sep (&range, "bytes"); paulo@0: string_sep_set (&range, " ="); /* accept "bytes=" and "bytes " */ paulo@0: paulo@0: if (r_end) paulo@0: *r_end = -1; paulo@0: if (r_start) paulo@0: *r_start = -1; paulo@0: if (r_size) paulo@0: *r_size = -1; paulo@0: paulo@0: if (!range) paulo@0: return FALSE; paulo@0: paulo@0: start = string_sep (&range, "-"); paulo@0: end = string_sep (&range, "/"); paulo@0: size = range; paulo@0: paulo@0: if (r_start && start) paulo@0: *r_start = ATOUL (start); paulo@0: if (r_end && end) paulo@0: *r_end = ATOUL (end); paulo@0: if (r_size && size) paulo@0: *r_size = ATOUL (size); paulo@0: paulo@0: if (start && end && size) paulo@0: return TRUE; paulo@0: paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: static int verify_range_response (GtTransfer *xfer, Chunk *chunk) paulo@0: { paulo@0: char *user_agent; paulo@0: char *content_range; paulo@0: char *content_len; paulo@0: off_t start, stop, size; paulo@0: #if 0 paulo@0: off_t file_size; paulo@0: #endif paulo@0: off_t xfer_size; paulo@0: int error = FALSE; paulo@0: paulo@0: #if 0 paulo@0: file_size = chunk->transfer->total; paulo@0: #endif paulo@0: xfer_size = xfer->stop - xfer->start; paulo@0: paulo@0: if ((content_len = dataset_lookupstr (xfer->header, "content-length"))) paulo@0: { paulo@0: size = ATOUL (content_len); paulo@0: paulo@0: if (size != xfer_size) paulo@0: { paulo@0: GIFT_ERROR (("bad content len=%lu, expected %lu", size, xfer_size)); paulo@0: error = TRUE; paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, "Bad Content-Length"); paulo@0: } paulo@0: } paulo@0: paulo@0: if ((content_range = dataset_lookupstr (xfer->header, "content-range"))) paulo@0: { paulo@0: if (HTTP_DEBUG) paulo@0: { paulo@0: GT->dbg (GT, "Content-Range: %s, start=%lu, stop=%lu", paulo@0: content_range, chunk->start, chunk->stop); paulo@0: } paulo@0: paulo@0: if (parse_content_range (content_range, &start, &stop, &size)) paulo@0: { paulo@0: if (start != xfer->start) paulo@0: { paulo@0: GIFT_ERROR (("bad xfer start: %lu %lu", xfer->start, start)); paulo@0: error = TRUE; paulo@0: } paulo@0: if (stop != xfer->stop - 1) paulo@0: { paulo@0: GIFT_ERROR (("bad xfer end: %lu %lu", xfer->stop, stop)); paulo@0: error = TRUE; paulo@0: } paulo@0: #if 0 paulo@0: if (size != file_size) paulo@0: { paulo@0: GIFT_ERROR (("bad xfer size: %lu, expected %lu", file_size, paulo@0: size)); paulo@0: error = TRUE; paulo@0: } paulo@0: #endif paulo@0: } paulo@0: else paulo@0: { paulo@0: GIFT_ERROR (("error parsing content-range hdr")); paulo@0: error = TRUE; paulo@0: } paulo@0: } paulo@0: paulo@0: if (!content_len && !content_range) paulo@0: { paulo@0: if (!(user_agent = dataset_lookupstr (xfer->header, "Server"))) paulo@0: user_agent = dataset_lookupstr (xfer->header, "User-Agent"); paulo@0: paulo@0: GIFT_ERROR (("missing Content-Range/Length, start=%lu, stop=%lu, " paulo@0: "culprit=%s", xfer->start, xfer->stop, user_agent)); paulo@0: paulo@0: error = TRUE; paulo@0: } paulo@0: paulo@0: if (error) paulo@0: { paulo@0: GT->DBGFN (GT, "removing source %s", chunk->source->url); paulo@0: GT->source_abort (GT, chunk->transfer, chunk->source); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Receive and process the HTTP response paulo@0: */ paulo@0: static void get_server_reply (int fd, input_id id, GtTransfer *xfer) paulo@0: { paulo@0: Chunk *chunk; paulo@0: TCPC *c; paulo@0: GtSource *gt; paulo@0: FDBuf *buf; paulo@0: unsigned char *data; paulo@0: char *msg; paulo@0: size_t data_len = 0; paulo@0: int n; paulo@0: paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: paulo@0: gt = gt_transfer_get_source (xfer); paulo@0: paulo@0: buf = tcp_readbuf (c); paulo@0: paulo@0: /* attempt to read the complete server response */ paulo@0: if ((n = fdbuf_delim (buf, "\n")) < 0) paulo@0: { paulo@0: msg = "Timed out"; paulo@0: paulo@0: /* paulo@0: * If the peer abruptly closed the connection, its possible it paulo@0: * didn't understand our request if it was a uri-res request. paulo@0: * So, disable uri-res in that case. paulo@0: */ paulo@0: if (fd != -1) paulo@0: { paulo@0: gt->uri_res_failed = TRUE; paulo@0: msg = "Connection closed"; paulo@0: } paulo@0: paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, msg); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (gt_fdbuf_full (buf)) paulo@0: { paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (n > 0) paulo@0: return; paulo@0: paulo@0: data = fdbuf_data (buf, &data_len); paulo@0: paulo@0: if (!gt_http_header_terminated (data, data_len)) paulo@0: return; paulo@0: paulo@0: fdbuf_release (buf); paulo@0: paulo@0: /* parse the server response */ paulo@0: if (!parse_server_reply (xfer, c, data)) paulo@0: { paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, "Malformed HTTP header"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * NOTE: if we wanted to do any further processing of the server reply paulo@0: * after GET /, this is where it would be paulo@0: */ paulo@0: paulo@0: /* determine what to do with the HTTP code reply */ paulo@0: if (!gt_http_handle_code (xfer, xfer->code)) paulo@0: return; paulo@0: paulo@0: /* make sure the server understood our "Range:" request */ paulo@0: if (!verify_range_response (xfer, chunk)) paulo@0: return; paulo@0: paulo@0: /* paulo@0: * Received HTTP headers, ...and now we are waiting for the file. This paulo@0: * should be a very short wait :) paulo@0: */ paulo@0: gt_transfer_status (xfer, SOURCE_WAITING, "Received HTTP headers"); paulo@0: xfer->transmitted_hdrs = TRUE; paulo@0: paulo@0: /* special case if the request size is 0 */ paulo@0: if (xfer->remaining_len == 0) paulo@0: { paulo@0: gt_transfer_close (xfer, FALSE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* disable the header read timeout */ paulo@0: timer_remove_zero (&xfer->header_timer); paulo@0: paulo@0: /* wait for the file to be sent by the server */ paulo@0: input_remove (id); paulo@0: input_add (fd, xfer, INPUT_READ, paulo@0: (InputCallback)gt_get_read_file, 0); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Receive the requested file from the server. paulo@0: */ paulo@0: void gt_get_read_file (int fd, input_id id, GtTransfer *xfer) paulo@0: { paulo@0: TCPC *c; paulo@0: Chunk *chunk; paulo@0: char buf[RW_BUFFER]; paulo@0: size_t size; paulo@0: int recv_len; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: /* set the max initial size of the request */ paulo@0: size = sizeof (buf); paulo@0: paulo@0: /* paulo@0: * Cap the size of the received length at the length paulo@0: * of the request. paulo@0: * paulo@0: * This way we can't receive part of the next request paulo@0: * header with persistent HTTP. paulo@0: */ paulo@0: if (size > xfer->remaining_len) paulo@0: size = xfer->remaining_len; paulo@0: paulo@0: /* paulo@0: * Ask giFT for the max size we should read. If this returns 0, the paulo@0: * download was suspended. paulo@0: */ paulo@0: if ((size = download_throttle (chunk, size)) == 0) paulo@0: return; paulo@0: paulo@0: if ((recv_len = tcp_recv (c, buf, size)) <= 0) paulo@0: { paulo@0: GT->DBGFN (GT, "tcp_recv error (%d) from %s:%hu: %s", recv_len, paulo@0: net_ip_str (c->host), c->port, GIFT_NETERROR()); paulo@0: paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, "Cancelled remotely"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * We are receiving a file here, so this will always be calling paulo@0: * gt_download. paulo@0: */ paulo@0: gt_transfer_write (xfer, chunk, buf, (size_t)recv_len); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void client_reset_timeout (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: /* normally we would call recv () here but there's no reason why the server paulo@0: * should be sending stuff down to use...so, disconnect */ paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "closing client HTTP connection"); paulo@0: paulo@0: gt_http_connection_close (GT_TRANSFER_DOWNLOAD, c, TRUE); paulo@0: } paulo@0: paulo@0: void gt_http_client_reset (TCPC *c) paulo@0: { paulo@0: /* This can happen when the GtTransfer and TCPC have been decoupled, paulo@0: * such as when we are waiting for a GIV response from a node. */ paulo@0: if (!c) paulo@0: return; paulo@0: paulo@0: tcp_flush (c, TRUE); paulo@0: paulo@0: input_remove_all (c->fd); paulo@0: input_add (c->fd, c, INPUT_READ, paulo@0: (InputCallback)client_reset_timeout, 2 * MINUTES); paulo@0: }