root/src/noit_http.c

Revision 652d766e888968c4809a0101e9cca758fc88f521, 33.0 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 5 years ago)

cleanup warning and handle an EVENTER_EXCEPTION, refs #34

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34 #include "noit_http.h"
35 #include "utils/noit_str.h"
36
37 #include <errno.h>
38 #include <ctype.h>
39 #include <assert.h>
40 #include <zlib.h>
41 #include <libxml/tree.h>
42
43 #define REQ_PAT "\r\n\r\n"
44 #define REQ_PATSIZE 4
45 #define HEADER_CONTENT_LENGTH "content-length"
46 #define HEADER_EXPECT "expect"
47
48 #define CTX_ADD_HEADER(a,b) \
49     noit_hash_replace(&ctx->res.headers, \
50                       strdup(a), strlen(a), strdup(b), free, free)
51 static const char _hexchars[16] =
52   {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
53
54 struct bchain *bchain_alloc(size_t size) {
55   struct bchain *n;
56   n = malloc(size + ((void *)n->buff - (void *)n));
57   if(!n) return NULL;
58   n->prev = n->next = NULL;
59   n->start = n->size = 0;
60   n->allocd = size;
61   return n;
62 }
63 #define RELEASE_BCHAIN(a) do { \
64   while(a) { \
65     struct bchain *__b; \
66     __b = a; \
67     a = __b->next; \
68     bchain_free(__b); \
69   } \
70 } while(0)
71 struct bchain *bchain_from_data(const void *d, size_t size) {
72   struct bchain *n;
73   n = bchain_alloc(size);
74   if(!n) return NULL;
75   memcpy(n->buff, d, size);
76   n->size = size;
77   return n;
78 }
79 void bchain_free(struct bchain *b) {
80   free(b);
81 }
82
83 static noit_http_method
84 _method_enum(const char *s) {
85   switch(*s) {
86    case 'G':
87     if(!strcasecmp(s, "GET")) return NOIT_HTTP_GET;
88     break;
89    case 'H':
90     if(!strcasecmp(s, "HEAD")) return NOIT_HTTP_HEAD;
91     break;
92    case 'P':
93     if(!strcasecmp(s, "POST")) return NOIT_HTTP_POST;
94     break;
95    default:
96     break;
97   }
98   return NOIT_HTTP_OTHER;
99 }
100 static noit_http_protocol
101 _protocol_enum(const char *s) {
102   if(!strcasecmp(s, "HTTP/1.1")) return NOIT_HTTP11;
103   if(!strcasecmp(s, "HTTP/1.0")) return NOIT_HTTP10;
104   return NOIT_HTTP09;
105 }
106 static noit_boolean
107 _fixup_bchain(struct bchain *b) {
108   /* make sure lines (CRLF terminated) don't cross chain boundaries */
109   while(b) {
110     struct bchain *f;
111     int start_in_b, end_in_f;
112     size_t new_size;
113     const char *str_in_f;
114
115     start_in_b = b->start;
116     if(b->size > 2) {
117       if(memcmp(b->buff + b->start + b->size - 2, "\r\n", 2) == 0) {
118         b = b->next;
119         continue;
120       }
121       start_in_b = b->start + b->size - 3; /* we already checked -2 */
122       while(start_in_b >= b->start) {
123         if(memcmp(b->buff + start_in_b, "\r\n", 2) == 0) {
124           start_in_b += 2;
125           break;
126         }
127       }
128     }
129
130     /* start_in_b points to the beginning of the string we need to build
131      * into a new buffer.
132      */
133     f = b->next;
134     if(!f) return noit_false; /* Nothing left, can't complete the line */
135     str_in_f = strnstrn("\r\n", 2, f->buff + f->start, f->size);
136     if(!str_in_f) return noit_false; /* nothing in next chain -- too long */
137     str_in_f += 2;
138     end_in_f = (str_in_f - f->buff - f->start);
139     new_size = end_in_f + (b->start + b->size - start_in_b);
140     if(new_size > DEFAULT_BCHAINSIZE) return noit_false; /* string too long */
141     f = bchain_alloc(new_size);
142     f->prev = b;
143     f->next = b->next;
144     f->start = 0;
145     f->size = new_size;
146     memcpy(f->buff, b->buff + start_in_b, b->start + b->size - start_in_b);
147     memcpy(f->buff + b->start + b->size - start_in_b,
148            f->buff + f->start, end_in_f);
149     f->next->prev = f;
150     f->prev->next = f;
151     f->prev->size -= start_in_b - b->start;
152     f->next->size -= end_in_f;
153     f->next->start += end_in_f;
154     b = f->next; /* skip f, we know it is right */
155   }
156   return noit_true;
157 }
158 static noit_boolean
159 _extract_header(char *l, const char **n, const char **v) {
160   *n = NULL;
161   if(*l == ' ' || *l == '\t') {
162     while(*l == ' ' || *l == '\t') l++;
163     *v = l;
164     return noit_true;
165   }
166   *n = l;
167   while(*l != ':' && *l) { *l = tolower(*l); l++; }
168   if(!*l) return noit_false;
169   *v = l+1;
170   /* Right trim the name */
171   *l-- = '\0';
172   while(*l == ' ' || *l == '\t') *l-- = '\0';
173   while(**v == ' ' || **v == '\t') (*v)++;
174   return noit_true;
175 }
176 static int
177 _http_perform_write(noit_http_session_ctx *ctx, int *mask) {
178   int len, tlen = 0;
179   struct bchain **head, *b;
180  choose_bucket:
181   head = ctx->res.leader ? &ctx->res.leader : &ctx->res.output_raw;
182   b = *head;
183
184   if(!ctx->conn.e) return 0;
185 #if 0
186   if(ctx->res.output_started == noit_false) return EVENTER_EXCEPTION;
187 #endif
188   if(!b) {
189     if(ctx->res.closed) ctx->res.complete = noit_true;
190     *mask = EVENTER_EXCEPTION;
191     return tlen;
192   }
193
194   if(ctx->res.output_raw_offset >= b->size) {
195     *head = b->next;
196     bchain_free(b);
197     b = *head;
198     if(b) b->prev = NULL;
199     ctx->res.output_raw_offset = 0;
200     goto choose_bucket;
201   }
202
203   len = ctx->conn.e->opset->
204           write(ctx->conn.e->fd,
205                 b->buff + b->start + ctx->res.output_raw_offset,
206                 b->size - ctx->res.output_raw_offset,
207                 mask, ctx->conn.e);
208   if(len == -1 && errno == EAGAIN) {
209     *mask |= EVENTER_EXCEPTION;
210     return tlen;
211   }
212   if(len == -1) {
213     /* socket error */
214     ctx->conn.e->opset->close(ctx->conn.e->fd, mask, ctx->conn.e);
215     ctx->conn.e = NULL;
216     return -1;
217   }
218   ctx->res.output_raw_offset += len;
219   tlen += len;
220   goto choose_bucket;
221 }
222 static noit_boolean
223 noit_http_request_finalize_headers(noit_http_request *req, noit_boolean *err) {
224   int start;
225   void *vval;
226   const char *mstr, *last_name = NULL;
227   struct bchain *b;
228
229   if(req->state != NOIT_HTTP_REQ_HEADERS) return noit_false;
230   if(!req->current_input) req->current_input = req->first_input;
231   if(!req->current_input) return noit_false;
232  restart:
233   while(req->current_input->prev &&
234         (req->current_offset < (req->current_input->start + REQ_PATSIZE - 1))) {
235     int inset;
236     /* cross bucket */
237     if(req->current_input == req->last_input &&
238        req->current_offset >= (req->last_input->start + req->last_input->size))
239       return noit_false;
240     req->current_offset++;
241     inset = req->current_offset - req->current_input->start;
242     if(memcmp(req->current_input->buff + req->current_input->start,
243               REQ_PAT + (REQ_PATSIZE - inset), inset) == 0 &&
244        memcmp(req->current_input->prev->buff +
245                 req->current_input->prev->start +
246                 req->current_input->prev->size - REQ_PATSIZE + inset,
247               REQ_PAT + inset,
248               REQ_PATSIZE - inset) == 0) goto match;
249   }
250   start = MAX(req->current_offset - REQ_PATSIZE, req->current_input->start);
251   mstr = strnstrn(REQ_PAT, REQ_PATSIZE,
252                   req->current_input->buff + start,
253                   req->current_input->size -
254                     (start - req->current_input->start));
255   if(!mstr && req->current_input->next) {
256     req->current_input = req->current_input->next;
257     req->current_offset = req->current_input->start;
258     goto restart;
259   }
260   if(!mstr) return noit_false;
261   req->current_offset = mstr - req->current_input->buff + REQ_PATSIZE;
262  match:
263   req->current_request_chain = req->first_input;
264   noitL(noit_debug, " noit_http_request_finalize : match(%d in %d)\n",
265         (int)(req->current_offset - req->current_input->start),
266         (int)req->current_input->size);
267   if(req->current_offset <
268      req->current_input->start + req->current_input->size) {
269     /* There are left-overs */
270     int lsize = req->current_input->size - req->current_offset;
271     noitL(noit_debug, " noit_http_request_finalize -- leftovers: %d\n", lsize);
272     req->first_input = bchain_alloc(lsize);
273     req->first_input->prev = NULL;
274     req->first_input->next = req->current_input->next;
275     req->first_input->start = 0;
276     req->first_input->size = lsize;
277     memcpy(req->first_input->buff,
278            req->current_input->buff + req->current_offset,
279            req->first_input->size);
280     if(req->last_input == req->current_input)
281       req->last_input = req->first_input;
282     else
283       bchain_free(req->current_input);
284   }
285   else {
286     req->first_input = req->last_input = NULL;
287   }
288   req->current_input = NULL;
289   req->current_offset = 0;
290
291   /* Now we need to dissect the current_request_chain into an HTTP request */
292   /* First step: make sure that no line crosses a chain boundary by
293    * inserting new chains as necessary.
294    */
295   if(!_fixup_bchain(req->current_request_chain)) {
296     *err = noit_true;
297     return noit_false;
298   }
299   /* Second step is to parse out the request itself */
300   for(b = req->current_request_chain; b; b = b->next) {
301     char *curr_str, *next_str;
302     b->buff[b->start + b->size - 2] = '\0';
303     curr_str = b->buff + b->start;
304     do {
305       next_str = strstr(curr_str, "\r\n");
306       if(next_str) {
307         *((char *)next_str) = '\0';
308         next_str += 2;
309       }
310       if(req->method_str && *curr_str == '\0')
311         break; /* our CRLFCRLF... end of req */
312 #define FAIL do { *err = noit_true; return noit_false; } while(0)
313       if(!req->method_str) { /* request line */
314         req->method_str = (char *)curr_str;
315         req->uri_str = strchr(curr_str, ' ');
316         if(!req->uri_str) FAIL;
317         *(req->uri_str) = '\0';
318         req->uri_str++;
319         req->protocol_str = strchr(req->uri_str, ' ');
320         if(!req->protocol_str) FAIL;
321         *(req->protocol_str) = '\0';
322         req->protocol_str++;
323         req->method = _method_enum(req->method_str);
324         req->protocol = _protocol_enum(req->protocol_str);
325         if(req->protocol == NOIT_HTTP11) req->opts |= NOIT_HTTP_CHUNKED;
326       }
327       else { /* request headers */
328         const char *name, *value;
329         if(_extract_header(curr_str, &name, &value) == noit_false) FAIL;
330         if(!name && !last_name) FAIL;
331         if(!strcmp(name ? name : last_name, "accept-encoding")) {
332           if(strstr(value, "gzip")) req->opts |= NOIT_HTTP_GZIP;
333           if(strstr(value, "deflate")) req->opts |= NOIT_HTTP_DEFLATE;
334         }
335         if(name)
336           noit_hash_replace(&req->headers, name, strlen(name), (void *)value,
337                             NULL, NULL);
338         else {
339           struct bchain *b;
340           const char *prefix = NULL;
341           int l1, l2;
342           noit_hash_retr_str(&req->headers, last_name, strlen(last_name),
343                              &prefix);
344           if(!prefix) FAIL;
345           l1 = strlen(prefix);
346           l2 = strlen(value);
347           b = bchain_alloc(l1 + l2 + 2);
348           b->next = req->current_request_chain;
349           b->next->prev = b;
350           req->current_request_chain = b;
351           b->size = l1 + l2 + 2;
352           memcpy(b->buff, prefix, l1);
353           b->buff[l1] = ' ';
354           memcpy(b->buff + l1 + 1, value, l2);
355           b->buff[l1 + 1 + l2] = '\0';
356           noit_hash_replace(&req->headers, last_name, strlen(last_name),
357                             b->buff, NULL, NULL);
358         }
359         if(name) last_name = name;
360       }
361       curr_str = next_str;
362     } while(next_str);
363   }
364
365   /* headers are done... we could need to read a payload */
366   if(noit_hash_retrieve(&req->headers,
367                         HEADER_CONTENT_LENGTH,
368                         sizeof(HEADER_CONTENT_LENGTH)-1, &vval)) {
369     const char *val = vval;
370     req->has_payload = noit_true;
371     req->content_length = strtoll(val, NULL, 10);
372   }
373   if(noit_hash_retrieve(&req->headers, HEADER_EXPECT,
374                         sizeof(HEADER_EXPECT)-1, &vval)) {
375     const char *val = vval;
376     if(strncmp(val, "100-", 4) || /* Bad expect header */
377        req->has_payload == noit_false) /* expect, but no content length */
378       FAIL;
379     /* We need to tell the client to "go-ahead" -- HTTP sucks */
380     req->state = NOIT_HTTP_REQ_EXPECT;
381     return noit_false;
382   }
383   if(req->content_length > 0) {
384     /* switch modes... let's go read the payload */
385     req->state = NOIT_HTTP_REQ_PAYLOAD;
386     return noit_false;
387   }
388
389   req->complete = noit_true;
390   return noit_true;
391 }
392 static noit_boolean
393 noit_http_request_finalize_payload(noit_http_request *req, noit_boolean *err) {
394   req->complete = noit_true;
395   return noit_true;
396 }
397 static noit_boolean
398 noit_http_request_finalize(noit_http_request *req, noit_boolean *err) {
399   if(req->state == NOIT_HTTP_REQ_HEADERS)
400     if(noit_http_request_finalize_headers(req, err)) return noit_true;
401   if(req->state == NOIT_HTTP_REQ_EXPECT) return noit_false;
402   if(req->state == NOIT_HTTP_REQ_PAYLOAD)
403     if(noit_http_request_finalize_payload(req, err)) return noit_true;
404   return noit_false;
405 }
406 int
407 noit_http_complete_request(noit_http_session_ctx *ctx, int mask) {
408   struct bchain *in;
409   noit_boolean rv, err = noit_false;
410
411   if(mask & EVENTER_EXCEPTION) {
412    full_error:
413     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
414     ctx->conn.e = NULL;
415     return 0;
416   }
417   if(ctx->req.complete == noit_true) return EVENTER_EXCEPTION;
418
419   /* We could have a complete request in the tail of a previous request */
420   rv = noit_http_request_finalize(&ctx->req, &err);
421   if(rv == noit_true) return EVENTER_WRITE | EVENTER_EXCEPTION;
422   if(err == noit_true) goto full_error;
423
424   while(1) {
425     int len;
426
427     in = ctx->req.last_input;
428     if(!in) {
429       in = ctx->req.first_input = ctx->req.last_input =
430         bchain_alloc(DEFAULT_BCHAINSIZE);
431       if(!in) goto full_error;
432     }
433     if(in->size > 0 && /* we've read something */
434        DEFAULT_BCHAINMINREAD > BCHAIN_SPACE(in) && /* we'd like read more */
435        DEFAULT_BCHAINMINREAD < DEFAULT_BCHAINSIZE) { /* and we can */
436       in->next = ctx->req.last_input =
437         bchain_alloc(DEFAULT_BCHAINSIZE);
438       in->next->prev = in;
439       in = in->next;
440       if(!in) goto full_error;
441     }
442
443     len = ctx->conn.e->opset->read(ctx->conn.e->fd,
444                                    in->buff + in->start + in->size,
445                                    in->allocd - in->size - in->start,
446                                    &mask, ctx->conn.e);
447     noitL(noit_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, len);
448     if(len == -1 && errno == EAGAIN) return mask;
449     if(len <= 0) goto full_error;
450     if(len > 0) in->size += len;
451     rv = noit_http_request_finalize(&ctx->req, &err);
452     if(len == -1 || err == noit_true) goto full_error;
453     if(ctx->req.state == NOIT_HTTP_REQ_EXPECT) {
454       const char *expect;
455       ctx->req.state = NOIT_HTTP_REQ_PAYLOAD;
456       assert(ctx->res.leader == NULL);
457       expect = "HTTP/1.1 100 Continue\r\n\r\n";
458       ctx->res.leader = bchain_from_data(expect, strlen(expect));
459       _http_perform_write(ctx, &mask);
460       if(ctx->res.leader != NULL) return mask;
461     }
462     if(rv == noit_true) return mask | EVENTER_WRITE | EVENTER_EXCEPTION;
463   }
464   return EVENTER_READ | EVENTER_EXCEPTION;
465 }
466 noit_boolean
467 noit_http_session_prime_input(noit_http_session_ctx *ctx,
468                               const void *data, size_t len) {
469   if(ctx->req.first_input != NULL) return noit_false;
470   if(len > DEFAULT_BCHAINSIZE) return noit_false;
471   ctx->req.first_input = ctx->req.last_input =
472       bchain_alloc(DEFAULT_BCHAINSIZE);
473   memcpy(ctx->req.first_input->buff, data, len);
474   ctx->req.first_input->size = len;
475   return noit_true;
476 }
477
478 void
479 noit_http_request_release(noit_http_session_ctx *ctx) {
480   noit_hash_destroy(&ctx->req.headers, NULL, NULL);
481   /* If we expected a payload, we expect a trailing \r\n */
482   if(ctx->req.has_payload) {
483     int drained, mask;
484     ctx->drainage = ctx->req.content_length - ctx->req.content_length_read;
485     /* best effort, we'll drain it before the next request anyway */
486     drained = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
487     ctx->drainage -= drained;
488   }
489   RELEASE_BCHAIN(ctx->req.current_request_chain);
490   memset(&ctx->req, 0, sizeof(ctx->req));
491 }
492 void
493 noit_http_response_release(noit_http_session_ctx *ctx) {
494   noit_hash_destroy(&ctx->res.headers, free, free);
495   if(ctx->res.status_reason) free(ctx->res.status_reason);
496   RELEASE_BCHAIN(ctx->res.leader);
497   RELEASE_BCHAIN(ctx->res.output);
498   RELEASE_BCHAIN(ctx->res.output_raw);
499   memset(&ctx->res, 0, sizeof(ctx->res));
500 }
501 void
502 noit_http_ctx_session_release(noit_http_session_ctx *ctx) {
503   if(noit_atomic_dec32(&ctx->ref_cnt) == 0) {
504     noit_http_request_release(ctx);
505     noit_http_response_release(ctx);
506     free(ctx);
507   }
508 }
509 int
510 noit_http_session_req_consume(noit_http_session_ctx *ctx,
511                               void *buf, size_t len, int *mask) {
512   size_t bytes_read = 0;
513   /* We attempt to consume from the first_input */
514   struct bchain *in, *tofree;
515   noitL(noit_debug, " ... noit_http_session_req_consume(%d) %d of %d\n",
516         ctx->conn.e->fd, (int)len,
517         (int)(ctx->req.content_length - ctx->req.content_length_read));
518   len = MIN(len, ctx->req.content_length - ctx->req.content_length_read);
519   while(bytes_read < len) {
520     int crlen = 0;
521     in = ctx->req.first_input;
522     while(in && bytes_read < len) {
523       int partial_len = MIN(in->size, len - bytes_read);
524       if(buf) memcpy(buf+bytes_read, in->buff+in->start, partial_len);
525       bytes_read += partial_len;
526       ctx->req.content_length_read += partial_len;
527       noitL(noit_debug, " ... filling %d bytes (read through %d/%d)\n",
528             (int)bytes_read, (int)ctx->req.content_length_read,
529             (int)ctx->req.content_length);
530       in->start += partial_len;
531       in->size -= partial_len;
532       if(in->size == 0) {
533         tofree = in;
534         ctx->req.first_input = in = in->next;
535         tofree->next = NULL;
536         RELEASE_BCHAIN(tofree);
537         if(in == NULL) {
538           ctx->req.last_input = NULL;
539           noitL(noit_debug, " ... noit_http_session_req_consume = %d\n",
540                 (int)bytes_read);
541           return bytes_read;
542         }
543       }
544     }
545     while(bytes_read + crlen < len) {
546       int rlen;
547       in = ctx->req.last_input;
548       if(!in)
549         in = ctx->req.first_input = ctx->req.last_input =
550             bchain_alloc(DEFAULT_BCHAINSIZE);
551       else if(in->start + in->size >= in->allocd) {
552         in->next = bchain_alloc(DEFAULT_BCHAINSIZE);
553         in = ctx->req.last_input = in->next;
554       }
555       /* pull next chunk */
556       rlen = ctx->conn.e->opset->read(ctx->conn.e->fd,
557                                       in->buff + in->start + in->size,
558                                       in->allocd - in->size - in->start,
559                                       mask, ctx->conn.e);
560       noitL(noit_debug, " noit_http -> read(%d) = %d\n", ctx->conn.e->fd, rlen);
561       if(rlen == -1 && errno == EAGAIN) {
562         /* We'd block to read more, but we have data,
563          * so do a short read */
564         if(ctx->req.first_input->size) break;
565         /* We've got nothing... */
566         noitL(noit_debug, " ... noit_http_session_req_consume = -1 (EAGAIN)\n");
567         return -1;
568       }
569       if(rlen <= 0) {
570         noitL(noit_debug, " ... noit_http_session_req_consume = -1 (error)\n");
571         return -1;
572       }
573       in->size += rlen;
574       crlen += rlen;
575     }
576   }
577   /* NOT REACHED */
578   return bytes_read;
579 }
580 int
581 noit_http_session_drive(eventer_t e, int origmask, void *closure,
582                         struct timeval *now) {
583   noit_http_session_ctx *ctx = closure;
584   int rv = 0;
585   int mask = origmask;
586
587   if(origmask & EVENTER_EXCEPTION)
588     goto abort_drive;
589
590   /* Drainage -- this is as nasty as it sounds
591    * The last request could have unread upload content, we would have
592    * noted that in noit_http_request_release.
593    */
594   noitL(noit_debug, " -> noit_http_session_drive(%d) [%x]\n", e->fd, origmask);
595   while(ctx->drainage > 0) {
596     int len;
597     noitL(noit_debug, "   ... draining last request(%d)\n", e->fd);
598     len = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
599     if(len == -1 && errno == EAGAIN) {
600       noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask);
601       return mask;
602     }
603     if(len <= 0) goto abort_drive;
604     ctx->drainage -= len;
605   }
606
607  next_req:
608   if(ctx->req.complete != noit_true) {
609     int maybe_write_mask;
610     noitL(noit_debug, "   -> noit_http_complete_request(%d)\n", e->fd);
611     mask = noit_http_complete_request(ctx, origmask);
612     noitL(noit_debug, "   <- noit_http_complete_request(%d) = %d\n",
613           e->fd, mask);
614     _http_perform_write(ctx, &maybe_write_mask);
615     if(ctx->conn.e == NULL) goto release;
616     if(ctx->req.complete != noit_true) {
617       noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd,
618             mask|maybe_write_mask);
619       return mask | maybe_write_mask;
620     }
621   }
622
623   /* only dispatch if the response is not complete */
624   if(ctx->res.complete == noit_false) {
625     noitL(noit_debug, "   -> dispatch(%d)\n", e->fd);
626     rv = ctx->dispatcher(ctx);
627     noitL(noit_debug, "   <- dispatch(%d) = %d\n", e->fd, rv);
628   }
629
630   _http_perform_write(ctx, &mask);
631   if(ctx->res.complete == noit_true &&
632      ctx->conn.e &&
633      ctx->conn.needs_close == noit_true) {
634    abort_drive:
635     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
636     ctx->conn.e = NULL;
637     goto release;
638     return 0;
639   }
640   if(ctx->res.complete == noit_true) {
641     noit_http_request_release(ctx);
642     noit_http_response_release(ctx);
643   }
644   if(ctx->req.complete == noit_false) goto next_req;
645   if(ctx->conn.e) {
646     noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, mask|rv);
647     return mask | rv;
648   }
649   noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
650   return 0;
651  release:
652   noit_http_ctx_session_release(ctx);
653   noitL(noit_debug, " <- noit_http_session_drive(%d) [%x]\n", e->fd, 0);
654   return 0;
655 }
656
657 noit_http_session_ctx *
658 noit_http_session_ctx_new(noit_http_dispatch_func f, void *c, eventer_t e) {
659   noit_http_session_ctx *ctx;
660   ctx = calloc(1, sizeof(*ctx));
661   ctx->ref_cnt = 1;
662   ctx->req.complete = noit_false;
663   ctx->conn.e = e;
664   ctx->dispatcher = f;
665   ctx->dispatcher_closure = c;
666   ctx->drive = noit_http_session_drive;
667   return ctx;
668 }
669
670 noit_boolean
671 noit_http_response_status_set(noit_http_session_ctx *ctx,
672                               int code, const char *reason) {
673   if(ctx->res.output_started == noit_true) return noit_false;
674   ctx->res.protocol = ctx->req.protocol;
675   if(code < 100 || code > 999) return noit_false;
676   ctx->res.status_code = code;
677   if(ctx->res.status_reason) free(ctx->res.status_reason);
678   ctx->res.status_reason = strdup(reason);
679   return noit_true;
680 }
681 noit_boolean
682 noit_http_response_header_set(noit_http_session_ctx *ctx,
683                               const char *name, const char *value) {
684   if(ctx->res.output_started == noit_true) return noit_false;
685   noit_hash_replace(&ctx->res.headers, strdup(name), strlen(name),
686                     strdup(value), free, free);
687   return noit_true;
688 }
689 noit_boolean
690 noit_http_response_option_set(noit_http_session_ctx *ctx, u_int32_t opt) {
691   if(ctx->res.output_started == noit_true) return noit_false;
692   /* transfer and content encodings only allowed in HTTP/1.1 */
693   if(ctx->res.protocol != NOIT_HTTP11 &&
694      (opt & NOIT_HTTP_CHUNKED))
695     return noit_false;
696   if(ctx->res.protocol != NOIT_HTTP11 &&
697      (opt & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)))
698     return noit_false;
699   if(((ctx->res.output_options | opt) &
700       (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) ==
701         (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE))
702     return noit_false;
703
704   /* Check out "accept" set */
705   if(!(opt & ctx->req.opts)) return noit_false;
706
707   ctx->res.output_options |= opt;
708   if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
709     CTX_ADD_HEADER("Transfer-Encoding", "chunked");
710   if(ctx->res.output_options & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) {
711     CTX_ADD_HEADER("Vary", "Accept-Encoding");
712     if(ctx->res.output_options & NOIT_HTTP_GZIP)
713       CTX_ADD_HEADER("Content-Encoding", "gzip");
714     else if(ctx->res.output_options & NOIT_HTTP_DEFLATE)
715       CTX_ADD_HEADER("Content-Encoding", "deflate");
716   }
717   if(ctx->res.output_options & NOIT_HTTP_CLOSE) {
718     CTX_ADD_HEADER("Connection", "close");
719     ctx->conn.needs_close = noit_true;
720   }
721   return noit_true;
722 }
723 noit_boolean
724 noit_http_response_append(noit_http_session_ctx *ctx,
725                           const void *b, size_t l) {
726   struct bchain *o;
727   int boff = 0;
728   if(ctx->res.closed == noit_true) return noit_false;
729   if(ctx->res.output_started == noit_true &&
730      !(ctx->res.output_options & (NOIT_HTTP_CLOSE | NOIT_HTTP_CHUNKED)))
731     return noit_false;
732   if(!ctx->res.output)
733     assert(ctx->res.output = bchain_alloc(DEFAULT_BCHAINSIZE));
734   o = ctx->res.output;
735   while(o->next) o = o->next;
736   while(l > 0) {
737     if(o->allocd == o->start + o->size) {
738       /* Filled up, need another */
739       o->next = bchain_alloc(DEFAULT_BCHAINSIZE);
740       o->next->prev = o->next;
741       o = o->next;
742     }
743     if(o->allocd > o->start + o->size) {
744       int tocopy = MIN(l, o->allocd - o->start - o->size);
745       memcpy(o->buff + o->start + o->size, (const char *)b + boff, tocopy);
746       o->size += tocopy;
747       boff += tocopy;
748       l -= tocopy;
749     }
750   }
751   return noit_true;
752 }
753 noit_boolean
754 noit_http_response_append_bchain(noit_http_session_ctx *ctx,
755                                  struct bchain *b) {
756   struct bchain *o;
757   if(ctx->res.closed == noit_true) return noit_false;
758   if(ctx->res.output_started == noit_true &&
759      !(ctx->res.output_options & (NOIT_HTTP_CHUNKED | NOIT_HTTP_CLOSE)))
760     return noit_false;
761   if(!ctx->res.output)
762     ctx->res.output = b;
763   else {
764     o = ctx->res.output;
765     while(o->next) o = o->next;
766     o->next = b;
767     b->prev = o;
768   }
769   return noit_true;
770 }
771 static int
772 _http_construct_leader(noit_http_session_ctx *ctx) {
773   int len = 0, tlen;
774   struct bchain *b;
775   const char *protocol_str;
776   const char *key, *value;
777   int klen;
778   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
779
780   assert(!ctx->res.leader);
781   ctx->res.leader = b = bchain_alloc(DEFAULT_BCHAINSIZE);
782
783   protocol_str = ctx->res.protocol == NOIT_HTTP11 ?
784                    "HTTP/1.1" :
785                    (ctx->res.protocol == NOIT_HTTP10 ?
786                      "HTTP/1.0" :
787                      "HTTP/0.9");
788   tlen = snprintf(b->buff, b->allocd, "%s %03d %s\r\n",
789                   protocol_str, ctx->res.status_code, ctx->res.status_reason);
790   if(tlen < 0) return -1;
791   len = b->size = tlen;
792
793 #define CTX_LEADER_APPEND(s, slen) do { \
794   if(b->size + slen > DEFAULT_BCHAINSIZE) { \
795     b->next = bchain_alloc(DEFAULT_BCHAINSIZE); \
796     assert(b->next); \
797     b->next->prev = b; \
798     b = b->next; \
799   } \
800   assert(DEFAULT_BCHAINSIZE >= b->size + slen); \
801   memcpy(b->buff + b->start + b->size, s, slen); \
802   b->size += slen; \
803 } while(0)
804   while(noit_hash_next_str(&ctx->res.headers, &iter,
805                            &key, &klen, &value)) {
806     int vlen = strlen(value);
807     CTX_LEADER_APPEND(key, klen);
808     CTX_LEADER_APPEND(": ", 2);
809     CTX_LEADER_APPEND(value, vlen);
810     CTX_LEADER_APPEND("\r\n", 2);
811   }
812   CTX_LEADER_APPEND("\r\n", 2);
813   return len;
814 }
815 static int memgzip2(Bytef *dest, uLongf *destLen,
816                     const Bytef *source, uLong sourceLen, int level) {
817   z_stream stream;
818   int err;
819
820   memset(&stream, 0, sizeof(stream));
821   stream.next_in = (Bytef*)source;
822   stream.avail_in = (uInt)sourceLen;
823   stream.next_out = dest;
824   stream.avail_out = (uInt)*destLen;
825   if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
826
827   err = deflateInit2(&stream, level, Z_DEFLATED, 15+16, 8,
828                      Z_DEFAULT_STRATEGY);
829   if (err != Z_OK) return err;
830
831   err = deflate(&stream, Z_FINISH);
832   if (err != Z_STREAM_END) {
833     deflateEnd(&stream);
834     return err == Z_OK ? Z_BUF_ERROR : err;
835   }
836   *destLen = stream.total_out;
837
838   err = deflateEnd(&stream);
839   return err;
840 }
841 static noit_boolean
842 _http_encode_chain(struct bchain *out, struct bchain *in, int opts) {
843   /* implement gzip and deflate! */
844   if(opts & NOIT_HTTP_GZIP) {
845     uLongf olen;
846     olen = out->allocd - out->start;
847     if(Z_OK != memgzip2((Bytef *)(out->buff + out->start), &olen,
848                         (Bytef *)(in->buff + in->start), (uLong)in->size,
849                         9)) {
850       noitL(noit_error, "zlib compress2 error\n");
851       return noit_false;
852     }
853     out->size += olen;
854   }
855   else if(opts & NOIT_HTTP_DEFLATE) {
856     uLongf olen;
857     olen = out->allocd - out->start;
858     if(Z_OK != compress2((Bytef *)(out->buff + out->start), &olen,
859                          (Bytef *)(in->buff + in->start), (uLong)in->size,
860                          9)) {
861       noitL(noit_error, "zlib compress2 error\n");
862       return noit_false;
863     }
864     out->size += olen;
865   }
866   else {
867     if(in->size > out->allocd - out->start) return noit_false;
868     memcpy(out->buff + out->start, in->buff + in->start, in->size);
869     out->size += in->size;
870   }
871   return noit_true;
872 }
873 struct bchain *
874 noit_http_process_output_bchain(noit_http_session_ctx *ctx,
875                                 struct bchain *in) {
876   struct bchain *out;
877   int ilen, maxlen = in->size, hexlen;
878   int opts = ctx->res.output_options;
879
880   /* a chunked header looks like: hex*\r\ndata\r\n */
881   /* let's assume that content never gets "larger" */
882   if(opts & NOIT_HTTP_GZIP) maxlen = deflateBound(NULL, in->size);
883   else if(opts & NOIT_HTTP_DEFLATE) maxlen = compressBound(in->size);
884
885   /* So, the link size is the len(data) + 4 + ceil(log(len(data))/log(16)) */
886   ilen = maxlen;
887   hexlen = 0;
888   while(ilen) { ilen >>= 4; hexlen++; }
889   if(hexlen == 0) hexlen = 1;
890
891   out = bchain_alloc(hexlen + 4 + maxlen);
892   /* if we're chunked, let's give outselved hexlen + 2 prefix space */
893   if(opts & NOIT_HTTP_CHUNKED) out->start = hexlen + 2;
894   if(_http_encode_chain(out, in, opts) == noit_false) {
895     free(out);
896     return NULL;
897   }
898   /* Too long! Out "larger" assumption is bad */
899   if(opts & NOIT_HTTP_CHUNKED) {
900     ilen = out->size;
901     assert(out->start+out->size+2 <= out->allocd);
902     out->buff[out->start + out->size++] = '\r';
903     out->buff[out->start + out->size++] = '\n';
904     out->start = 0;
905     /* terminate */
906     out->size += 2;
907     out->buff[hexlen] = '\r';
908     out->buff[hexlen+1] = '\n';
909     /* backfill */
910     out->size += hexlen;
911     while(hexlen > 0) {
912       out->buff[hexlen - 1] = _hexchars[ilen & 0xf];
913       ilen >>= 4;
914       hexlen--;
915     }
916     while(out->buff[out->start] == '0') {
917       out->start++;
918       out->size--;
919     }
920   }
921   return out;
922 }
923 noit_boolean
924 noit_http_response_flush(noit_http_session_ctx *ctx, noit_boolean final) {
925   struct bchain *o, *r;
926   int mask;
927
928   if(ctx->res.closed == noit_true) return noit_false;
929   if(ctx->res.output_started == noit_false) {
930     _http_construct_leader(ctx);
931     ctx->res.output_started = noit_true;
932   }
933   /* encode output to output_raw */
934   r = ctx->res.output_raw;
935   while(r && r->next) r = r->next;
936   /* r is the last raw output link */
937   o = ctx->res.output;
938   /* o is the first output link to process */
939   while(o) {
940     struct bchain *tofree, *n;
941     n = noit_http_process_output_bchain(ctx, o);
942     if(!n) {
943       /* Bad, response stops here! */
944       noitL(noit_error, "noit_http_process_output_bchain: NULL\n");
945       while(o) { tofree = o; o = o->next; free(tofree); }
946       final = noit_true;
947       break;
948     }
949     if(r) {
950       r->next = n;
951       n->prev = r;
952       r = n;
953     }
954     else {
955       r = ctx->res.output_raw = n;
956     }
957     tofree = o; o = o->next; free(tofree); /* advance and free */
958   }
959   ctx->res.output = NULL;
960   if(final) {
961     struct bchain *n;
962     ctx->res.closed = noit_true;
963     if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
964       n = bchain_from_data("0\r\n\r\n", 5);
965     else
966       n = bchain_from_data("\r\n", 2);
967     if(r) {
968       r->next = n;
969       n->prev = r;
970       r = n;
971     }
972     else {
973       r = ctx->res.output_raw = n;
974     }
975   }
976
977   _http_perform_write(ctx, &mask);
978   if(ctx->conn.e) {
979     eventer_update(ctx->conn.e, mask);
980   }
981   /* If the write fails completely, the event will be closed, freed and NULL */
982   return ctx->conn.e ? noit_true : noit_false;
983 }
984 noit_boolean
985 noit_http_response_end(noit_http_session_ctx *ctx) {
986   if(!noit_http_response_flush(ctx, noit_true)) return noit_false;
987   return noit_true;
988 }
989
990
991 /* Helper functions */
992
993 static int
994 noit_http_write_xml(void *vctx, const char *buffer, int len) {
995   if(noit_http_response_append((noit_http_session_ctx *)vctx, buffer, len))
996     return len;
997   return -1;
998 }
999 static int
1000 noit_http_close_xml(void *vctx) {
1001   noit_http_response_end((noit_http_session_ctx *)vctx);
1002   return 0;
1003 }
1004 void
1005 noit_http_response_xml(noit_http_session_ctx *ctx, xmlDocPtr doc) {
1006   xmlOutputBufferPtr out;
1007   xmlCharEncodingHandlerPtr enc;
1008   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
1009   out = xmlOutputBufferCreateIO(noit_http_write_xml,
1010                                 noit_http_close_xml,
1011                                 ctx, enc);
1012   xmlSaveFormatFileTo(out, doc, "utf8", 1);
1013 }
1014
Note: See TracBrowser for help on using the browser.