rev |
line source |
paulo@0
|
1 /*
|
paulo@0
|
2 * $Id: gt_http_server.c,v 1.73 2005/01/04 15:03:40 mkern Exp $
|
paulo@0
|
3 *
|
paulo@0
|
4 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net)
|
paulo@0
|
5 *
|
paulo@0
|
6 * This program is free software; you can redistribute it and/or modify it
|
paulo@0
|
7 * under the terms of the GNU General Public License as published by the
|
paulo@0
|
8 * Free Software Foundation; either version 2, or (at your option) any
|
paulo@0
|
9 * later version.
|
paulo@0
|
10 *
|
paulo@0
|
11 * This program is distributed in the hope that it will be useful, but
|
paulo@0
|
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
paulo@0
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
paulo@0
|
14 * General Public License for more details.
|
paulo@0
|
15 */
|
paulo@0
|
16
|
paulo@0
|
17 #include "gt_gnutella.h"
|
paulo@0
|
18
|
paulo@0
|
19 #include "gt_xfer_obj.h"
|
paulo@0
|
20 #include "gt_xfer.h"
|
paulo@0
|
21
|
paulo@0
|
22 #include "gt_http_server.h"
|
paulo@0
|
23 #include "gt_http_client.h"
|
paulo@0
|
24
|
paulo@0
|
25 #include "gt_accept.h"
|
paulo@0
|
26 #include "gt_ban.h"
|
paulo@0
|
27 #include "gt_version.h"
|
paulo@0
|
28
|
paulo@0
|
29 /*****************************************************************************/
|
paulo@0
|
30
|
paulo@0
|
31 /* convenient shorthand */
|
paulo@0
|
32 #define CONTENT_URN_FIELD "X-Gnutella-Content-URN"
|
paulo@0
|
33
|
paulo@0
|
34 #define INCOMING_TIMEOUT (1 * MINUTES)
|
paulo@0
|
35
|
paulo@0
|
36 /*****************************************************************************/
|
paulo@0
|
37
|
paulo@0
|
38 struct http_incoming
|
paulo@0
|
39 {
|
paulo@0
|
40 TCPC *c;
|
paulo@0
|
41 timer_id timer;
|
paulo@0
|
42 };
|
paulo@0
|
43
|
paulo@0
|
44 /*****************************************************************************/
|
paulo@0
|
45
|
paulo@0
|
46 static void server_handle_get (GtTransfer *xfer);
|
paulo@0
|
47 static void get_client_request (int fd, input_id id,
|
paulo@0
|
48 struct http_incoming *http);
|
paulo@0
|
49 static void send_http_response (int fd, input_id id, GtTransfer *xfer);
|
paulo@0
|
50
|
paulo@0
|
51 /*****************************************************************************/
|
paulo@0
|
52 /* SERVER HELPERS */
|
paulo@0
|
53
|
paulo@0
|
54 static char *lookup_http_code (int code, char **desc)
|
paulo@0
|
55 {
|
paulo@0
|
56 char *err;
|
paulo@0
|
57 char *txt;
|
paulo@0
|
58
|
paulo@0
|
59 switch (code)
|
paulo@0
|
60 {
|
paulo@0
|
61 case 200: err = "OK";
|
paulo@0
|
62 txt = "Success";
|
paulo@0
|
63 break;
|
paulo@0
|
64 case 206: err = "Partial Content";
|
paulo@0
|
65 txt = "Resume successful";
|
paulo@0
|
66 break;
|
paulo@0
|
67 case 403: err = "Forbidden";
|
paulo@0
|
68 txt = "You do not have access to this file";
|
paulo@0
|
69 break;
|
paulo@0
|
70 case 404: err = "Not Found";
|
paulo@0
|
71 txt = "File is not available";
|
paulo@0
|
72 break;
|
paulo@0
|
73 case 500: err = "Internal Server Error";
|
paulo@0
|
74 txt = "Stale file entry, retry later";
|
paulo@0
|
75 break;
|
paulo@0
|
76 case 501: err = "Not Implemented";
|
paulo@0
|
77 txt = "???";
|
paulo@0
|
78 break;
|
paulo@0
|
79 case 503: err = "Service Unavailable";
|
paulo@0
|
80 txt = "Upload queue is currently full, please try again later";
|
paulo@0
|
81 break;
|
paulo@0
|
82 default: err = NULL;
|
paulo@0
|
83 txt = NULL;
|
paulo@0
|
84 break;
|
paulo@0
|
85 }
|
paulo@0
|
86
|
paulo@0
|
87 if (desc)
|
paulo@0
|
88 *desc = txt;
|
paulo@0
|
89
|
paulo@0
|
90 return err;
|
paulo@0
|
91 }
|
paulo@0
|
92
|
paulo@0
|
93 static String *alloc_header (int code)
|
paulo@0
|
94 {
|
paulo@0
|
95 char *code_text;
|
paulo@0
|
96 String *s;
|
paulo@0
|
97
|
paulo@0
|
98 /* so that we can communicate both the numerical code and the human
|
paulo@0
|
99 * readable string */
|
paulo@0
|
100 if (!(code_text = lookup_http_code (code, NULL)))
|
paulo@0
|
101 return FALSE;
|
paulo@0
|
102
|
paulo@0
|
103 if (!(s = string_new (NULL, 0, 0, TRUE)))
|
paulo@0
|
104 return FALSE;
|
paulo@0
|
105
|
paulo@0
|
106 string_appendf (s, "HTTP/1.1 %i %s\r\n", code, code_text);
|
paulo@0
|
107
|
paulo@0
|
108 return s;
|
paulo@0
|
109 }
|
paulo@0
|
110
|
paulo@0
|
111 static void construct_header_va (String *s, int code, va_list args)
|
paulo@0
|
112 {
|
paulo@0
|
113 char *key;
|
paulo@0
|
114 char *value;
|
paulo@0
|
115
|
paulo@0
|
116 /* Add "Server: " header */
|
paulo@0
|
117 string_appendf (s, "Server: %s\r\n", gt_version ());
|
paulo@0
|
118
|
paulo@0
|
119 for (;;)
|
paulo@0
|
120 {
|
paulo@0
|
121 if (!(key = va_arg (args, char *)))
|
paulo@0
|
122 break;
|
paulo@0
|
123
|
paulo@0
|
124 if (!(value = va_arg (args, char *)))
|
paulo@0
|
125 continue;
|
paulo@0
|
126
|
paulo@0
|
127 string_appendf (s, "%s: %s\r\n", key, value);
|
paulo@0
|
128 }
|
paulo@0
|
129
|
paulo@0
|
130 /* append final message terminator */
|
paulo@0
|
131 string_append (s, "\r\n");
|
paulo@0
|
132 }
|
paulo@0
|
133
|
paulo@0
|
134 static String *construct_header (int code, ...)
|
paulo@0
|
135 {
|
paulo@0
|
136 String *s;
|
paulo@0
|
137 va_list args;
|
paulo@0
|
138
|
paulo@0
|
139 if (!(s = alloc_header (code)))
|
paulo@0
|
140 return NULL;
|
paulo@0
|
141
|
paulo@0
|
142 va_start (args, code);
|
paulo@0
|
143 construct_header_va (s, code, args);
|
paulo@0
|
144 va_end (args);
|
paulo@0
|
145
|
paulo@0
|
146 return s;
|
paulo@0
|
147 }
|
paulo@0
|
148
|
paulo@0
|
149 /*
|
paulo@0
|
150 * Construct and send a server reply.
|
paulo@0
|
151 */
|
paulo@0
|
152 static BOOL gt_http_server_send (TCPC *c, int code, ...)
|
paulo@0
|
153 {
|
paulo@0
|
154 String *s;
|
paulo@0
|
155 int ret;
|
paulo@0
|
156 size_t len;
|
paulo@0
|
157 va_list args;
|
paulo@0
|
158
|
paulo@0
|
159 if (!(s = alloc_header (code)))
|
paulo@0
|
160 return FALSE;
|
paulo@0
|
161
|
paulo@0
|
162 va_start (args, code);
|
paulo@0
|
163
|
paulo@0
|
164 construct_header_va (s, code, args);
|
paulo@0
|
165
|
paulo@0
|
166 va_end (args);
|
paulo@0
|
167
|
paulo@0
|
168 if (HTTP_DEBUG)
|
paulo@0
|
169 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str);
|
paulo@0
|
170
|
paulo@0
|
171 len = s->len;
|
paulo@0
|
172 ret = tcp_send (c, s->str, s->len);
|
paulo@0
|
173
|
paulo@0
|
174 string_free (s);
|
paulo@0
|
175
|
paulo@0
|
176 return (ret == len);
|
paulo@0
|
177 }
|
paulo@0
|
178
|
paulo@0
|
179 static char *get_error_page (GtTransfer *xfer, int code)
|
paulo@0
|
180 {
|
paulo@0
|
181 char *page;
|
paulo@0
|
182 char *err;
|
paulo@0
|
183 char *errtxt = NULL;
|
paulo@0
|
184
|
paulo@0
|
185 if (!(err = lookup_http_code (code, &errtxt)))
|
paulo@0
|
186 return 0;
|
paulo@0
|
187
|
paulo@0
|
188 page = stringf ("<h1>%i %s</h1><br>%s.", code, err, errtxt);
|
paulo@0
|
189
|
paulo@0
|
190 return page;
|
paulo@0
|
191 }
|
paulo@0
|
192
|
paulo@0
|
193 static BOOL supports_queue (GtTransfer *xfer)
|
paulo@0
|
194 {
|
paulo@0
|
195 char *features;
|
paulo@0
|
196
|
paulo@0
|
197 if (dataset_lookupstr (xfer->header, "x-queue"))
|
paulo@0
|
198 return TRUE;
|
paulo@0
|
199
|
paulo@0
|
200 if ((features = dataset_lookupstr (xfer->header, "x-features")))
|
paulo@0
|
201 {
|
paulo@0
|
202 /* XXX: case-sensitive */
|
paulo@0
|
203 if (strstr (features, "queue"))
|
paulo@0
|
204 return TRUE;
|
paulo@0
|
205 }
|
paulo@0
|
206
|
paulo@0
|
207 return FALSE;
|
paulo@0
|
208 }
|
paulo@0
|
209
|
paulo@0
|
210 static char *get_queue_line (GtTransfer *xfer)
|
paulo@0
|
211 {
|
paulo@0
|
212 String *s;
|
paulo@0
|
213
|
paulo@0
|
214 /* do nothing if not queued */
|
paulo@0
|
215 if (xfer->queue_pos == 0)
|
paulo@0
|
216 return NULL;
|
paulo@0
|
217
|
paulo@0
|
218 if (!(s = string_new (NULL, 0, 0, TRUE)))
|
paulo@0
|
219 return NULL;
|
paulo@0
|
220
|
paulo@0
|
221 string_appendf (s, "position=%d,length=%d,pollMin=%d,pollMax=%d",
|
paulo@0
|
222 xfer->queue_pos, xfer->queue_ttl, 45, 120);
|
paulo@0
|
223
|
paulo@0
|
224 return string_free_keep (s);
|
paulo@0
|
225 }
|
paulo@0
|
226
|
paulo@0
|
227 static String *get_error_header (GtTransfer *xfer, int code,
|
paulo@0
|
228 const char *error_page)
|
paulo@0
|
229 {
|
paulo@0
|
230 size_t len;
|
paulo@0
|
231 char content_len[256];
|
paulo@0
|
232 char *queue_line = NULL;
|
paulo@0
|
233 char *content_type = "text/html";
|
paulo@0
|
234 String *s;
|
paulo@0
|
235
|
paulo@0
|
236 len = strlen (error_page);
|
paulo@0
|
237 snprintf (content_len, sizeof (content_len), "%u", len);
|
paulo@0
|
238
|
paulo@0
|
239 if (code == 503 && supports_queue (xfer))
|
paulo@0
|
240 queue_line = get_queue_line (xfer);
|
paulo@0
|
241
|
paulo@0
|
242 /* don't send a content-type header if there is no entity body */
|
paulo@0
|
243 if (len == 0)
|
paulo@0
|
244 content_type = NULL;
|
paulo@0
|
245
|
paulo@0
|
246 s = construct_header (code,
|
paulo@0
|
247 "Content-Type", content_type,
|
paulo@0
|
248 "Content-Length", content_len,
|
paulo@0
|
249 CONTENT_URN_FIELD, xfer->content_urns,
|
paulo@0
|
250 "X-Queue", queue_line,
|
paulo@0
|
251 NULL);
|
paulo@0
|
252
|
paulo@0
|
253 free (queue_line);
|
paulo@0
|
254 return s;
|
paulo@0
|
255 }
|
paulo@0
|
256
|
paulo@0
|
257 static void send_error_reply (int fd, input_id id, GtTransfer *xfer)
|
paulo@0
|
258 {
|
paulo@0
|
259 String *s;
|
paulo@0
|
260 const char *error_page;
|
paulo@0
|
261 int ret;
|
paulo@0
|
262 TCPC *c;
|
paulo@0
|
263
|
paulo@0
|
264 c = gt_transfer_get_tcpc (xfer);
|
paulo@0
|
265
|
paulo@0
|
266 if (!(error_page = get_error_page (xfer, xfer->code)))
|
paulo@0
|
267 {
|
paulo@0
|
268 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
269 return;
|
paulo@0
|
270 }
|
paulo@0
|
271
|
paulo@0
|
272 /*
|
paulo@0
|
273 * If the remote end supports queueing or supplied
|
paulo@0
|
274 * "X-Gnutella-Content-URN",, it's a Gnutella client and we don't want to
|
paulo@0
|
275 * keep sending the error page, because over many requests from this
|
paulo@0
|
276 * client the bandwidth could add up.
|
paulo@0
|
277 */
|
paulo@0
|
278 if (supports_queue (xfer) ||
|
paulo@0
|
279 dataset_lookupstr (xfer->header, "x-gnutella-content-urn"))
|
paulo@0
|
280 {
|
paulo@0
|
281 error_page = ""; /* empty */
|
paulo@0
|
282 }
|
paulo@0
|
283
|
paulo@0
|
284 if (!(s = get_error_header (xfer, xfer->code, error_page)))
|
paulo@0
|
285 {
|
paulo@0
|
286 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
287 return;
|
paulo@0
|
288 }
|
paulo@0
|
289
|
paulo@0
|
290 string_append (s, error_page);
|
paulo@0
|
291
|
paulo@0
|
292 if (HTTP_DEBUG)
|
paulo@0
|
293 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str);
|
paulo@0
|
294
|
paulo@0
|
295 /* send the whole page at once */
|
paulo@0
|
296 ret = tcp_send (c, s->str, s->len);
|
paulo@0
|
297
|
paulo@0
|
298 /* if the whole thing was sent keep the connection open */
|
paulo@0
|
299 if (ret == s->len)
|
paulo@0
|
300 {
|
paulo@0
|
301 xfer->transmitted_hdrs = TRUE;
|
paulo@0
|
302 xfer->remaining_len = 0;
|
paulo@0
|
303 }
|
paulo@0
|
304
|
paulo@0
|
305 string_free (s);
|
paulo@0
|
306
|
paulo@0
|
307 gt_transfer_close (xfer, FALSE);
|
paulo@0
|
308 }
|
paulo@0
|
309
|
paulo@0
|
310 static void gt_http_server_send_error_and_close (GtTransfer *xfer, int code)
|
paulo@0
|
311 {
|
paulo@0
|
312 TCPC *c;
|
paulo@0
|
313
|
paulo@0
|
314 c = gt_transfer_get_tcpc (xfer);
|
paulo@0
|
315
|
paulo@0
|
316 xfer->code = code;
|
paulo@0
|
317
|
paulo@0
|
318 input_remove_all (c->fd);
|
paulo@0
|
319 input_add (c->fd, xfer, INPUT_WRITE,
|
paulo@0
|
320 (InputCallback)send_error_reply, TIMEOUT_DEF);
|
paulo@0
|
321 }
|
paulo@0
|
322
|
paulo@0
|
323 /*****************************************************************************/
|
paulo@0
|
324
|
paulo@0
|
325 /* parse the Range: bytes=0-10000 format */
|
paulo@0
|
326 static void parse_client_request_range (Dataset *dataset,
|
paulo@0
|
327 off_t *r_start, off_t *r_stop)
|
paulo@0
|
328 {
|
paulo@0
|
329 char *range;
|
paulo@0
|
330 off_t start;
|
paulo@0
|
331 off_t stop;
|
paulo@0
|
332
|
paulo@0
|
333 if (!r_start && !r_stop)
|
paulo@0
|
334 return;
|
paulo@0
|
335
|
paulo@0
|
336 if (r_start)
|
paulo@0
|
337 *r_start = 0;
|
paulo@0
|
338 if (r_stop)
|
paulo@0
|
339 *r_stop = 0;
|
paulo@0
|
340
|
paulo@0
|
341 /* leave stop as 0 if we can't figure anything out yet. This is expected
|
paulo@0
|
342 * to be handled separately by GET and PUSH */
|
paulo@0
|
343 if (!(range = dataset_lookupstr (dataset, "range")))
|
paulo@0
|
344 return;
|
paulo@0
|
345
|
paulo@0
|
346 /* WARNING: this butchers the data in the dataset! */
|
paulo@0
|
347 string_sep (&range, "bytes");
|
paulo@0
|
348 string_sep_set (&range, " =");
|
paulo@0
|
349
|
paulo@0
|
350 if (!range)
|
paulo@0
|
351 {
|
paulo@0
|
352 if (HTTP_DEBUG)
|
paulo@0
|
353 GT->DBGFN (GT, "error parsing Range: header");
|
paulo@0
|
354
|
paulo@0
|
355 return;
|
paulo@0
|
356 }
|
paulo@0
|
357
|
paulo@0
|
358 start = (off_t) ATOI (string_sep (&range, "-"));
|
paulo@0
|
359 stop = (off_t) ATOI (string_sep (&range, " "));
|
paulo@0
|
360
|
paulo@0
|
361 /*
|
paulo@0
|
362 * The end of the range is optional (e.g. "Range: 0-"), and in that case
|
paulo@0
|
363 * stop == 0. In the case of a single byte file, stop == 1.
|
paulo@0
|
364 *
|
paulo@0
|
365 * TODO: this is broken for one-byte requests at the start of the file.
|
paulo@0
|
366 */
|
paulo@0
|
367 if (stop > 0)
|
paulo@0
|
368 stop = stop + 1;
|
paulo@0
|
369
|
paulo@0
|
370 if (r_start)
|
paulo@0
|
371 *r_start = start;
|
paulo@0
|
372 if (r_stop)
|
paulo@0
|
373 *r_stop = stop;
|
paulo@0
|
374 }
|
paulo@0
|
375
|
paulo@0
|
376 /*
|
paulo@0
|
377 * Parse requests of the forms:
|
paulo@0
|
378 *
|
paulo@0
|
379 * /get/47/File maybe with spaces HTTP
|
paulo@0
|
380 * /get/47/files_%20_url_encoded HTTP/1.1
|
paulo@0
|
381 * /uri-res/N2R?urn:sha1:ABCD HTTP/1.0
|
paulo@0
|
382 *
|
paulo@0
|
383 * and combinations thereof. The "HTTP" trailer is mandatory.
|
paulo@0
|
384 */
|
paulo@0
|
385 static void get_request_and_version (char *data, char **request,
|
paulo@0
|
386 char **version)
|
paulo@0
|
387 {
|
paulo@0
|
388 size_t len;
|
paulo@0
|
389 char *next;
|
paulo@0
|
390 char *dup;
|
paulo@0
|
391 char *http_version = NULL;
|
paulo@0
|
392
|
paulo@0
|
393 *request = NULL;
|
paulo@0
|
394 *version = NULL;
|
paulo@0
|
395
|
paulo@0
|
396 /* trim whitespace inbetween command and request */
|
paulo@0
|
397 string_trim (data);
|
paulo@0
|
398
|
paulo@0
|
399 if (!(dup = STRDUP (data)))
|
paulo@0
|
400 return;
|
paulo@0
|
401
|
paulo@0
|
402 string_upper (dup);
|
paulo@0
|
403
|
paulo@0
|
404 next = dup;
|
paulo@0
|
405
|
paulo@0
|
406 /* find the last instance of "HTTP" in the string */
|
paulo@0
|
407 while ((next = strstr (next, "HTTP")))
|
paulo@0
|
408 {
|
paulo@0
|
409 http_version = next;
|
paulo@0
|
410 next += sizeof ("HTTP") - 1;
|
paulo@0
|
411 }
|
paulo@0
|
412
|
paulo@0
|
413 /* the rest of the string must be the request */
|
paulo@0
|
414 if (http_version != NULL && http_version != dup)
|
paulo@0
|
415 {
|
paulo@0
|
416 len = http_version - dup;
|
paulo@0
|
417 data[len - 1] = 0;
|
paulo@0
|
418
|
paulo@0
|
419 *request = data;
|
paulo@0
|
420 *version = data + len;
|
paulo@0
|
421 }
|
paulo@0
|
422
|
paulo@0
|
423 free (dup);
|
paulo@0
|
424 }
|
paulo@0
|
425
|
paulo@0
|
426 /*
|
paulo@0
|
427 * Break down the clients HTTP request
|
paulo@0
|
428 */
|
paulo@0
|
429 static int parse_client_request (Dataset **r_dataset, char **r_command,
|
paulo@0
|
430 char **r_request, char **r_version,
|
paulo@0
|
431 off_t *r_start, off_t *r_stop, char *hdr)
|
paulo@0
|
432 {
|
paulo@0
|
433 Dataset *dataset = NULL;
|
paulo@0
|
434 char *command; /* GET */
|
paulo@0
|
435 char *request; /* /file.tar.gz */
|
paulo@0
|
436 char *version; /* HTTP/1.1 */
|
paulo@0
|
437 char *req_line;
|
paulo@0
|
438
|
paulo@0
|
439 if (!hdr)
|
paulo@0
|
440 return FALSE;
|
paulo@0
|
441
|
paulo@0
|
442 /*
|
paulo@0
|
443 * Get the first line of the request
|
paulo@0
|
444 */
|
paulo@0
|
445 req_line = string_sep_set (&hdr, "\r\n");
|
paulo@0
|
446
|
paulo@0
|
447 /* get the command (GET, HEAD, etc.) */
|
paulo@0
|
448 command = string_sep (&req_line, " ");
|
paulo@0
|
449
|
paulo@0
|
450 /*
|
paulo@0
|
451 * Handle non-url-encoded requests as well as encoded
|
paulo@0
|
452 * ones and get the request and version from this HTTP line.
|
paulo@0
|
453 */
|
paulo@0
|
454 get_request_and_version (req_line, &request, &version);
|
paulo@0
|
455
|
paulo@0
|
456 if (HTTP_DEBUG)
|
paulo@0
|
457 {
|
paulo@0
|
458 GT->DBGFN (GT, "command=%s version=%s request=%s",
|
paulo@0
|
459 command, version, request);
|
paulo@0
|
460 }
|
paulo@0
|
461
|
paulo@0
|
462 if (!request || string_isempty (request))
|
paulo@0
|
463 return FALSE;
|
paulo@0
|
464
|
paulo@0
|
465 if (r_command)
|
paulo@0
|
466 *r_command = command;
|
paulo@0
|
467 if (r_request)
|
paulo@0
|
468 *r_request = request;
|
paulo@0
|
469 if (r_version)
|
paulo@0
|
470 *r_version = version;
|
paulo@0
|
471
|
paulo@0
|
472 gt_http_header_parse (hdr, &dataset);
|
paulo@0
|
473
|
paulo@0
|
474 if (r_dataset)
|
paulo@0
|
475 *r_dataset = dataset;
|
paulo@0
|
476
|
paulo@0
|
477 /* handle Range: header */
|
paulo@0
|
478 parse_client_request_range (dataset, r_start, r_stop);
|
paulo@0
|
479
|
paulo@0
|
480 if (r_start && r_stop)
|
paulo@0
|
481 {
|
paulo@0
|
482 if (HTTP_DEBUG)
|
paulo@0
|
483 GT->dbg (GT, "range: [%i, %i)", *r_start, *r_stop);
|
paulo@0
|
484 }
|
paulo@0
|
485
|
paulo@0
|
486 return TRUE;
|
paulo@0
|
487 }
|
paulo@0
|
488
|
paulo@0
|
489 /*****************************************************************************/
|
paulo@0
|
490
|
paulo@0
|
491 /*
|
paulo@0
|
492 * Send the request reply back to the client
|
paulo@0
|
493 *
|
paulo@0
|
494 * NOTE:
|
paulo@0
|
495 * This is used by both GET / and PUSH /
|
paulo@0
|
496 */
|
paulo@0
|
497 static void reply_to_client_request (GtTransfer *xfer)
|
paulo@0
|
498 {
|
paulo@0
|
499 TCPC *c;
|
paulo@0
|
500 Chunk *chunk;
|
paulo@0
|
501 off_t entity_size;
|
paulo@0
|
502 char range[128];
|
paulo@0
|
503 char length[32];
|
paulo@0
|
504 BOOL ret;
|
paulo@0
|
505
|
paulo@0
|
506 if (!xfer)
|
paulo@0
|
507 return;
|
paulo@0
|
508
|
paulo@0
|
509 c = gt_transfer_get_tcpc (xfer);
|
paulo@0
|
510 chunk = gt_transfer_get_chunk (xfer);
|
paulo@0
|
511
|
paulo@0
|
512 /*
|
paulo@0
|
513 * Determine the "total" entity body that we have locally, not necessarily
|
paulo@0
|
514 * the data that we are uploading. HTTP demands this, but OpenFT really
|
paulo@0
|
515 * doesn't give a shit.
|
paulo@0
|
516 *
|
paulo@0
|
517 * NOTE:
|
paulo@0
|
518 * This only works to standard when operating on a GET / request, PUSH's
|
paulo@0
|
519 * merely use the range!
|
paulo@0
|
520 */
|
paulo@0
|
521 if (xfer->open_path_size)
|
paulo@0
|
522 entity_size = xfer->open_path_size;
|
paulo@0
|
523 else
|
paulo@0
|
524 entity_size = xfer->stop - xfer->start;
|
paulo@0
|
525
|
paulo@0
|
526 /* NOTE: we are "working" around the fact that HTTP's Content-Range
|
paulo@0
|
527 * reply is inclusive for the last byte, whereas giFT's is not. */
|
paulo@0
|
528 snprintf (range, sizeof (range) - 1, "bytes %i-%i/%i",
|
paulo@0
|
529 (int) xfer->start, (int) (xfer->stop - 1), (int) entity_size);
|
paulo@0
|
530
|
paulo@0
|
531 snprintf (length, sizeof (length) - 1, "%i",
|
paulo@0
|
532 (int) (xfer->stop - xfer->start));
|
paulo@0
|
533
|
paulo@0
|
534 ret = gt_http_server_send (c, xfer->code,
|
paulo@0
|
535 "Content-Range", range,
|
paulo@0
|
536 "Content-Length", length,
|
paulo@0
|
537 "Content-Type", xfer->content_type,
|
paulo@0
|
538 CONTENT_URN_FIELD, xfer->content_urns,
|
paulo@0
|
539 NULL);
|
paulo@0
|
540
|
paulo@0
|
541 /* if we transmitted all headers successfully, set transmitted_hdrs
|
paulo@0
|
542 * to keep the connection alive, possibly */
|
paulo@0
|
543 if (ret)
|
paulo@0
|
544 xfer->transmitted_hdrs = TRUE;
|
paulo@0
|
545 }
|
paulo@0
|
546
|
paulo@0
|
547 /*****************************************************************************/
|
paulo@0
|
548
|
paulo@0
|
549 static void http_incoming_free (struct http_incoming *incoming)
|
paulo@0
|
550 {
|
paulo@0
|
551 timer_remove (incoming->timer);
|
paulo@0
|
552 free (incoming);
|
paulo@0
|
553 }
|
paulo@0
|
554
|
paulo@0
|
555 static void http_incoming_close (struct http_incoming *incoming)
|
paulo@0
|
556 {
|
paulo@0
|
557 gt_http_connection_close (GT_TRANSFER_UPLOAD, incoming->c, TRUE);
|
paulo@0
|
558 http_incoming_free (incoming);
|
paulo@0
|
559 }
|
paulo@0
|
560
|
paulo@0
|
561 static BOOL http_incoming_timeout (struct http_incoming *incoming)
|
paulo@0
|
562 {
|
paulo@0
|
563 http_incoming_close (incoming);
|
paulo@0
|
564 return FALSE;
|
paulo@0
|
565 }
|
paulo@0
|
566
|
paulo@0
|
567 static struct http_incoming *http_incoming_alloc (TCPC *c)
|
paulo@0
|
568 {
|
paulo@0
|
569 struct http_incoming *incoming;
|
paulo@0
|
570
|
paulo@0
|
571 incoming = malloc (sizeof (struct http_incoming));
|
paulo@0
|
572 if (!incoming)
|
paulo@0
|
573 return NULL;
|
paulo@0
|
574
|
paulo@0
|
575 incoming->c = c;
|
paulo@0
|
576 incoming->timer = timer_add (INCOMING_TIMEOUT,
|
paulo@0
|
577 (TimerCallback)http_incoming_timeout,
|
paulo@0
|
578 incoming);
|
paulo@0
|
579
|
paulo@0
|
580 return incoming;
|
paulo@0
|
581 }
|
paulo@0
|
582
|
paulo@0
|
583 void gt_http_server_dispatch (int fd, input_id id, TCPC *c)
|
paulo@0
|
584 {
|
paulo@0
|
585 struct http_incoming *incoming;
|
paulo@0
|
586
|
paulo@0
|
587 if (net_sock_error (c->fd))
|
paulo@0
|
588 {
|
paulo@0
|
589 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE);
|
paulo@0
|
590 return;
|
paulo@0
|
591 }
|
paulo@0
|
592
|
paulo@0
|
593 if (!(incoming = http_incoming_alloc (c)))
|
paulo@0
|
594 {
|
paulo@0
|
595 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE);
|
paulo@0
|
596 return;
|
paulo@0
|
597 }
|
paulo@0
|
598
|
paulo@0
|
599 /* keep track of this incoming connection */
|
paulo@0
|
600 /* gt_http_connection_insert (GT_TRANSFER_UPLOAD, c); */
|
paulo@0
|
601
|
paulo@0
|
602 input_remove (id);
|
paulo@0
|
603 input_add (c->fd, incoming, INPUT_READ,
|
paulo@0
|
604 (InputCallback)get_client_request, 0);
|
paulo@0
|
605 }
|
paulo@0
|
606
|
paulo@0
|
607 /*
|
paulo@0
|
608 * Handle the client's GET commands.
|
paulo@0
|
609 */
|
paulo@0
|
610 static void get_client_request (int fd, input_id id, struct http_incoming *http)
|
paulo@0
|
611 {
|
paulo@0
|
612 GtTransfer *xfer;
|
paulo@0
|
613 TCPC *c;
|
paulo@0
|
614 Dataset *dataset = NULL;
|
paulo@0
|
615 char *command = NULL;
|
paulo@0
|
616 char *request = NULL;
|
paulo@0
|
617 char *version = NULL;
|
paulo@0
|
618 off_t start = 0;
|
paulo@0
|
619 off_t stop = 0;
|
paulo@0
|
620 FDBuf *buf;
|
paulo@0
|
621 unsigned char *data;
|
paulo@0
|
622 size_t data_len = 0;
|
paulo@0
|
623 int n;
|
paulo@0
|
624
|
paulo@0
|
625 c = http->c;
|
paulo@0
|
626 buf = tcp_readbuf (c);
|
paulo@0
|
627
|
paulo@0
|
628 if ((n = fdbuf_delim (buf, "\n")) < 0)
|
paulo@0
|
629 {
|
paulo@0
|
630 http_incoming_close (http);
|
paulo@0
|
631 return;
|
paulo@0
|
632 }
|
paulo@0
|
633
|
paulo@0
|
634 if (gt_fdbuf_full (buf))
|
paulo@0
|
635 {
|
paulo@0
|
636 http_incoming_close (http);
|
paulo@0
|
637 return;
|
paulo@0
|
638 }
|
paulo@0
|
639
|
paulo@0
|
640 if (n > 0)
|
paulo@0
|
641 return;
|
paulo@0
|
642
|
paulo@0
|
643 data = fdbuf_data (buf, &data_len);
|
paulo@0
|
644
|
paulo@0
|
645 if (!gt_http_header_terminated (data, data_len))
|
paulo@0
|
646 return;
|
paulo@0
|
647
|
paulo@0
|
648 fdbuf_release (buf);
|
paulo@0
|
649
|
paulo@0
|
650 if (HTTP_DEBUG)
|
paulo@0
|
651 GT->DBGSOCK (GT, c, "client request:\n%s", data);
|
paulo@0
|
652
|
paulo@0
|
653 /* parse the client's request and determine how we should proceed */
|
paulo@0
|
654 if (!parse_client_request (&dataset, &command, &request, &version,
|
paulo@0
|
655 &start, &stop, data))
|
paulo@0
|
656 {
|
paulo@0
|
657 GT->DBGSOCK (GT, c, "invalid http header");
|
paulo@0
|
658 http_incoming_close (http);
|
paulo@0
|
659 return;
|
paulo@0
|
660 }
|
paulo@0
|
661
|
paulo@0
|
662 /* discard incoming connection timeout maintainance structure */
|
paulo@0
|
663 http_incoming_free (http);
|
paulo@0
|
664
|
paulo@0
|
665 /*
|
paulo@0
|
666 * We have enough information now to actually allocate the transfer
|
paulo@0
|
667 * structure and pass it along to all logic that follows this
|
paulo@0
|
668 *
|
paulo@0
|
669 * NOTE:
|
paulo@0
|
670 * Each individual handler can determine when it wants to let giFT
|
paulo@0
|
671 * in on this
|
paulo@0
|
672 */
|
paulo@0
|
673 xfer = gt_transfer_new (GT_TRANSFER_UPLOAD, NULL,
|
paulo@0
|
674 net_peer (c->fd), 0, start, stop);
|
paulo@0
|
675
|
paulo@0
|
676 /* connect the connection and the xfer in unholy matrimony */
|
paulo@0
|
677 gt_transfer_set_tcpc (xfer, c);
|
paulo@0
|
678
|
paulo@0
|
679 /* assign all our own memory */
|
paulo@0
|
680 xfer->command = STRDUP (command);
|
paulo@0
|
681 xfer->header = dataset;
|
paulo@0
|
682 xfer->version = STRDUP (version);
|
paulo@0
|
683
|
paulo@0
|
684 if (!gt_transfer_set_request (xfer, request))
|
paulo@0
|
685 {
|
paulo@0
|
686 if (HTTP_DEBUG)
|
paulo@0
|
687 GT->DBGSOCK (GT, c, "invalid request \"s\"", request);
|
paulo@0
|
688
|
paulo@0
|
689 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
690 return;
|
paulo@0
|
691 }
|
paulo@0
|
692
|
paulo@0
|
693 /* no need for this function again */
|
paulo@0
|
694 input_remove (id);
|
paulo@0
|
695
|
paulo@0
|
696 /* figure out how to handle this request */
|
paulo@0
|
697 if (!strcasecmp (xfer->command, "GET") ||
|
paulo@0
|
698 !strcasecmp (xfer->command, "HEAD"))
|
paulo@0
|
699 {
|
paulo@0
|
700 server_handle_get (xfer);
|
paulo@0
|
701 return;
|
paulo@0
|
702 }
|
paulo@0
|
703
|
paulo@0
|
704 gt_http_server_send_error_and_close (xfer, 501);
|
paulo@0
|
705 }
|
paulo@0
|
706
|
paulo@0
|
707 /*****************************************************************************/
|
paulo@0
|
708
|
paulo@0
|
709 static Transfer *start_upload (GtTransfer *xfer, Chunk **chunk)
|
paulo@0
|
710 {
|
paulo@0
|
711 Transfer *transfer;
|
paulo@0
|
712 char *user;
|
paulo@0
|
713
|
paulo@0
|
714 user = net_ip_str (xfer->ip);
|
paulo@0
|
715
|
paulo@0
|
716 transfer = GT->upload_start (GT, chunk, user, xfer->share_authd,
|
paulo@0
|
717 xfer->start, xfer->stop);
|
paulo@0
|
718
|
paulo@0
|
719 assert (transfer != NULL);
|
paulo@0
|
720
|
paulo@0
|
721 return transfer;
|
paulo@0
|
722 }
|
paulo@0
|
723
|
paulo@0
|
724 /* setup the structure for uploading. this will be called from within
|
paulo@0
|
725 * client space for PUSH requests as well */
|
paulo@0
|
726 int gt_server_setup_upload (GtTransfer *xfer)
|
paulo@0
|
727 {
|
paulo@0
|
728 Transfer *transfer; /* giFT structure */
|
paulo@0
|
729 Chunk *chunk;
|
paulo@0
|
730 TCPC *c;
|
paulo@0
|
731
|
paulo@0
|
732 if (!xfer)
|
paulo@0
|
733 return FALSE;
|
paulo@0
|
734
|
paulo@0
|
735 c = gt_transfer_get_tcpc (xfer);
|
paulo@0
|
736 assert (xfer->chunk == NULL);
|
paulo@0
|
737
|
paulo@0
|
738 /*
|
paulo@0
|
739 * Ban the host if they don't have access -- this gives no information
|
paulo@0
|
740 * about whether we have the file or not, and i think this is in violation
|
paulo@0
|
741 * of the HTTP spec (supposed to return "404 not found" before 403, but
|
paulo@0
|
742 * i'm not sure.
|
paulo@0
|
743 */
|
paulo@0
|
744 if (gt_ban_ipv4_is_banned (c->host))
|
paulo@0
|
745 {
|
paulo@0
|
746 xfer->code = 403;
|
paulo@0
|
747 return FALSE;
|
paulo@0
|
748 }
|
paulo@0
|
749
|
paulo@0
|
750 /* open the file that was requested before we go bringing giFT into
|
paulo@0
|
751 * this */
|
paulo@0
|
752 if (!(xfer->f = gt_transfer_open_request (xfer, &xfer->code)))
|
paulo@0
|
753 return FALSE;
|
paulo@0
|
754
|
paulo@0
|
755 /* assign stop a value before we proceed */
|
paulo@0
|
756 if (xfer->stop == 0)
|
paulo@0
|
757 {
|
paulo@0
|
758 struct stat st;
|
paulo@0
|
759
|
paulo@0
|
760 if (!file_stat (xfer->open_path, &st) || st.st_size == 0)
|
paulo@0
|
761 {
|
paulo@0
|
762 /* stupid bastards have a 0 length file */
|
paulo@0
|
763 GT->DBGSOCK (GT, c, "cannot satisfy %s: invalid share",
|
paulo@0
|
764 xfer->open_path);
|
paulo@0
|
765 return FALSE;
|
paulo@0
|
766 }
|
paulo@0
|
767
|
paulo@0
|
768 xfer->stop = st.st_size;
|
paulo@0
|
769 xfer->remaining_len = xfer->stop - xfer->start;
|
paulo@0
|
770 }
|
paulo@0
|
771
|
paulo@0
|
772 /* we can now be certain that we are handling a download request from
|
paulo@0
|
773 * the client. allocate the appropriate structures to hook into giFT */
|
paulo@0
|
774 if (!(transfer = start_upload (xfer, &chunk)))
|
paulo@0
|
775 {
|
paulo@0
|
776 GT->DBGFN (GT, "unable to register upload with the daemon");
|
paulo@0
|
777 return FALSE;
|
paulo@0
|
778 }
|
paulo@0
|
779
|
paulo@0
|
780 /* override 200 w/ 206 if the request is not the whole file size */
|
paulo@0
|
781 if (xfer->remaining_len != xfer->share_authd->size)
|
paulo@0
|
782 xfer->code = 206;
|
paulo@0
|
783
|
paulo@0
|
784 /* assign the circular references for passing the chunk along */
|
paulo@0
|
785 gt_transfer_set_chunk (xfer, chunk);
|
paulo@0
|
786
|
paulo@0
|
787 /* finally, seek the file descriptor where it needs to be */
|
paulo@0
|
788 fseek (xfer->f, xfer->start, SEEK_SET);
|
paulo@0
|
789
|
paulo@0
|
790 return TRUE;
|
paulo@0
|
791 }
|
paulo@0
|
792
|
paulo@0
|
793 static void server_handle_get (GtTransfer *xfer)
|
paulo@0
|
794 {
|
paulo@0
|
795 TCPC *c;
|
paulo@0
|
796
|
paulo@0
|
797 c = gt_transfer_get_tcpc (xfer);
|
paulo@0
|
798 assert (xfer->chunk == NULL);
|
paulo@0
|
799
|
paulo@0
|
800 /* WARNING: this block is duplicated in http_client:client_push_request */
|
paulo@0
|
801 if (!gt_server_setup_upload (xfer))
|
paulo@0
|
802 {
|
paulo@0
|
803 if (xfer->code == 200)
|
paulo@0
|
804 xfer->code = 404;
|
paulo@0
|
805
|
paulo@0
|
806 gt_http_server_send_error_and_close (xfer, xfer->code);
|
paulo@0
|
807 return;
|
paulo@0
|
808 }
|
paulo@0
|
809
|
paulo@0
|
810 input_add (c->fd, xfer, INPUT_WRITE,
|
paulo@0
|
811 (InputCallback)send_http_response, TIMEOUT_DEF);
|
paulo@0
|
812 }
|
paulo@0
|
813
|
paulo@0
|
814 static void send_http_response (int fd, input_id id, GtTransfer *xfer)
|
paulo@0
|
815 {
|
paulo@0
|
816 TCPC *c;
|
paulo@0
|
817
|
paulo@0
|
818 c = gt_transfer_get_tcpc (xfer);
|
paulo@0
|
819
|
paulo@0
|
820 if (net_sock_error (c->fd))
|
paulo@0
|
821 {
|
paulo@0
|
822 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
823 return;
|
paulo@0
|
824 }
|
paulo@0
|
825
|
paulo@0
|
826 /* ok, send client the header */
|
paulo@0
|
827 reply_to_client_request (xfer);
|
paulo@0
|
828
|
paulo@0
|
829 if (!strcasecmp (xfer->command, "HEAD"))
|
paulo@0
|
830 {
|
paulo@0
|
831 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
832 return;
|
paulo@0
|
833 }
|
paulo@0
|
834
|
paulo@0
|
835 /* disable header read timer */
|
paulo@0
|
836 timer_remove_zero (&xfer->header_timer);
|
paulo@0
|
837
|
paulo@0
|
838 input_remove (id);
|
paulo@0
|
839 input_add (c->fd, xfer, INPUT_WRITE,
|
paulo@0
|
840 (InputCallback)gt_server_upload_file, 0);
|
paulo@0
|
841 }
|
paulo@0
|
842
|
paulo@0
|
843 /*
|
paulo@0
|
844 * Uploads the file requests
|
paulo@0
|
845 */
|
paulo@0
|
846 void gt_server_upload_file (int fd, input_id id, GtTransfer *xfer)
|
paulo@0
|
847 {
|
paulo@0
|
848 TCPC *c;
|
paulo@0
|
849 Chunk *chunk;
|
paulo@0
|
850 char buf[RW_BUFFER];
|
paulo@0
|
851 size_t read_len;
|
paulo@0
|
852 size_t size;
|
paulo@0
|
853 int sent_len = 0;
|
paulo@0
|
854 off_t remainder;
|
paulo@0
|
855
|
paulo@0
|
856 c = gt_transfer_get_tcpc (xfer);
|
paulo@0
|
857 chunk = gt_transfer_get_chunk (xfer);
|
paulo@0
|
858
|
paulo@0
|
859 assert (xfer->f != NULL);
|
paulo@0
|
860
|
paulo@0
|
861 /* number of bytes left to be uploaded by this chunk */
|
paulo@0
|
862 if ((remainder = xfer->remaining_len) <= 0)
|
paulo@0
|
863 {
|
paulo@0
|
864 /* for whatever reason this function may have been called when we have
|
paulo@0
|
865 * already overrun the transfer...in that case we will simply fall
|
paulo@0
|
866 * through to the end-of-transfer condition */
|
paulo@0
|
867 gt_transfer_write (xfer, chunk, NULL, 0);
|
paulo@0
|
868 return;
|
paulo@0
|
869 }
|
paulo@0
|
870
|
paulo@0
|
871 size = sizeof (buf);
|
paulo@0
|
872
|
paulo@0
|
873 if (size > remainder)
|
paulo@0
|
874 size = remainder;
|
paulo@0
|
875
|
paulo@0
|
876 /*
|
paulo@0
|
877 * Ask giFT for the size we should send. If this returns 0, the upload
|
paulo@0
|
878 * was suspended.
|
paulo@0
|
879 */
|
paulo@0
|
880 if ((size = upload_throttle (chunk, size)) == 0)
|
paulo@0
|
881 return;
|
paulo@0
|
882
|
paulo@0
|
883 /* read as much as we can from the local file */
|
paulo@0
|
884 if (!(read_len = fread (buf, sizeof (char), size, xfer->f)))
|
paulo@0
|
885 {
|
paulo@0
|
886 GT->DBGFN (GT, "unable to read from %s: %s", xfer->open_path,
|
paulo@0
|
887 GIFT_STRERROR ());
|
paulo@0
|
888 gt_transfer_status (xfer, SOURCE_CANCELLED, "Local read error");
|
paulo@0
|
889 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
890 return;
|
paulo@0
|
891 }
|
paulo@0
|
892
|
paulo@0
|
893 if ((sent_len = tcp_send (c, buf, MIN (read_len, remainder))) <= 0)
|
paulo@0
|
894 {
|
paulo@0
|
895 gt_transfer_status (xfer, SOURCE_CANCELLED,
|
paulo@0
|
896 "Unable to send data block");
|
paulo@0
|
897 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
898 return;
|
paulo@0
|
899 }
|
paulo@0
|
900
|
paulo@0
|
901 /* check if the file was too small for the transfer TODO: this isn't
|
paulo@0
|
902 * checked earlier, but should be */
|
paulo@0
|
903 if (read_len != size)
|
paulo@0
|
904 {
|
paulo@0
|
905 gt_transfer_status (xfer, SOURCE_CANCELLED, "Unexpected end of file");
|
paulo@0
|
906 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
907 return;
|
paulo@0
|
908 }
|
paulo@0
|
909
|
paulo@0
|
910 /*
|
paulo@0
|
911 * Check for short send(). This could use fseek(), but I have this
|
paulo@0
|
912 * growing feeling that using stdio everywhere is a bad idea.
|
paulo@0
|
913 */
|
paulo@0
|
914 if (read_len != sent_len)
|
paulo@0
|
915 {
|
paulo@0
|
916 gt_transfer_status (xfer, SOURCE_CANCELLED, "Short send()");
|
paulo@0
|
917 gt_transfer_close (xfer, TRUE);
|
paulo@0
|
918 return;
|
paulo@0
|
919 }
|
paulo@0
|
920
|
paulo@0
|
921 /*
|
paulo@0
|
922 * Call gt_upload to report back to giFT. This will also cancel
|
paulo@0
|
923 * the transfer if the upload has completed.
|
paulo@0
|
924 */
|
paulo@0
|
925 gt_transfer_write (xfer, chunk, buf, sent_len);
|
paulo@0
|
926 }
|
paulo@0
|
927
|
paulo@0
|
928 /*****************************************************************************/
|
paulo@0
|
929
|
paulo@0
|
930 void gt_http_server_reset (TCPC *c)
|
paulo@0
|
931 {
|
paulo@0
|
932 /*
|
paulo@0
|
933 * This can happen because the GtTransfer and TCPC can be decoupled, as in
|
paulo@0
|
934 * the case of a push request sent.
|
paulo@0
|
935 */
|
paulo@0
|
936 if (!c)
|
paulo@0
|
937 return;
|
paulo@0
|
938
|
paulo@0
|
939 /* finish all queued writes before we reset */
|
paulo@0
|
940 tcp_flush (c, TRUE);
|
paulo@0
|
941
|
paulo@0
|
942 /* reset the input state */
|
paulo@0
|
943 input_remove_all (c->fd);
|
paulo@0
|
944 input_add (c->fd, c, INPUT_READ,
|
paulo@0
|
945 (InputCallback)gt_http_server_dispatch, 2 * MINUTES);
|
paulo@0
|
946 }
|