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: }