paulo@0: /* paulo@0: * $Id: gt_xfer_obj.c,v 1.48 2004/12/19 12:38:42 mkern Exp $ paulo@0: * paulo@0: * acts as a gateway between giFT and the HTTP implementation 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: #include "gt_http_server.h" paulo@0: #include "gt_http_client.h" paulo@0: #include "gt_share.h" paulo@0: paulo@0: #include "encoding/url.h" paulo@0: paulo@0: #include "transfer/source.h" paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static List *upload_connections = NULL; paulo@0: static List *download_connections = NULL; paulo@0: paulo@0: /*****************************************************************************/ paulo@0: /* HTTP COMMUNICATION */ paulo@0: paulo@0: static BOOL header_read_timeout (GtTransfer *xfer) paulo@0: { paulo@0: gt_transfer_status (xfer, SOURCE_TIMEOUT, "Timed out"); paulo@0: gt_transfer_close (xfer, TRUE); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: GtTransfer *gt_transfer_new (GtTransferType type, Source *source, paulo@0: in_addr_t ip, in_port_t port, paulo@0: off_t start, off_t stop) paulo@0: { paulo@0: GtTransfer *xfer; paulo@0: GtTransferCB cb; paulo@0: paulo@0: if (!(xfer = malloc (sizeof (GtTransfer)))) paulo@0: return NULL; paulo@0: paulo@0: memset (xfer, 0, sizeof (GtTransfer)); paulo@0: paulo@0: if (type == GT_TRANSFER_UPLOAD) paulo@0: cb = gt_upload; paulo@0: else if (type == GT_TRANSFER_DOWNLOAD) paulo@0: cb = gt_download; paulo@0: else paulo@0: abort (); paulo@0: paulo@0: xfer->type = type; paulo@0: xfer->callback = cb; paulo@0: xfer->source = source; paulo@0: paulo@0: /* parsed information about the source */ paulo@0: xfer->ip = ip; paulo@0: xfer->port = port; paulo@0: paulo@0: xfer->start = start; paulo@0: xfer->stop = stop; paulo@0: paulo@0: xfer->shared = TRUE; paulo@0: paulo@0: xfer->detach_timer = TIMER_NONE; paulo@0: xfer->detach_msgtxt = NULL; paulo@0: paulo@0: /* set the size of this http request */ paulo@0: xfer->remaining_len = xfer->stop - xfer->start; paulo@0: paulo@0: /* give this GtTransfer a maximum amount of time before cancelling */ paulo@0: xfer->header_timer = timer_add (1 * MINUTES, paulo@0: (TimerCallback)header_read_timeout, paulo@0: xfer); paulo@0: paulo@0: return xfer; paulo@0: } paulo@0: paulo@0: static void gt_transfer_free (GtTransfer *xfer) paulo@0: { paulo@0: if (!xfer) paulo@0: return; paulo@0: paulo@0: free (xfer->command); paulo@0: free (xfer->request); paulo@0: free (xfer->request_path); paulo@0: free (xfer->content_urns); paulo@0: free (xfer->detach_msgtxt); paulo@0: paulo@0: if (xfer->header) paulo@0: dataset_clear (xfer->header); paulo@0: paulo@0: #if 0 paulo@0: if (xfer->share_authd && xfer->shared_authd_free) paulo@0: share_free (xfer->share_authd); paulo@0: #endif paulo@0: paulo@0: timer_remove (xfer->detach_timer); paulo@0: timer_remove (xfer->header_timer); paulo@0: paulo@0: /* uploads use these */ paulo@0: free (xfer->open_path); paulo@0: free (xfer->hash); paulo@0: free (xfer->version); paulo@0: paulo@0: if (xfer->f) paulo@0: fclose (xfer->f); paulo@0: paulo@0: /* whee */ paulo@0: free (xfer); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: void gt_transfer_set_tcpc (GtTransfer *xfer, TCPC *c) paulo@0: { paulo@0: assert (c->udata == NULL); paulo@0: assert (xfer->c == NULL); paulo@0: paulo@0: c->udata = xfer; paulo@0: xfer->c = c; paulo@0: } paulo@0: paulo@0: void gt_transfer_set_chunk (GtTransfer *xfer, Chunk *chunk) paulo@0: { paulo@0: assert (chunk->udata == NULL); paulo@0: assert (xfer->chunk == NULL); paulo@0: paulo@0: xfer->chunk = chunk; paulo@0: chunk->udata = xfer; paulo@0: } paulo@0: paulo@0: Chunk *gt_transfer_get_chunk (GtTransfer *xfer) paulo@0: { paulo@0: assert (xfer->chunk != NULL); paulo@0: assert (xfer->chunk->udata == xfer); paulo@0: paulo@0: return xfer->chunk; paulo@0: } paulo@0: paulo@0: TCPC *gt_transfer_get_tcpc (GtTransfer *xfer) paulo@0: { paulo@0: assert (xfer->c != NULL); paulo@0: assert (xfer->c->udata == xfer); paulo@0: paulo@0: return xfer->c; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: GtSource *gt_transfer_get_source (GtTransfer *xfer) paulo@0: { paulo@0: Source *source = xfer->source; paulo@0: paulo@0: /* could be null for uploads */ paulo@0: if (!source) paulo@0: return NULL; paulo@0: paulo@0: assert (source->udata != NULL); paulo@0: return source->udata; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * giftd may change the Chunk size. This routine will reset the range of the paulo@0: * transfer we'll ask the remote end for. It should be called before we've paulo@0: * transmitted the HTTP headers that include the Range: request. paulo@0: */ paulo@0: void gt_transfer_set_length (GtTransfer *xfer, Chunk *chunk) paulo@0: { paulo@0: TCPC *c; paulo@0: off_t old_start; paulo@0: off_t old_stop; paulo@0: off_t old_len; paulo@0: paulo@0: c = gt_transfer_get_tcpc (xfer); paulo@0: paulo@0: /* don't call me if you've already transmitted headers */ paulo@0: assert (!xfer->transmitted_hdrs); paulo@0: paulo@0: old_start = xfer->start; paulo@0: old_stop = xfer->stop; paulo@0: old_len = xfer->remaining_len; paulo@0: paulo@0: xfer->start = chunk->start + chunk->transmit; paulo@0: xfer->stop = chunk->stop; paulo@0: paulo@0: xfer->remaining_len = xfer->stop - xfer->start; paulo@0: paulo@0: /* i believe this is true even for push downloads... */ paulo@0: assert (xfer->start == old_start); paulo@0: paulo@0: if (xfer->stop != old_stop) paulo@0: { paulo@0: assert (xfer->remaining_len != old_len); paulo@0: paulo@0: GT->DBGSOCK (GT, c, "(%s) old chunk range: [%lu,%lu) " paulo@0: "new range: [%lu,%lu) old len: %lu new len: %lu", paulo@0: xfer->request, (long)old_start,(long)old_stop, paulo@0: (long)xfer->start, (long)xfer->stop, paulo@0: (long)old_len, (long)xfer->remaining_len); paulo@0: } paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Cancels a possibly active HTTP transfer paulo@0: * paulo@0: * NOTE: paulo@0: * This function may be called recursively if you don't do what giFT expects paulo@0: * of you. This is a feature, not a bug, trust me. paulo@0: */ paulo@0: static void gt_transfer_cancel (Chunk *chunk, TransferType type) paulo@0: { paulo@0: GtTransfer *xfer; paulo@0: BOOL force_close = FALSE; paulo@0: paulo@0: if (!chunk) paulo@0: return; paulo@0: paulo@0: xfer = chunk->udata; paulo@0: paulo@0: /* each time this function is called we _MUST_ uncouple the transfer paulo@0: * and the chunk or the code to follow will eventually call this func paulo@0: * again!! */ paulo@0: if (!xfer) paulo@0: return; paulo@0: paulo@0: /* do not emit a callback signal */ paulo@0: xfer->callback = NULL; paulo@0: gt_transfer_close (xfer, force_close); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void close_http_connection (TCPC *c, BOOL force_close, paulo@0: GtTransferType type, GtSource *gt_src) paulo@0: { paulo@0: if (!c) paulo@0: return; paulo@0: paulo@0: /* paulo@0: * If this is an incoming indirect download that we sent a push out paulo@0: * for, then don't store the connection in the HTTP connection cache, paulo@0: * store it in the separate push connection cache that uses the client paulo@0: * id as well as the IP address to identify the node. paulo@0: */ paulo@0: if (!force_close && type == GT_TRANSFER_DOWNLOAD && !c->outgoing) paulo@0: { paulo@0: if (gt_src != NULL) paulo@0: { paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "Keeping push connection"); paulo@0: paulo@0: /* nullify the previous data on this connection */ paulo@0: c->udata = NULL; paulo@0: paulo@0: gt_push_source_add_conn (gt_src->guid, gt_src->user_ip, c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * This could happen if the chunk has no source url to parse paulo@0: * at the moment. Argh, GtTransfer should always have a GtSource paulo@0: * if xfer->type == GT_TRANSFER_DOWNLOAD. paulo@0: */ paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "Closing pushed connection! ARGH!"); paulo@0: paulo@0: force_close = TRUE; paulo@0: } paulo@0: paulo@0: gt_http_connection_close (type, c, force_close); paulo@0: } paulo@0: paulo@0: /* paulo@0: * This function is very critical to OpenFT's transfer system. It is called paulo@0: * anytime either the client or server HTTP implementations need to "cleanup" paulo@0: * all data associated. This includes disconnecting the socket, unlinking paulo@0: * itself from the chunk system and registering this status with giFT, just paulo@0: * in case this is premature. If anything is leaking or fucking up, blame paulo@0: * this :) paulo@0: */ paulo@0: void gt_transfer_close (GtTransfer *xfer, BOOL force_close) paulo@0: { paulo@0: TCPC *c; paulo@0: Chunk *chunk; paulo@0: GtSource *gt_src = NULL; paulo@0: char *conn_hdr; paulo@0: paulo@0: if (!xfer) paulo@0: return; paulo@0: paulo@0: c = xfer->c; paulo@0: chunk = xfer->chunk; paulo@0: paulo@0: assert (xfer != NULL); paulo@0: paulo@0: /* remove the xfer from the indirect src list */ paulo@0: gt_push_source_remove_xfer (xfer); paulo@0: paulo@0: /* get this source if this was a download */ paulo@0: if (xfer->type == GT_TRANSFER_DOWNLOAD && chunk && chunk->source) paulo@0: gt_src = gt_source_unserialize (chunk->source->url); paulo@0: paulo@0: /* if we have associated a chunk with this transfer we need to make sure paulo@0: * we remove cleanly detach */ paulo@0: if (chunk) paulo@0: { paulo@0: chunk->udata = NULL; paulo@0: paulo@0: /* paulo@0: * notify the transfer callback that we have terminated this paulo@0: * connection. let giFT handle the rest paulo@0: * paulo@0: * NOTE: paulo@0: * see gt_transfer_cancel for some warnings about this code paulo@0: */ paulo@0: if (xfer->callback) paulo@0: (*xfer->callback) (chunk, NULL, 0); paulo@0: paulo@0: /* WARNING: chunk is free'd in the depths of xfer->callback! */ paulo@0: } paulo@0: paulo@0: /* HTTP/1.0 does not support persist connections or something...i dunno */ paulo@0: if (!STRCASECMP (xfer->version, "HTTP/1.0")) paulo@0: force_close = TRUE; paulo@0: paulo@0: /* older gnutella clients send a plain "HTTP" version, that is paulo@0: * not persistent */ paulo@0: if (!STRCASECMP (xfer->version, "HTTP")) paulo@0: force_close = TRUE; paulo@0: paulo@0: /* paulo@0: * We must force a socket close if there is still data waiting to paulo@0: * be read on this transfer. paulo@0: */ paulo@0: if (!xfer->transmitted_hdrs || xfer->remaining_len != 0) paulo@0: force_close = TRUE; paulo@0: paulo@0: /* close the connection if "Connection: close" was supplied */ paulo@0: conn_hdr = dataset_lookupstr (xfer->header, "connection"); paulo@0: if (!STRCASECMP (conn_hdr, "close")) paulo@0: force_close = TRUE; paulo@0: paulo@0: close_http_connection (c, force_close, xfer->type, gt_src); paulo@0: paulo@0: gt_source_free (gt_src); paulo@0: paulo@0: gt_transfer_free (xfer); paulo@0: } paulo@0: paulo@0: void gt_transfer_status (GtTransfer *xfer, SourceStatus status, char *text) paulo@0: { paulo@0: Chunk *chunk; paulo@0: GtSource *gt_src; paulo@0: char *next_status; paulo@0: paulo@0: if (!xfer || !text) paulo@0: return; paulo@0: paulo@0: /* Added this on 2004-12-19 to track observed assertion failure in paulo@0: * gt_transfer_get_chunk. -- mkern paulo@0: */ paulo@0: if (!xfer->chunk || xfer->chunk->udata != xfer) paulo@0: { paulo@0: GT->DBGFN (GT, "This is where we say good bye. status = %d, " paulo@0: "text = %s, xfer->request_path = %s, xfer->ip = %s", paulo@0: status, text, xfer->request_path, net_ip_str (xfer->ip)); paulo@0: } paulo@0: paulo@0: chunk = gt_transfer_get_chunk (xfer); paulo@0: paulo@0: GT->source_status (GT, chunk->source, status, text); paulo@0: paulo@0: /* paulo@0: * HACK: Store the status message on the GtSource, paulo@0: * so we can reuse it sometimes. paulo@0: */ paulo@0: if (!xfer->source || !(gt_src = xfer->source->udata)) paulo@0: return; paulo@0: paulo@0: /* allocate first so it's ok to call this function with an old value of paulo@0: * gt_src->status_txt */ paulo@0: next_status = STRDUP (text); paulo@0: free (gt_src->status_txt); paulo@0: gt_src->status_txt = next_status; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: /* PERSISTENT HTTP HANDLING */ paulo@0: paulo@0: struct conn_info paulo@0: { paulo@0: in_addr_t ip; paulo@0: in_port_t port; paulo@0: size_t count; paulo@0: }; paulo@0: paulo@0: static int conn_cmp (TCPC *c, struct conn_info *info) paulo@0: { paulo@0: if (info->port != c->port) paulo@0: return -1; paulo@0: paulo@0: if (net_peer (c->fd) != info->ip) paulo@0: return 1; paulo@0: paulo@0: return 0; paulo@0: } paulo@0: paulo@0: TCPC *gt_http_connection_lookup (GtTransferType type, in_addr_t ip, paulo@0: in_port_t port) paulo@0: { paulo@0: List *link; paulo@0: List **connlist_ptr; paulo@0: TCPC *c = NULL; paulo@0: struct conn_info info; paulo@0: paulo@0: info.ip = ip; paulo@0: info.port = port; paulo@0: paulo@0: if (type == GT_TRANSFER_DOWNLOAD) paulo@0: connlist_ptr = &download_connections; paulo@0: else paulo@0: connlist_ptr = &upload_connections; paulo@0: paulo@0: link = list_find_custom (*connlist_ptr, &info, (CompareFunc)conn_cmp); paulo@0: paulo@0: if (link) paulo@0: { paulo@0: c = link->data; paulo@0: paulo@0: GT->DBGFN (GT, "using previous connection to %s:%hu", paulo@0: net_ip_str (ip), port); paulo@0: paulo@0: /* remove from the open list */ paulo@0: *connlist_ptr = list_remove_link (*connlist_ptr, link); paulo@0: input_remove_all (c->fd); paulo@0: } paulo@0: paulo@0: return c; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Handles outgoing HTTP connections. This function is capable of paulo@0: * retrieving an already connected socket that was left over from a previous paulo@0: * transfer. paulo@0: */ paulo@0: TCPC *gt_http_connection_open (GtTransferType type, in_addr_t ip, paulo@0: in_port_t port) paulo@0: { paulo@0: TCPC *c; paulo@0: paulo@0: if (!(c = gt_http_connection_lookup (type, ip, port))) paulo@0: c = tcp_open (ip, port, FALSE); paulo@0: paulo@0: return c; paulo@0: } paulo@0: paulo@0: static BOOL count_open (TCPC *c, struct conn_info *info) paulo@0: { paulo@0: if (info->ip == net_peer (c->fd)) paulo@0: info->count++; paulo@0: paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: size_t gt_http_connection_length (GtTransferType type, in_addr_t ip) paulo@0: { paulo@0: struct conn_info info; paulo@0: List *list; paulo@0: paulo@0: info.ip = ip; paulo@0: info.port = 0; paulo@0: info.count = 0; paulo@0: paulo@0: assert (type == GT_TRANSFER_DOWNLOAD || type == GT_TRANSFER_UPLOAD); paulo@0: list = (type == GT_TRANSFER_DOWNLOAD ? download_connections : paulo@0: upload_connections); paulo@0: paulo@0: list_foreach (list, (ListForeachFunc)count_open, &info); paulo@0: paulo@0: return info.count; paulo@0: } paulo@0: paulo@0: void gt_http_connection_close (GtTransferType type, TCPC *c, BOOL force_close) paulo@0: { paulo@0: List **connlist_ptr; paulo@0: paulo@0: if (!c) paulo@0: return; paulo@0: paulo@0: switch (type) paulo@0: { paulo@0: case GT_TRANSFER_DOWNLOAD: paulo@0: { paulo@0: gt_http_client_reset (c); paulo@0: connlist_ptr = &download_connections; paulo@0: } paulo@0: break; paulo@0: paulo@0: case GT_TRANSFER_UPLOAD: paulo@0: { paulo@0: gt_http_server_reset (c); paulo@0: connlist_ptr = &upload_connections; paulo@0: } paulo@0: break; paulo@0: paulo@0: default: paulo@0: abort (); paulo@0: } paulo@0: paulo@0: if (force_close) paulo@0: { paulo@0: *connlist_ptr = list_remove (*connlist_ptr, c); paulo@0: paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "force closing"); paulo@0: paulo@0: tcp_close (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* remove the data associated with this connection */ paulo@0: c->udata = NULL; paulo@0: paulo@0: /* paulo@0: * This condition will happen because the server doesn't remove the paulo@0: * connection from the persistent list until the connection fails. paulo@0: */ paulo@0: if (list_find (*connlist_ptr, c)) paulo@0: { paulo@0: assert (type == GT_TRANSFER_UPLOAD); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* track it */ paulo@0: *connlist_ptr = list_prepend (*connlist_ptr, c); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static char *localize_request (GtTransfer *xfer, BOOL *authorized) paulo@0: { paulo@0: char *open_path; paulo@0: char *s_path; paulo@0: int auth = FALSE; paulo@0: int need_free = FALSE; paulo@0: paulo@0: if (!xfer || !xfer->request) paulo@0: return NULL; paulo@0: paulo@0: /* dont call secure_path if they dont even care if it's a secure paulo@0: * lookup */ paulo@0: s_path = paulo@0: (authorized ? file_secure_path (xfer->request) : xfer->request); paulo@0: paulo@0: if (authorized) paulo@0: need_free = TRUE; paulo@0: paulo@0: open_path = gt_localize_request (xfer, s_path, &auth); paulo@0: paulo@0: if (need_free || !open_path) paulo@0: free (s_path); paulo@0: paulo@0: if (authorized) paulo@0: *authorized = auth; paulo@0: paulo@0: /* we need a unix style path for authorization */ paulo@0: return open_path; paulo@0: } paulo@0: paulo@0: /* paulo@0: * request is expected in the form: paulo@0: * /shared/Fuck%20Me%20Hard.mpg paulo@0: */ paulo@0: BOOL gt_transfer_set_request (GtTransfer *xfer, char *request) paulo@0: { paulo@0: #if 0 paulo@0: FileShare *file; paulo@0: char *shared_path; paulo@0: #endif paulo@0: paulo@0: free (xfer->request); paulo@0: xfer->request = NULL; paulo@0: paulo@0: /* lets keep this sane shall we */ paulo@0: if (!request || *request != '/') paulo@0: return FALSE; paulo@0: paulo@0: xfer->request = STRDUP (request); paulo@0: xfer->request_path = gt_url_decode (request); /* storing here for opt */ paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: /* attempts to open the requested file locally. paulo@0: * NOTE: this handles verification */ paulo@0: FILE *gt_transfer_open_request (GtTransfer *xfer, int *code) paulo@0: { paulo@0: FILE *f; paulo@0: char *shared_path; paulo@0: char *full_path = NULL; paulo@0: char *host_path; paulo@0: int auth = FALSE; paulo@0: int code_l = 200; paulo@0: paulo@0: if (code) paulo@0: *code = 404; /* Not Found */ paulo@0: paulo@0: if (!xfer || !xfer->request) paulo@0: return NULL; paulo@0: paulo@0: if (!(shared_path = localize_request (xfer, &auth))) paulo@0: { paulo@0: /* paulo@0: * If we havent finished syncing shares, that may be why the paulo@0: * request failed. If so, return 503 here. paulo@0: */ paulo@0: if (!gt_share_local_sync_is_done () && code != NULL) paulo@0: *code = 503; paulo@0: paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: /* needs more work for virtual requests */ paulo@0: #if 0 paulo@0: /* check to see if we matched a special OpenFT condition. If we did, the paulo@0: * localized path is in the wrong order, so convert it (just to be paulo@0: * converted back again...it's easier to maintain this way :) */ paulo@0: if (auth) paulo@0: { paulo@0: xfer->shared = FALSE; paulo@0: full_path = file_unix_path (shared_path); paulo@0: } paulo@0: else paulo@0: #endif paulo@0: { paulo@0: Share *share; paulo@0: int reason = UPLOAD_AUTH_NOTSHARED; paulo@0: upload_auth_t cond; paulo@0: paulo@0: /* paulo@0: * NOTE: the user string is not consistent with the one paulo@0: * echoed through search results, which prefixes the IP paulo@0: * with the GUID, if this node is firewalled. paulo@0: */ paulo@0: if ((share = GT->share_lookup (GT, SHARE_LOOKUP_HPATH, shared_path))) paulo@0: reason = GT->upload_auth (GT, net_ip_str (xfer->ip), share, &cond); paulo@0: paulo@0: xfer->share_authd = share; paulo@0: paulo@0: switch (reason) paulo@0: { paulo@0: case UPLOAD_AUTH_STALE: paulo@0: code_l = 500; /* Internal Server Error */ paulo@0: break; paulo@0: case UPLOAD_AUTH_NOTSHARED: paulo@0: code_l = 404; /* Not Found */ paulo@0: break; paulo@0: case UPLOAD_AUTH_ALLOW: paulo@0: code_l = 200; /* OK */ paulo@0: xfer->open_path_size = share->size; paulo@0: xfer->content_type = share->mime; paulo@0: full_path = STRDUP (share->path); paulo@0: break; paulo@0: case UPLOAD_AUTH_MAX: paulo@0: case UPLOAD_AUTH_MAX_PERUSER: paulo@0: case UPLOAD_AUTH_HIDDEN: paulo@0: default: paulo@0: code_l = 503; /* Service Unavailable */ paulo@0: xfer->queue_pos = cond.queue_pos; paulo@0: xfer->queue_ttl = cond.queue_ttl; paulo@0: break; paulo@0: } paulo@0: } paulo@0: paulo@0: if (code) paulo@0: *code = code_l; paulo@0: paulo@0: /* error of some kind, get out of here */ paulo@0: if (code_l != 200) paulo@0: return NULL; paulo@0: paulo@0: /* figure out the actual filename that we should be opening */ paulo@0: host_path = file_host_path (full_path); paulo@0: free (full_path); paulo@0: paulo@0: /* needs more work for virtual requests */ paulo@0: #if 0 paulo@0: /* complete the rest of the data required */ paulo@0: if (auth) paulo@0: { paulo@0: struct stat st; paulo@0: paulo@0: if (!file_stat (host_path, &st)) paulo@0: { paulo@0: free (host_path); paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: xfer->open_path_size = st.st_size; paulo@0: xfer->content_type = mime_type (host_path); paulo@0: } paulo@0: #endif paulo@0: paulo@0: if (!(f = fopen (host_path, "rb"))) paulo@0: { paulo@0: *code = 500; paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: /* NOTE: gt_transfer_close will be responsible for freeing this now */ paulo@0: xfer->open_path = host_path; paulo@0: paulo@0: if (code) paulo@0: *code = 200; /* OK */ paulo@0: paulo@0: return f; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * The callbacks here are from within the HTTP system, centralized for paulo@0: * maintainability. paulo@0: */ paulo@0: paulo@0: /* report back the progress of this download chunk */ paulo@0: void gt_download (Chunk *chunk, unsigned char *segment, size_t len) paulo@0: { paulo@0: GT->chunk_write (GT, chunk->transfer, chunk, chunk->source, paulo@0: segment, len); paulo@0: } paulo@0: paulo@0: /* report back the progress of this upload chunk */ paulo@0: void gt_upload (Chunk *chunk, unsigned char *segment, size_t len) paulo@0: { paulo@0: GT->chunk_write (GT, chunk->transfer, chunk, chunk->source, paulo@0: segment, len); paulo@0: } paulo@0: paulo@0: void gt_transfer_write (GtTransfer *xfer, Chunk *chunk, paulo@0: unsigned char *segment, size_t len) paulo@0: { paulo@0: /* paulo@0: * Cap the data at the remaining size of the xfer. Note that this is paulo@0: * the size of the HTTP request issued, _NOT_ the chunk size, which may paulo@0: * have been altered by giFT in splitting up chunks. I think giFT paulo@0: * handles that case properly, but we also have to guard against paulo@0: * remaining_len becoming less than 0. Note that p->chunk_write paulo@0: * will cancel the transfer if remaining_len goes to 0. paulo@0: * paulo@0: * TODO: remaining_len is off_t, make sure this is handled right wrt paulo@0: * negative file sizes (do big files become negative sizes?) paulo@0: */ paulo@0: if (len > xfer->remaining_len) paulo@0: len = xfer->remaining_len; paulo@0: paulo@0: xfer->remaining_len -= len; paulo@0: (*xfer->callback) (chunk, segment, len); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static BOOL throttle_suspend (Chunk *chunk, int s_opt, float factor) paulo@0: { paulo@0: GtTransfer *xfer; paulo@0: paulo@0: if (!chunk) paulo@0: return FALSE; paulo@0: paulo@0: xfer = chunk->udata; paulo@0: paulo@0: if (!xfer || !xfer->c) paulo@0: { paulo@0: GT->DBGFN (GT, "no connection found to suspend"); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: input_suspend_all (xfer->c->fd); paulo@0: paulo@0: if (factor) paulo@0: net_sock_adj_buf (xfer->c->fd, s_opt, factor); paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: static BOOL throttle_resume (Chunk *chunk, int s_opt, float factor) paulo@0: { paulo@0: GtTransfer *xfer = NULL; paulo@0: paulo@0: if (!chunk) paulo@0: return FALSE; paulo@0: paulo@0: xfer = chunk->udata; paulo@0: paulo@0: if (!xfer || !xfer->c) paulo@0: { paulo@0: GT->DBGFN (GT, "no connection found to resume"); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: input_resume_all (xfer->c->fd); paulo@0: paulo@0: #if 0 paulo@0: if (factor) paulo@0: net_sock_adj_buf (xfer->c->fd, s_opt, factor); paulo@0: #endif paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: void gt_download_cancel (Chunk *chunk, void *data) paulo@0: { paulo@0: gt_transfer_cancel (chunk, TRANSFER_DOWNLOAD); paulo@0: } paulo@0: paulo@0: /* cancel the transfer associate with the chunk giFT gave us */ paulo@0: void gt_upload_cancel (Chunk *chunk, void *data) paulo@0: { paulo@0: gt_transfer_cancel (chunk, TRANSFER_UPLOAD); paulo@0: } paulo@0: paulo@0: static int throttle_sopt (Transfer *transfer) paulo@0: { paulo@0: int sopt = 0; paulo@0: paulo@0: switch (transfer_direction (transfer)) paulo@0: { paulo@0: case TRANSFER_DOWNLOAD: paulo@0: sopt = SO_RCVBUF; paulo@0: break; paulo@0: case TRANSFER_UPLOAD: paulo@0: sopt = SO_SNDBUF; paulo@0: break; paulo@0: } paulo@0: paulo@0: return sopt; paulo@0: } paulo@0: paulo@0: BOOL gt_chunk_suspend (Chunk *chunk, Transfer *transfer, void *data) paulo@0: { paulo@0: return throttle_suspend (chunk, throttle_sopt (transfer), 0.0); paulo@0: } paulo@0: paulo@0: BOOL gt_chunk_resume (Chunk *chunk, Transfer *transfer, void *data) paulo@0: { paulo@0: return throttle_resume (chunk, throttle_sopt (transfer), 0.0); paulo@0: }