root/src/noit_http.c

Revision 003a683dd82a9ebcc4c43d5565e7e7c3d189384f, 31.2 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 9 years ago)

fix consuming the post and handle an event shutdown in the noit_http_complete_request

  • 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   if(req->current_offset <
265      req->current_input->start + req->current_input->size) {
266     /* There are left-overs */
267     req->first_input = bchain_alloc(MAX(DEFAULT_BCHAINSIZE,
268                                         req->current_input->size));
269     req->first_input->prev = NULL;
270     req->first_input->next = req->current_input->next;
271     req->first_input->start = 0;
272     req->first_input->size = req->current_input->size - (req->current_offset + req->current_input->start);
273     memcpy(req->first_input->buff,
274            req->current_input->buff +
275              req->current_input->start + req->current_offset,
276            req->first_input->size);
277     if(req->last_input == req->current_input)
278       req->last_input = req->first_input;
279   }
280   else {
281     req->first_input = req->last_input = NULL;
282   }
283   req->current_input = NULL;
284   req->current_offset = 0;
285
286   /* Now we need to dissect the current_request_chain into an HTTP request */
287   /* First step: make sure that no line crosses a chain boundary by
288    * inserting new chains as necessary.
289    */
290   if(!_fixup_bchain(req->current_request_chain)) {
291     *err = noit_true;
292     return noit_false;
293   }
294   /* Second step is to parse out the request itself */
295   for(b = req->current_request_chain; b; b = b->next) {
296     char *curr_str, *next_str;
297     b->buff[b->start + b->size - 2] = '\0';
298     curr_str = b->buff + b->start;
299     do {
300       next_str = strstr(curr_str, "\r\n");
301       if(next_str) {
302         *((char *)next_str) = '\0';
303         next_str += 2;
304       }
305       if(req->method_str && *curr_str == '\0')
306         break; /* our CRLFCRLF... end of req */
307 #define FAIL do { *err = noit_true; return noit_false; } while(0)
308       if(!req->method_str) { /* request line */
309         req->method_str = (char *)curr_str;
310         req->uri_str = strchr(curr_str, ' ');
311         if(!req->uri_str) FAIL;
312         *(req->uri_str) = '\0';
313         req->uri_str++;
314         req->protocol_str = strchr(req->uri_str, ' ');
315         if(!req->protocol_str) FAIL;
316         *(req->protocol_str) = '\0';
317         req->protocol_str++;
318         req->method = _method_enum(req->method_str);
319         req->protocol = _protocol_enum(req->protocol_str);
320         if(req->protocol == NOIT_HTTP11) req->opts |= NOIT_HTTP_CHUNKED;
321       }
322       else { /* request headers */
323         const char *name, *value;
324         if(_extract_header(curr_str, &name, &value) == noit_false) FAIL;
325         if(!name && !last_name) FAIL;
326         if(!strcmp(name ? name : last_name, "accept-encoding")) {
327           if(strstr(value, "gzip")) req->opts |= NOIT_HTTP_GZIP;
328           if(strstr(value, "deflate")) req->opts |= NOIT_HTTP_DEFLATE;
329         }
330         if(name)
331           noit_hash_replace(&req->headers, name, strlen(name), (void *)value,
332                             NULL, NULL);
333         else {
334           struct bchain *b;
335           const char *prefix = NULL;
336           int l1, l2;
337           noit_hash_retr_str(&req->headers, last_name, strlen(last_name),
338                              &prefix);
339           if(!prefix) FAIL;
340           l1 = strlen(prefix);
341           l2 = strlen(value);
342           b = bchain_alloc(l1 + l2 + 2);
343           b->next = req->current_request_chain;
344           b->next->prev = b;
345           req->current_request_chain = b;
346           b->size = l1 + l2 + 2;
347           memcpy(b->buff, prefix, l1);
348           b->buff[l1] = ' ';
349           memcpy(b->buff + l1 + 1, value, l2);
350           b->buff[l1 + 1 + l2] = '\0';
351           noit_hash_replace(&req->headers, last_name, strlen(last_name),
352                             b->buff, NULL, NULL);
353         }
354         if(name) last_name = name;
355       }
356       curr_str = next_str;
357     } while(next_str);
358   }
359
360   /* headers are done... we could need to read a payload */
361   if(noit_hash_retrieve(&req->headers,
362                         HEADER_CONTENT_LENGTH,
363                         sizeof(HEADER_CONTENT_LENGTH)-1, &vval)) {
364     const char *val = vval;
365     req->has_payload = noit_true;
366     req->content_length = strtoll(val, NULL, 10);
367   }
368   if(noit_hash_retrieve(&req->headers, HEADER_EXPECT,
369                         sizeof(HEADER_EXPECT)-1, &vval)) {
370     const char *val = vval;
371     if(strncmp(val, "100-", 4) || /* Bad expect header */
372        req->has_payload == noit_false) /* expect, but no content length */
373       FAIL;
374     /* We need to tell the client to "go-ahead" -- HTTP sucks */
375     req->state = NOIT_HTTP_REQ_EXPECT;
376     return noit_false;
377   }
378   if(req->content_length > 0) {
379     /* switch modes... let's go read the payload */
380     req->state = NOIT_HTTP_REQ_PAYLOAD;
381     return noit_false;
382   }
383
384   req->complete = noit_true;
385   return noit_true;
386 }
387 static noit_boolean
388 noit_http_request_finalize_payload(noit_http_request *req, noit_boolean *err) {
389   req->complete = noit_true;
390   return noit_true;
391 }
392 static noit_boolean
393 noit_http_request_finalize(noit_http_request *req, noit_boolean *err) {
394   if(req->state == NOIT_HTTP_REQ_HEADERS)
395     if(noit_http_request_finalize_headers(req, err)) return noit_true;
396   if(req->state == NOIT_HTTP_REQ_EXPECT) return noit_false;
397   if(req->state == NOIT_HTTP_REQ_PAYLOAD)
398     if(noit_http_request_finalize_payload(req, err)) return noit_true;
399   return noit_false;
400 }
401 int
402 noit_http_complete_request(noit_http_session_ctx *ctx, int mask) {
403   struct bchain *in;
404   noit_boolean rv, err = noit_false;
405
406   if(mask & EVENTER_EXCEPTION) {
407    full_error:
408     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
409     ctx->conn.e = NULL;
410     return 0;
411   }
412   if(ctx->req.complete == noit_true) return EVENTER_EXCEPTION;
413
414   /* We could have a complete request in the tail of a previous request */
415   rv = noit_http_request_finalize(&ctx->req, &err);
416   if(rv == noit_true) return EVENTER_WRITE | EVENTER_EXCEPTION;
417   if(err == noit_true) goto full_error;
418
419   in = ctx->req.last_input;
420   if(!in) {
421     in = ctx->req.first_input = ctx->req.last_input =
422       bchain_alloc(DEFAULT_BCHAINSIZE);
423     if(!in) goto full_error;
424   }
425   while(1) {
426     int len;
427
428     if(in->size > 0 && /* we've read something */
429        DEFAULT_BCHAINMINREAD > BCHAIN_SPACE(in) && /* we'd like read more */
430        DEFAULT_BCHAINMINREAD < DEFAULT_BCHAINSIZE) { /* and we can */
431       in->next = ctx->req.last_input =
432         bchain_alloc(DEFAULT_BCHAINSIZE);
433       in->next->prev = in;
434       in = in->next;
435       if(!in) goto full_error;
436     }
437
438     len = ctx->conn.e->opset->read(ctx->conn.e->fd,
439                                    in->buff + in->start + in->size,
440                                    in->allocd - in->size - in->start,
441                                    &mask, ctx->conn.e);
442     if(len == -1 && errno == EAGAIN) return mask;
443     if(len <= 0) goto full_error;
444     if(len > 0) in->size += len;
445     rv = noit_http_request_finalize(&ctx->req, &err);
446     if(len == -1 || err == noit_true) goto full_error;
447     if(ctx->req.state == NOIT_HTTP_REQ_EXPECT) {
448       const char *expect;
449       ctx->req.state = NOIT_HTTP_REQ_PAYLOAD;
450       assert(ctx->res.leader == NULL);
451       expect = "HTTP/1.1 100 Continue\r\n\r\n";
452       ctx->res.leader = bchain_from_data(expect, strlen(expect));
453       _http_perform_write(ctx, &mask);
454       if(ctx->res.leader != NULL) return mask;
455     }
456     if(rv == noit_true) return mask | EVENTER_WRITE | EVENTER_EXCEPTION;
457   }
458   return EVENTER_READ | EVENTER_EXCEPTION;
459 }
460 noit_boolean
461 noit_http_session_prime_input(noit_http_session_ctx *ctx,
462                               const void *data, size_t len) {
463   if(ctx->req.first_input != NULL) return noit_false;
464   if(len > DEFAULT_BCHAINSIZE) return noit_false;
465   ctx->req.first_input = ctx->req.last_input =
466       bchain_alloc(DEFAULT_BCHAINSIZE);
467   memcpy(ctx->req.first_input->buff, data, len);
468   ctx->req.first_input->size = len;
469   return noit_true;
470 }
471
472 void
473 noit_http_request_release(noit_http_session_ctx *ctx) {
474   noit_hash_destroy(&ctx->req.headers, NULL, NULL);
475   /* If we expected a payload, we expect a trailing \r\n */
476   if(ctx->req.has_payload) {
477     int drained, mask;
478     ctx->drainage = ctx->req.content_length - ctx->req.content_length_read;
479     /* best effort, we'll drain it before the next request anyway */
480     drained = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
481     ctx->drainage -= drained;
482   }
483   RELEASE_BCHAIN(ctx->req.current_request_chain);
484   memset(&ctx->req, 0, sizeof(ctx->req));
485 }
486 void
487 noit_http_response_release(noit_http_session_ctx *ctx) {
488   noit_hash_destroy(&ctx->res.headers, free, free);
489   if(ctx->res.status_reason) free(ctx->res.status_reason);
490   RELEASE_BCHAIN(ctx->res.leader);
491   RELEASE_BCHAIN(ctx->res.output);
492   RELEASE_BCHAIN(ctx->res.output_raw);
493   memset(&ctx->res, 0, sizeof(ctx->res));
494 }
495 void
496 noit_http_ctx_session_release(noit_http_session_ctx *ctx) {
497   if(noit_atomic_dec32(&ctx->ref_cnt) == 0) {
498     noit_http_request_release(ctx);
499     noit_http_response_release(ctx);
500     free(ctx);
501   }
502 }
503 int
504 noit_http_session_req_consume(noit_http_session_ctx *ctx,
505                               void *buf, size_t len, int *mask) {
506   size_t bytes_read = 0;
507   /* We attempt to consume from the first_input */
508   struct bchain *in, *tofree;
509   len = MIN(len, ctx->req.content_length - ctx->req.content_length_read);
510   while(bytes_read < len) {
511     int crlen = 0;
512     in = ctx->req.first_input;
513     while(in && bytes_read < len) {
514       int partial_len = MIN(in->size, len - bytes_read);
515       if(buf) memcpy(buf+bytes_read, in->buff+in->start, partial_len);
516       bytes_read += partial_len;
517       ctx->req.content_length_read += partial_len;
518       in->start += partial_len;
519       in->size -= partial_len;
520       if(in->size == 0) {
521         tofree = in;
522         ctx->req.first_input = in = in->next;
523         tofree->next = NULL;
524         RELEASE_BCHAIN(tofree);
525         if(in == NULL) {
526           ctx->req.last_input = NULL;
527           return bytes_read;
528         }
529       }
530     }
531     while(bytes_read + crlen < len) {
532       int rlen;
533       in = ctx->req.last_input;
534       if(!in)
535         in = ctx->req.first_input = ctx->req.last_input =
536             bchain_alloc(DEFAULT_BCHAINSIZE);
537       else if(in->start + in->size >= in->allocd) {
538         in->next = bchain_alloc(DEFAULT_BCHAINSIZE);
539         in = ctx->req.last_input = in->next;
540       }
541       /* pull next chunk */
542       rlen = ctx->conn.e->opset->read(ctx->conn.e->fd,
543                                       in->buff + in->start + in->size,
544                                       in->allocd - in->size - in->start,
545                                       mask, ctx->conn.e);
546       if(rlen == -1 && errno == EAGAIN) {
547          /* We'd block to read more, but we have data,
548           * so do a short read */
549          if(ctx->req.first_input->size) break;
550          /* We've got nothing... */
551          return -1;
552       }
553       if(rlen <= 0) return -1;
554       in->size += rlen;
555       crlen += rlen;
556     }
557   }
558   /* NOT REACHED */
559   return bytes_read;
560 }
561 int
562 noit_http_session_drive(eventer_t e, int origmask, void *closure,
563                         struct timeval *now) {
564   noit_http_session_ctx *ctx = closure;
565   int rv = 0;
566   int mask = origmask;
567
568   /* Drainage -- this is as nasty as it sounds
569    * The last request could have unread upload content, we would have
570    * noted that in noit_http_request_release.
571    */
572   while(ctx->drainage > 0) {
573     int len;
574     len = noit_http_session_req_consume(ctx, NULL, ctx->drainage, &mask);
575     if(len == -1 && errno == EAGAIN) return mask;
576     if(len <= 0) goto abort_drive;
577     ctx->drainage -= len;
578   }
579
580  next_req:
581   if(ctx->req.complete != noit_true) {
582     int maybe_write_mask;
583     mask = noit_http_complete_request(ctx, origmask);
584     _http_perform_write(ctx, &maybe_write_mask);
585     if(ctx->conn.e == NULL) goto release;
586     if(ctx->req.complete != noit_true) return mask | maybe_write_mask;
587   }
588
589   /* only dispatch if the response is not complete */
590   if(ctx->res.complete == noit_false) rv = ctx->dispatcher(ctx);
591
592   _http_perform_write(ctx, &mask);
593   if(ctx->res.complete == noit_true &&
594      ctx->conn.e &&
595      ctx->conn.needs_close == noit_true) {
596    abort_drive:
597     ctx->conn.e->opset->close(ctx->conn.e->fd, &mask, ctx->conn.e);
598     ctx->conn.e = NULL;
599     goto release;
600     return 0;
601   }
602   if(ctx->res.complete == noit_true) {
603     noit_http_request_release(ctx);
604     noit_http_response_release(ctx);
605   }
606   if(ctx->req.complete == noit_false) goto next_req;
607   if(ctx->conn.e) {
608     return mask | rv;
609   }
610   return 0;
611  release:
612   noit_http_ctx_session_release(ctx);
613   return 0;
614 }
615
616 noit_http_session_ctx *
617 noit_http_session_ctx_new(noit_http_dispatch_func f, void *c, eventer_t e) {
618   noit_http_session_ctx *ctx;
619   ctx = calloc(1, sizeof(*ctx));
620   ctx->ref_cnt = 1;
621   ctx->req.complete = noit_false;
622   ctx->conn.e = e;
623   ctx->dispatcher = f;
624   ctx->dispatcher_closure = c;
625   ctx->drive = noit_http_session_drive;
626   return ctx;
627 }
628
629 noit_boolean
630 noit_http_response_status_set(noit_http_session_ctx *ctx,
631                               int code, const char *reason) {
632   if(ctx->res.output_started == noit_true) return noit_false;
633   ctx->res.protocol = ctx->req.protocol;
634   if(code < 100 || code > 999) return noit_false;
635   ctx->res.status_code = code;
636   if(ctx->res.status_reason) free(ctx->res.status_reason);
637   ctx->res.status_reason = strdup(reason);
638   return noit_true;
639 }
640 noit_boolean
641 noit_http_response_header_set(noit_http_session_ctx *ctx,
642                               const char *name, const char *value) {
643   if(ctx->res.output_started == noit_true) return noit_false;
644   noit_hash_replace(&ctx->res.headers, strdup(name), strlen(name),
645                     strdup(value), free, free);
646   return noit_true;
647 }
648 noit_boolean
649 noit_http_response_option_set(noit_http_session_ctx *ctx, u_int32_t opt) {
650   if(ctx->res.output_started == noit_true) return noit_false;
651   /* transfer and content encodings only allowed in HTTP/1.1 */
652   if(ctx->res.protocol != NOIT_HTTP11 &&
653      (opt & NOIT_HTTP_CHUNKED))
654     return noit_false;
655   if(ctx->res.protocol != NOIT_HTTP11 &&
656      (opt & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)))
657     return noit_false;
658   if(((ctx->res.output_options | opt) &
659       (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) ==
660         (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE))
661     return noit_false;
662
663   /* Check out "accept" set */
664   if(!(opt & ctx->req.opts)) return noit_false;
665
666   ctx->res.output_options |= opt;
667   if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
668     CTX_ADD_HEADER("Transfer-Encoding", "chunked");
669   if(ctx->res.output_options & (NOIT_HTTP_GZIP | NOIT_HTTP_DEFLATE)) {
670     CTX_ADD_HEADER("Vary", "Accept-Encoding");
671     if(ctx->res.output_options & NOIT_HTTP_GZIP)
672       CTX_ADD_HEADER("Content-Encoding", "gzip");
673     else if(ctx->res.output_options & NOIT_HTTP_DEFLATE)
674       CTX_ADD_HEADER("Content-Encoding", "deflate");
675   }
676   if(ctx->res.output_options & NOIT_HTTP_CLOSE) {
677     CTX_ADD_HEADER("Connection", "close");
678     ctx->conn.needs_close = noit_true;
679   }
680   return noit_true;
681 }
682 noit_boolean
683 noit_http_response_append(noit_http_session_ctx *ctx,
684                           const void *b, size_t l) {
685   struct bchain *o;
686   int boff = 0;
687   if(ctx->res.closed == noit_true) return noit_false;
688   if(ctx->res.output_started == noit_true &&
689      !(ctx->res.output_options & (NOIT_HTTP_CLOSE | NOIT_HTTP_CHUNKED)))
690     return noit_false;
691   if(!ctx->res.output)
692     assert(ctx->res.output = bchain_alloc(DEFAULT_BCHAINSIZE));
693   o = ctx->res.output;
694   while(o->next) o = o->next;
695   while(l > 0) {
696     if(o->allocd == o->start + o->size) {
697       /* Filled up, need another */
698       o->next = bchain_alloc(DEFAULT_BCHAINSIZE);
699       o->next->prev = o->next;
700       o = o->next;
701     }
702     if(o->allocd > o->start + o->size) {
703       int tocopy = MIN(l, o->allocd - o->start - o->size);
704       memcpy(o->buff + o->start + o->size, (const char *)b + boff, tocopy);
705       o->size += tocopy;
706       boff += tocopy;
707       l -= tocopy;
708     }
709   }
710   return noit_true;
711 }
712 noit_boolean
713 noit_http_response_append_bchain(noit_http_session_ctx *ctx,
714                                  struct bchain *b) {
715   struct bchain *o;
716   if(ctx->res.closed == noit_true) return noit_false;
717   if(ctx->res.output_started == noit_true &&
718      !(ctx->res.output_options & (NOIT_HTTP_CHUNKED | NOIT_HTTP_CLOSE)))
719     return noit_false;
720   if(!ctx->res.output)
721     ctx->res.output = b;
722   else {
723     o = ctx->res.output;
724     while(o->next) o = o->next;
725     o->next = b;
726     b->prev = o;
727   }
728   return noit_true;
729 }
730 static int
731 _http_construct_leader(noit_http_session_ctx *ctx) {
732   int len = 0, tlen;
733   struct bchain *b;
734   const char *protocol_str;
735   const char *key, *value;
736   int klen;
737   noit_hash_iter iter = NOIT_HASH_ITER_ZERO;
738
739   assert(!ctx->res.leader);
740   ctx->res.leader = b = bchain_alloc(DEFAULT_BCHAINSIZE);
741
742   protocol_str = ctx->res.protocol == NOIT_HTTP11 ?
743                    "HTTP/1.1" :
744                    (ctx->res.protocol == NOIT_HTTP10 ?
745                      "HTTP/1.0" :
746                      "HTTP/0.9");
747   tlen = snprintf(b->buff, b->allocd, "%s %03d %s\r\n",
748                   protocol_str, ctx->res.status_code, ctx->res.status_reason);
749   if(tlen < 0) return -1;
750   len = b->size = tlen;
751
752 #define CTX_LEADER_APPEND(s, slen) do { \
753   if(b->size + slen > DEFAULT_BCHAINSIZE) { \
754     b->next = bchain_alloc(DEFAULT_BCHAINSIZE); \
755     assert(b->next); \
756     b->next->prev = b; \
757     b = b->next; \
758   } \
759   assert(DEFAULT_BCHAINSIZE >= b->size + slen); \
760   memcpy(b->buff + b->start + b->size, s, slen); \
761   b->size += slen; \
762 } while(0)
763   while(noit_hash_next_str(&ctx->res.headers, &iter,
764                            &key, &klen, &value)) {
765     int vlen = strlen(value);
766     CTX_LEADER_APPEND(key, klen);
767     CTX_LEADER_APPEND(": ", 2);
768     CTX_LEADER_APPEND(value, vlen);
769     CTX_LEADER_APPEND("\r\n", 2);
770   }
771   CTX_LEADER_APPEND("\r\n", 2);
772   return len;
773 }
774 /* memgzip */
775 static int memgzip2(Bytef *dest, uLongf *destLen,
776                     const Bytef *source, uLong sourceLen, int level) {
777     z_stream stream;
778     int err;
779
780     memset(&stream, 0, sizeof(stream));
781     stream.next_in = (Bytef*)source;
782     stream.avail_in = (uInt)sourceLen;
783     stream.next_out = dest;
784     stream.avail_out = (uInt)*destLen;
785     if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
786
787     err = deflateInit2(&stream, level, Z_DEFLATED, 15+16, 8,
788                        Z_DEFAULT_STRATEGY);
789     if (err != Z_OK) return err;
790
791     err = deflate(&stream, Z_FINISH);
792     if (err != Z_STREAM_END) {
793         deflateEnd(&stream);
794         return err == Z_OK ? Z_BUF_ERROR : err;
795     }
796     *destLen = stream.total_out;
797
798     err = deflateEnd(&stream);
799     return err;
800 }
801 static noit_boolean
802 _http_encode_chain(struct bchain *out, struct bchain *in, int opts) {
803   /* implement gzip and deflate! */
804   if(opts & NOIT_HTTP_GZIP) {
805     uLongf olen;
806     olen = out->allocd - out->start;
807     if(Z_OK != memgzip2((Bytef *)(out->buff + out->start), &olen,
808                         (Bytef *)(in->buff + in->start), (uLong)in->size,
809                         9)) {
810       noitL(noit_error, "zlib compress2 error\n");
811       return noit_false;
812     }
813     out->size += olen;
814   }
815   else if(opts & NOIT_HTTP_DEFLATE) {
816     uLongf olen;
817     olen = out->allocd - out->start;
818     if(Z_OK != compress2((Bytef *)(out->buff + out->start), &olen,
819                          (Bytef *)(in->buff + in->start), (uLong)in->size,
820                          9)) {
821       noitL(noit_error, "zlib compress2 error\n");
822       return noit_false;
823     }
824     out->size += olen;
825   }
826   else {
827     if(in->size > out->allocd - out->start) return noit_false;
828     memcpy(out->buff + out->start, in->buff + in->start, in->size);
829     out->size += in->size;
830   }
831   return noit_true;
832 }
833 struct bchain *
834 noit_http_process_output_bchain(noit_http_session_ctx *ctx,
835                                 struct bchain *in) {
836   struct bchain *out;
837   int ilen, maxlen = in->size, hexlen;
838   int opts = ctx->res.output_options;
839
840   /* a chunked header looks like: hex*\r\ndata\r\n */
841   /* let's assume that content never gets "larger" */
842   if(opts & NOIT_HTTP_GZIP) maxlen = deflateBound(NULL, in->size);
843   else if(opts & NOIT_HTTP_DEFLATE) maxlen = compressBound(in->size);
844
845   /* So, the link size is the len(data) + 4 + ceil(log(len(data))/log(16)) */
846   ilen = maxlen;
847   hexlen = 0;
848   while(ilen) { ilen >>= 4; hexlen++; }
849   if(hexlen == 0) hexlen = 1;
850
851   out = bchain_alloc(hexlen + 4 + maxlen);
852   /* if we're chunked, let's give outselved hexlen + 2 prefix space */
853   if(opts & NOIT_HTTP_CHUNKED) out->start = hexlen + 2;
854   if(_http_encode_chain(out, in, opts) == noit_false) {
855     free(out);
856     return NULL;
857   }
858   /* Too long! Out "larger" assumption is bad */
859   if(opts & NOIT_HTTP_CHUNKED) {
860     ilen = out->size;
861     assert(out->start+out->size+2 <= out->allocd);
862     out->buff[out->start + out->size++] = '\r';
863     out->buff[out->start + out->size++] = '\n';
864     out->start = 0;
865     /* terminate */
866     out->size += 2;
867     out->buff[hexlen] = '\r';
868     out->buff[hexlen+1] = '\n';
869     /* backfill */
870     out->size += hexlen;
871     while(hexlen > 0) {
872       out->buff[hexlen - 1] = _hexchars[ilen & 0xf];
873       ilen >>= 4;
874       hexlen--;
875     }
876     while(out->buff[out->start] == '0') {
877       out->start++;
878       out->size--;
879     }
880   }
881   return out;
882 }
883 noit_boolean
884 noit_http_response_flush(noit_http_session_ctx *ctx, noit_boolean final) {
885   struct bchain *o, *r;
886   int mask;
887
888   if(ctx->res.closed == noit_true) return noit_false;
889   if(ctx->res.output_started == noit_false) {
890     _http_construct_leader(ctx);
891     ctx->res.output_started = noit_true;
892   }
893   /* encode output to output_raw */
894   r = ctx->res.output_raw;
895   while(r && r->next) r = r->next;
896   /* r is the last raw output link */
897   o = ctx->res.output;
898   /* o is the first output link to process */
899   while(o) {
900     struct bchain *tofree, *n;
901     n = noit_http_process_output_bchain(ctx, o);
902     if(!n) {
903       /* Bad, response stops here! */
904       noitL(noit_error, "noit_http_process_output_bchain: NULL\n");
905       while(o) { tofree = o; o = o->next; free(tofree); }
906       final = noit_true;
907       break;
908     }
909     if(r) {
910       r->next = n;
911       n->prev = r;
912       r = n;
913     }
914     else {
915       r = ctx->res.output_raw = n;
916     }
917     tofree = o; o = o->next; free(tofree); /* advance and free */
918   }
919   ctx->res.output = NULL;
920   if(final) {
921     struct bchain *n;
922     ctx->res.closed = noit_true;
923     if(ctx->res.output_options & NOIT_HTTP_CHUNKED)
924       n = bchain_from_data("0\r\n\r\n", 5);
925     else
926       n = bchain_from_data("\r\n", 2);
927     if(r) {
928       r->next = n;
929       n->prev = r;
930       r = n;
931     }
932     else {
933       r = ctx->res.output_raw = n;
934     }
935   }
936
937   _http_perform_write(ctx, &mask);
938   if(ctx->conn.e) {
939     eventer_update(ctx->conn.e, mask);
940   }
941   /* If the write fails completely, the event will be closed, freed and NULL */
942   return ctx->conn.e ? noit_true : noit_false;
943 }
944 noit_boolean
945 noit_http_response_end(noit_http_session_ctx *ctx) {
946   if(!noit_http_response_flush(ctx, noit_true)) return noit_false;
947   return noit_true;
948 }
949
950
951 /* Helper functions */
952
953 static int
954 noit_http_write_xml(void *vctx, const char *buffer, int len) {
955   if(noit_http_response_append((noit_http_session_ctx *)vctx, buffer, len))
956     return len;
957   return -1;
958 }
959 static int
960 noit_http_close_xml(void *vctx) {
961   noit_http_response_end((noit_http_session_ctx *)vctx);
962   return 0;
963 }
964 void
965 noit_http_response_xml(noit_http_session_ctx *ctx, xmlDocPtr doc) {
966   xmlOutputBufferPtr out;
967   xmlCharEncodingHandlerPtr enc;
968   enc = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF8);
969   out = xmlOutputBufferCreateIO(noit_http_write_xml,
970                                 noit_http_close_xml,
971                                 ctx, enc);
972   xmlSaveFormatFileTo(out, doc, "utf8", 1);
973 }
974
Note: See TracBrowser for help on using the browser.