paulo@0: /* paulo@0: * $Id: gt_http_server.c,v 1.73 2005/01/04 15:03: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: paulo@0: #include "gt_xfer_obj.h" paulo@0: #include "gt_xfer.h" paulo@0: paulo@0: #include "gt_http_server.h" paulo@0: #include "gt_http_client.h" paulo@0: paulo@0: #include "gt_accept.h" paulo@0: #include "gt_ban.h" paulo@0: #include "gt_version.h" paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* convenient shorthand */ paulo@0: #define CONTENT_URN_FIELD "X-Gnutella-Content-URN" paulo@0: paulo@0: #define INCOMING_TIMEOUT (1 * MINUTES) paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: struct http_incoming paulo@0: { paulo@0: TCPC *c; paulo@0: timer_id timer; paulo@0: }; paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void server_handle_get (GtTransfer *xfer); paulo@0: static void get_client_request (int fd, input_id id, paulo@0: struct http_incoming *http); paulo@0: static void send_http_response (int fd, input_id id, GtTransfer *xfer); paulo@0: paulo@0: /*****************************************************************************/ paulo@0: /* SERVER HELPERS */ paulo@0: paulo@0: static char *lookup_http_code (int code, char **desc) paulo@0: { paulo@0: char *err; paulo@0: char *txt; paulo@0: paulo@0: switch (code) paulo@0: { paulo@0: case 200: err = "OK"; paulo@0: txt = "Success"; paulo@0: break; paulo@0: case 206: err = "Partial Content"; paulo@0: txt = "Resume successful"; paulo@0: break; paulo@0: case 403: err = "Forbidden"; paulo@0: txt = "You do not have access to this file"; paulo@0: break; paulo@0: case 404: err = "Not Found"; paulo@0: txt = "File is not available"; paulo@0: break; paulo@0: case 500: err = "Internal Server Error"; paulo@0: txt = "Stale file entry, retry later"; paulo@0: break; paulo@0: case 501: err = "Not Implemented"; paulo@0: txt = "???"; paulo@0: break; paulo@0: case 503: err = "Service Unavailable"; paulo@0: txt = "Upload queue is currently full, please try again later"; paulo@0: break; paulo@0: default: err = NULL; paulo@0: txt = NULL; paulo@0: break; paulo@0: } paulo@0: paulo@0: if (desc) paulo@0: *desc = txt; paulo@0: paulo@0: return err; paulo@0: } paulo@0: paulo@0: static String *alloc_header (int code) paulo@0: { paulo@0: char *code_text; paulo@0: String *s; paulo@0: paulo@0: /* so that we can communicate both the numerical code and the human paulo@0: * readable string */ paulo@0: if (!(code_text = lookup_http_code (code, NULL))) paulo@0: return FALSE; paulo@0: paulo@0: if (!(s = string_new (NULL, 0, 0, TRUE))) paulo@0: return FALSE; paulo@0: paulo@0: string_appendf (s, "HTTP/1.1 %i %s\r\n", code, code_text); paulo@0: paulo@0: return s; paulo@0: } paulo@0: paulo@0: static void construct_header_va (String *s, int code, va_list args) paulo@0: { paulo@0: char *key; paulo@0: char *value; paulo@0: paulo@0: /* Add "Server: " header */ paulo@0: string_appendf (s, "Server: %s\r\n", gt_version ()); paulo@0: paulo@0: for (;;) paulo@0: { 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: /* append final message terminator */ paulo@0: string_append (s, "\r\n"); paulo@0: } paulo@0: paulo@0: static String *construct_header (int code, ...) paulo@0: { paulo@0: String *s; paulo@0: va_list args; paulo@0: paulo@0: if (!(s = alloc_header (code))) paulo@0: return NULL; paulo@0: paulo@0: va_start (args, code); paulo@0: construct_header_va (s, code, args); paulo@0: va_end (args); paulo@0: paulo@0: return s; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Construct and send a server reply. paulo@0: */ paulo@0: static BOOL gt_http_server_send (TCPC *c, int code, ...) paulo@0: { paulo@0: String *s; paulo@0: int ret; paulo@0: size_t len; paulo@0: va_list args; paulo@0: paulo@0: if (!(s = alloc_header (code))) paulo@0: return FALSE; paulo@0: paulo@0: va_start (args, code); paulo@0: paulo@0: construct_header_va (s, code, args); paulo@0: paulo@0: va_end (args); paulo@0: paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str); paulo@0: paulo@0: len = s->len; paulo@0: ret = tcp_send (c, s->str, s->len); paulo@0: paulo@0: string_free (s); paulo@0: paulo@0: return (ret == len); paulo@0: } paulo@0: paulo@0: static char *get_error_page (GtTransfer *xfer, int code) paulo@0: { paulo@0: char *page; paulo@0: char *err; paulo@0: char *errtxt = NULL; paulo@0: paulo@0: if (!(err = lookup_http_code (code, &errtxt))) paulo@0: return 0; paulo@0: paulo@0: page = stringf ("

%i %s


%s.", code, err, errtxt); paulo@0: paulo@0: return page; paulo@0: } paulo@0: paulo@0: static BOOL supports_queue (GtTransfer *xfer) paulo@0: { paulo@0: char *features; paulo@0: paulo@0: if (dataset_lookupstr (xfer->header, "x-queue")) paulo@0: return TRUE; paulo@0: paulo@0: if ((features = dataset_lookupstr (xfer->header, "x-features"))) paulo@0: { paulo@0: /* XXX: case-sensitive */ paulo@0: if (strstr (features, "queue")) paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: static char *get_queue_line (GtTransfer *xfer) paulo@0: { paulo@0: String *s; paulo@0: paulo@0: /* do nothing if not queued */ paulo@0: if (xfer->queue_pos == 0) paulo@0: return NULL; paulo@0: paulo@0: if (!(s = string_new (NULL, 0, 0, TRUE))) paulo@0: return NULL; paulo@0: paulo@0: string_appendf (s, "position=%d,length=%d,pollMin=%d,pollMax=%d", paulo@0: xfer->queue_pos, xfer->queue_ttl, 45, 120); paulo@0: paulo@0: return string_free_keep (s); paulo@0: } paulo@0: paulo@0: static String *get_error_header (GtTransfer *xfer, int code, paulo@0: const char *error_page) paulo@0: { paulo@0: size_t len; paulo@0: char content_len[256]; paulo@0: char *queue_line = NULL; paulo@0: char *content_type = "text/html"; paulo@0: String *s; paulo@0: paulo@0: len = strlen (error_page); paulo@0: snprintf (content_len, sizeof (content_len), "%u", len); paulo@0: paulo@0: if (code == 503 && supports_queue (xfer)) paulo@0: queue_line = get_queue_line (xfer); paulo@0: paulo@0: /* don't send a content-type header if there is no entity body */ paulo@0: if (len == 0) paulo@0: content_type = NULL; paulo@0: paulo@0: s = construct_header (code, paulo@0: "Content-Type", content_type, paulo@0: "Content-Length", content_len, paulo@0: CONTENT_URN_FIELD, xfer->content_urns, paulo@0: "X-Queue", queue_line, paulo@0: NULL); paulo@0: paulo@0: free (queue_line); paulo@0: return s; paulo@0: } paulo@0: paulo@0: static void send_error_reply (int fd, input_id id, GtTransfer *xfer) paulo@0: { paulo@0: String *s; paulo@0: const char *error_page; paulo@0: int ret; paulo@0: TCPC *c; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: paulo@0: if (!(error_page = get_error_page (xfer, xfer->code))) paulo@0: { paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * If the remote end supports queueing or supplied paulo@0: * "X-Gnutella-Content-URN",, it's a Gnutella client and we don't want to paulo@0: * keep sending the error page, because over many requests from this paulo@0: * client the bandwidth could add up. paulo@0: */ paulo@0: if (supports_queue (xfer) || paulo@0: dataset_lookupstr (xfer->header, "x-gnutella-content-urn")) paulo@0: { paulo@0: error_page = ""; /* empty */ paulo@0: } paulo@0: paulo@0: if (!(s = get_error_header (xfer, xfer->code, error_page))) paulo@0: { paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: string_append (s, error_page); paulo@0: paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str); paulo@0: paulo@0: /* send the whole page at once */ paulo@0: ret = tcp_send (c, s->str, s->len); paulo@0: paulo@0: /* if the whole thing was sent keep the connection open */ paulo@0: if (ret == s->len) paulo@0: { paulo@0: xfer->transmitted_hdrs = TRUE; paulo@0: xfer->remaining_len = 0; paulo@0: } paulo@0: paulo@0: string_free (s); paulo@0: paulo@0: gt_transfer_close (xfer, FALSE); paulo@0: } paulo@0: paulo@0: static void gt_http_server_send_error_and_close (GtTransfer *xfer, int code) paulo@0: { paulo@0: TCPC *c; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: paulo@0: xfer->code = code; paulo@0: paulo@0: input_remove_all (c->fd); paulo@0: input_add (c->fd, xfer, INPUT_WRITE, paulo@0: (InputCallback)send_error_reply, TIMEOUT_DEF); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* parse the Range: bytes=0-10000 format */ paulo@0: static void parse_client_request_range (Dataset *dataset, paulo@0: off_t *r_start, off_t *r_stop) paulo@0: { paulo@0: char *range; paulo@0: off_t start; paulo@0: off_t stop; paulo@0: paulo@0: if (!r_start && !r_stop) paulo@0: return; paulo@0: paulo@0: if (r_start) paulo@0: *r_start = 0; paulo@0: if (r_stop) paulo@0: *r_stop = 0; paulo@0: paulo@0: /* leave stop as 0 if we can't figure anything out yet. This is expected paulo@0: * to be handled separately by GET and PUSH */ paulo@0: if (!(range = dataset_lookupstr (dataset, "range"))) paulo@0: return; paulo@0: paulo@0: /* WARNING: this butchers the data in the dataset! */ paulo@0: string_sep (&range, "bytes"); paulo@0: string_sep_set (&range, " ="); paulo@0: paulo@0: if (!range) paulo@0: { paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGFN (GT, "error parsing Range: header"); paulo@0: paulo@0: return; paulo@0: } paulo@0: paulo@0: start = (off_t) ATOI (string_sep (&range, "-")); paulo@0: stop = (off_t) ATOI (string_sep (&range, " ")); paulo@0: paulo@0: /* paulo@0: * The end of the range is optional (e.g. "Range: 0-"), and in that case paulo@0: * stop == 0. In the case of a single byte file, stop == 1. paulo@0: * paulo@0: * TODO: this is broken for one-byte requests at the start of the file. paulo@0: */ paulo@0: if (stop > 0) paulo@0: stop = stop + 1; paulo@0: paulo@0: if (r_start) paulo@0: *r_start = start; paulo@0: if (r_stop) paulo@0: *r_stop = stop; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Parse requests of the forms: paulo@0: * paulo@0: * /get/47/File maybe with spaces HTTP paulo@0: * /get/47/files_%20_url_encoded HTTP/1.1 paulo@0: * /uri-res/N2R?urn:sha1:ABCD HTTP/1.0 paulo@0: * paulo@0: * and combinations thereof. The "HTTP" trailer is mandatory. paulo@0: */ paulo@0: static void get_request_and_version (char *data, char **request, paulo@0: char **version) paulo@0: { paulo@0: size_t len; paulo@0: char *next; paulo@0: char *dup; paulo@0: char *http_version = NULL; paulo@0: paulo@0: *request = NULL; paulo@0: *version = NULL; paulo@0: paulo@0: /* trim whitespace inbetween command and request */ paulo@0: string_trim (data); paulo@0: paulo@0: if (!(dup = STRDUP (data))) paulo@0: return; paulo@0: paulo@0: string_upper (dup); paulo@0: paulo@0: next = dup; paulo@0: paulo@0: /* find the last instance of "HTTP" in the string */ paulo@0: while ((next = strstr (next, "HTTP"))) paulo@0: { paulo@0: http_version = next; paulo@0: next += sizeof ("HTTP") - 1; paulo@0: } paulo@0: paulo@0: /* the rest of the string must be the request */ paulo@0: if (http_version != NULL && http_version != dup) paulo@0: { paulo@0: len = http_version - dup; paulo@0: data[len - 1] = 0; paulo@0: paulo@0: *request = data; paulo@0: *version = data + len; paulo@0: } paulo@0: paulo@0: free (dup); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Break down the clients HTTP request paulo@0: */ paulo@0: static int parse_client_request (Dataset **r_dataset, char **r_command, paulo@0: char **r_request, char **r_version, paulo@0: off_t *r_start, off_t *r_stop, char *hdr) paulo@0: { paulo@0: Dataset *dataset = NULL; paulo@0: char *command; /* GET */ paulo@0: char *request; /* /file.tar.gz */ paulo@0: char *version; /* HTTP/1.1 */ paulo@0: char *req_line; paulo@0: paulo@0: if (!hdr) paulo@0: return FALSE; paulo@0: paulo@0: /* paulo@0: * Get the first line of the request paulo@0: */ paulo@0: req_line = string_sep_set (&hdr, "\r\n"); paulo@0: paulo@0: /* get the command (GET, HEAD, etc.) */ paulo@0: command = string_sep (&req_line, " "); paulo@0: paulo@0: /* paulo@0: * Handle non-url-encoded requests as well as encoded paulo@0: * ones and get the request and version from this HTTP line. paulo@0: */ paulo@0: get_request_and_version (req_line, &request, &version); paulo@0: paulo@0: if (HTTP_DEBUG) paulo@0: { paulo@0: GT->DBGFN (GT, "command=%s version=%s request=%s", paulo@0: command, version, request); paulo@0: } paulo@0: paulo@0: if (!request || string_isempty (request)) paulo@0: return FALSE; paulo@0: paulo@0: if (r_command) paulo@0: *r_command = command; paulo@0: if (r_request) paulo@0: *r_request = request; paulo@0: if (r_version) paulo@0: *r_version = version; paulo@0: paulo@0: gt_http_header_parse (hdr, &dataset); paulo@0: paulo@0: if (r_dataset) paulo@0: *r_dataset = dataset; paulo@0: paulo@0: /* handle Range: header */ paulo@0: parse_client_request_range (dataset, r_start, r_stop); paulo@0: paulo@0: if (r_start && r_stop) paulo@0: { paulo@0: if (HTTP_DEBUG) paulo@0: GT->dbg (GT, "range: [%i, %i)", *r_start, *r_stop); paulo@0: } paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Send the request reply back to the client paulo@0: * paulo@0: * NOTE: paulo@0: * This is used by both GET / and PUSH / paulo@0: */ paulo@0: static void reply_to_client_request (GtTransfer *xfer) paulo@0: { paulo@0: TCPC *c; paulo@0: Chunk *chunk; paulo@0: off_t entity_size; paulo@0: char range[128]; paulo@0: char length[32]; paulo@0: BOOL ret; paulo@0: paulo@0: if (!xfer) paulo@0: return; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: /* paulo@0: * Determine the "total" entity body that we have locally, not necessarily paulo@0: * the data that we are uploading. HTTP demands this, but OpenFT really paulo@0: * doesn't give a shit. paulo@0: * paulo@0: * NOTE: paulo@0: * This only works to standard when operating on a GET / request, PUSH's paulo@0: * merely use the range! paulo@0: */ paulo@0: if (xfer->open_path_size) paulo@0: entity_size = xfer->open_path_size; paulo@0: else paulo@0: entity_size = xfer->stop - xfer->start; paulo@0: paulo@0: /* NOTE: we are "working" around the fact that HTTP's Content-Range paulo@0: * reply is inclusive for the last byte, whereas giFT's is not. */ paulo@0: snprintf (range, sizeof (range) - 1, "bytes %i-%i/%i", paulo@0: (int) xfer->start, (int) (xfer->stop - 1), (int) entity_size); paulo@0: paulo@0: snprintf (length, sizeof (length) - 1, "%i", paulo@0: (int) (xfer->stop - xfer->start)); paulo@0: paulo@0: ret = gt_http_server_send (c, xfer->code, paulo@0: "Content-Range", range, paulo@0: "Content-Length", length, paulo@0: "Content-Type", xfer->content_type, paulo@0: CONTENT_URN_FIELD, xfer->content_urns, paulo@0: NULL); paulo@0: paulo@0: /* if we transmitted all headers successfully, set transmitted_hdrs paulo@0: * to keep the connection alive, possibly */ paulo@0: if (ret) paulo@0: xfer->transmitted_hdrs = TRUE; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void http_incoming_free (struct http_incoming *incoming) paulo@0: { paulo@0: timer_remove (incoming->timer); paulo@0: free (incoming); paulo@0: } paulo@0: paulo@0: static void http_incoming_close (struct http_incoming *incoming) paulo@0: { paulo@0: gt_http_connection_close (GT_TRANSFER_UPLOAD, incoming->c, TRUE); paulo@0: http_incoming_free (incoming); paulo@0: } paulo@0: paulo@0: static BOOL http_incoming_timeout (struct http_incoming *incoming) paulo@0: { paulo@0: http_incoming_close (incoming); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: static struct http_incoming *http_incoming_alloc (TCPC *c) paulo@0: { paulo@0: struct http_incoming *incoming; paulo@0: paulo@0: incoming = malloc (sizeof (struct http_incoming)); paulo@0: if (!incoming) paulo@0: return NULL; paulo@0: paulo@0: incoming->c = c; paulo@0: incoming->timer = timer_add (INCOMING_TIMEOUT, paulo@0: (TimerCallback)http_incoming_timeout, paulo@0: incoming); paulo@0: paulo@0: return incoming; paulo@0: } paulo@0: paulo@0: void gt_http_server_dispatch (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: struct http_incoming *incoming; paulo@0: paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (!(incoming = http_incoming_alloc (c))) paulo@0: { paulo@0: gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* keep track of this incoming connection */ paulo@0: /* gt_http_connection_insert (GT_TRANSFER_UPLOAD, c); */ paulo@0: paulo@0: input_remove (id); paulo@0: input_add (c->fd, incoming, INPUT_READ, paulo@0: (InputCallback)get_client_request, 0); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Handle the client's GET commands. paulo@0: */ paulo@0: static void get_client_request (int fd, input_id id, struct http_incoming *http) paulo@0: { paulo@0: GtTransfer *xfer; paulo@0: TCPC *c; paulo@0: Dataset *dataset = NULL; paulo@0: char *command = NULL; paulo@0: char *request = NULL; paulo@0: char *version = NULL; paulo@0: off_t start = 0; paulo@0: off_t stop = 0; paulo@0: FDBuf *buf; paulo@0: unsigned char *data; paulo@0: size_t data_len = 0; paulo@0: int n; paulo@0: paulo@0: c = http->c; paulo@0: buf = tcp_readbuf (c); paulo@0: paulo@0: if ((n = fdbuf_delim (buf, "\n")) < 0) paulo@0: { paulo@0: http_incoming_close (http); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (gt_fdbuf_full (buf)) paulo@0: { paulo@0: http_incoming_close (http); 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: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "client request:\n%s", data); paulo@0: paulo@0: /* parse the client's request and determine how we should proceed */ paulo@0: if (!parse_client_request (&dataset, &command, &request, &version, paulo@0: &start, &stop, data)) paulo@0: { paulo@0: GT->DBGSOCK (GT, c, "invalid http header"); paulo@0: http_incoming_close (http); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* discard incoming connection timeout maintainance structure */ paulo@0: http_incoming_free (http); paulo@0: paulo@0: /* paulo@0: * We have enough information now to actually allocate the transfer paulo@0: * structure and pass it along to all logic that follows this paulo@0: * paulo@0: * NOTE: paulo@0: * Each individual handler can determine when it wants to let giFT paulo@0: * in on this paulo@0: */ paulo@0: xfer = gt_transfer_new (GT_TRANSFER_UPLOAD, NULL, paulo@0: net_peer (c->fd), 0, start, stop); paulo@0: paulo@0: /* connect the connection and the xfer in unholy matrimony */ paulo@0: gt_transfer_set_tcpc (xfer, c); paulo@0: paulo@0: /* assign all our own memory */ paulo@0: xfer->command = STRDUP (command); paulo@0: xfer->header = dataset; paulo@0: xfer->version = STRDUP (version); paulo@0: paulo@0: if (!gt_transfer_set_request (xfer, request)) paulo@0: { paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "invalid request \"s\"", request); paulo@0: paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* no need for this function again */ paulo@0: input_remove (id); paulo@0: paulo@0: /* figure out how to handle this request */ paulo@0: if (!strcasecmp (xfer->command, "GET") || paulo@0: !strcasecmp (xfer->command, "HEAD")) paulo@0: { paulo@0: server_handle_get (xfer); paulo@0: return; paulo@0: } paulo@0: paulo@0: gt_http_server_send_error_and_close (xfer, 501); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static Transfer *start_upload (GtTransfer *xfer, Chunk **chunk) paulo@0: { paulo@0: Transfer *transfer; paulo@0: char *user; paulo@0: paulo@0: user = net_ip_str (xfer->ip); paulo@0: paulo@0: transfer = GT->upload_start (GT, chunk, user, xfer->share_authd, paulo@0: xfer->start, xfer->stop); paulo@0: paulo@0: assert (transfer != NULL); paulo@0: paulo@0: return transfer; paulo@0: } paulo@0: paulo@0: /* setup the structure for uploading. this will be called from within paulo@0: * client space for PUSH requests as well */ paulo@0: int gt_server_setup_upload (GtTransfer *xfer) paulo@0: { paulo@0: Transfer *transfer; /* giFT structure */ paulo@0: Chunk *chunk; paulo@0: TCPC *c; paulo@0: paulo@0: if (!xfer) paulo@0: return FALSE; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: assert (xfer->chunk == NULL); paulo@0: paulo@0: /* paulo@0: * Ban the host if they don't have access -- this gives no information paulo@0: * about whether we have the file or not, and i think this is in violation paulo@0: * of the HTTP spec (supposed to return "404 not found" before 403, but paulo@0: * i'm not sure. paulo@0: */ paulo@0: if (gt_ban_ipv4_is_banned (c->host)) paulo@0: { paulo@0: xfer->code = 403; paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: /* open the file that was requested before we go bringing giFT into paulo@0: * this */ paulo@0: if (!(xfer->f = gt_transfer_open_request (xfer, &xfer->code))) paulo@0: return FALSE; paulo@0: paulo@0: /* assign stop a value before we proceed */ paulo@0: if (xfer->stop == 0) paulo@0: { paulo@0: struct stat st; paulo@0: paulo@0: if (!file_stat (xfer->open_path, &st) || st.st_size == 0) paulo@0: { paulo@0: /* stupid bastards have a 0 length file */ paulo@0: GT->DBGSOCK (GT, c, "cannot satisfy %s: invalid share", paulo@0: xfer->open_path); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: xfer->stop = st.st_size; paulo@0: xfer->remaining_len = xfer->stop - xfer->start; paulo@0: } paulo@0: paulo@0: /* we can now be certain that we are handling a download request from paulo@0: * the client. allocate the appropriate structures to hook into giFT */ paulo@0: if (!(transfer = start_upload (xfer, &chunk))) paulo@0: { paulo@0: GT->DBGFN (GT, "unable to register upload with the daemon"); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: /* override 200 w/ 206 if the request is not the whole file size */ paulo@0: if (xfer->remaining_len != xfer->share_authd->size) paulo@0: xfer->code = 206; paulo@0: paulo@0: /* assign the circular references for passing the chunk along */ paulo@0: gt_transfer_set_chunk (xfer, chunk); paulo@0: paulo@0: /* finally, seek the file descriptor where it needs to be */ paulo@0: fseek (xfer->f, xfer->start, SEEK_SET); paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: static void server_handle_get (GtTransfer *xfer) paulo@0: { paulo@0: TCPC *c; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: assert (xfer->chunk == NULL); paulo@0: paulo@0: /* WARNING: this block is duplicated in http_client:client_push_request */ paulo@0: if (!gt_server_setup_upload (xfer)) paulo@0: { paulo@0: if (xfer->code == 200) paulo@0: xfer->code = 404; paulo@0: paulo@0: gt_http_server_send_error_and_close (xfer, xfer->code); paulo@0: return; paulo@0: } paulo@0: paulo@0: input_add (c->fd, xfer, INPUT_WRITE, paulo@0: (InputCallback)send_http_response, TIMEOUT_DEF); paulo@0: } paulo@0: paulo@0: static void send_http_response (int fd, input_id id, GtTransfer *xfer) paulo@0: { paulo@0: TCPC *c; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* ok, send client the header */ paulo@0: reply_to_client_request (xfer); paulo@0: paulo@0: if (!strcasecmp (xfer->command, "HEAD")) paulo@0: { paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* disable header read timer */ paulo@0: timer_remove_zero (&xfer->header_timer); paulo@0: paulo@0: input_remove (id); paulo@0: input_add (c->fd, xfer, INPUT_WRITE, paulo@0: (InputCallback)gt_server_upload_file, 0); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Uploads the file requests paulo@0: */ paulo@0: void gt_server_upload_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 read_len; paulo@0: size_t size; paulo@0: int sent_len = 0; paulo@0: off_t remainder; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: assert (xfer->f != NULL); paulo@0: paulo@0: /* number of bytes left to be uploaded by this chunk */ paulo@0: if ((remainder = xfer->remaining_len) <= 0) paulo@0: { paulo@0: /* for whatever reason this function may have been called when we have paulo@0: * already overrun the transfer...in that case we will simply fall paulo@0: * through to the end-of-transfer condition */ paulo@0: gt_transfer_write (xfer, chunk, NULL, 0); paulo@0: return; paulo@0: } paulo@0: paulo@0: size = sizeof (buf); paulo@0: paulo@0: if (size > remainder) paulo@0: size = remainder; paulo@0: paulo@0: /* paulo@0: * Ask giFT for the size we should send. If this returns 0, the upload paulo@0: * was suspended. paulo@0: */ paulo@0: if ((size = upload_throttle (chunk, size)) == 0) paulo@0: return; paulo@0: paulo@0: /* read as much as we can from the local file */ paulo@0: if (!(read_len = fread (buf, sizeof (char), size, xfer->f))) paulo@0: { paulo@0: GT->DBGFN (GT, "unable to read from %s: %s", xfer->open_path, paulo@0: GIFT_STRERROR ()); paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, "Local read error"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: if ((sent_len = tcp_send (c, buf, MIN (read_len, remainder))) <= 0) paulo@0: { paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, paulo@0: "Unable to send data block"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* check if the file was too small for the transfer TODO: this isn't paulo@0: * checked earlier, but should be */ paulo@0: if (read_len != size) paulo@0: { paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, "Unexpected end of file"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Check for short send(). This could use fseek(), but I have this paulo@0: * growing feeling that using stdio everywhere is a bad idea. paulo@0: */ paulo@0: if (read_len != sent_len) paulo@0: { paulo@0: gt_transfer_status (xfer, SOURCE_CANCELLED, "Short send()"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Call gt_upload to report back to giFT. This will also cancel paulo@0: * the transfer if the upload has completed. paulo@0: */ paulo@0: gt_transfer_write (xfer, chunk, buf, sent_len); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: void gt_http_server_reset (TCPC *c) paulo@0: { paulo@0: /* paulo@0: * This can happen because the GtTransfer and TCPC can be decoupled, as in paulo@0: * the case of a push request sent. paulo@0: */ paulo@0: if (!c) paulo@0: return; paulo@0: paulo@0: /* finish all queued writes before we reset */ paulo@0: tcp_flush (c, TRUE); paulo@0: paulo@0: /* reset the input state */ paulo@0: input_remove_all (c->fd); paulo@0: input_add (c->fd, c, INPUT_READ, paulo@0: (InputCallback)gt_http_server_dispatch, 2 * MINUTES); paulo@0: }