diff options
author | Yorhel <git@yorhel.nl> | 2015-12-20 11:26:49 +0100 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2015-12-20 11:29:32 +0100 |
commit | 36f4c73123e70e437628f424a1c47877278ab335 (patch) | |
tree | 5779967ef417d379551b8fa5dc079fc8e2c6a946 | |
parent | a82d9fee21b78045b2c8143ea0bb1c64954e71a3 (diff) |
Request management + param passing + some fastcgi reader fixes
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/fastcgi.c | 37 | ||||
-rw-r--r-- | src/fastcgi.h | 36 | ||||
-rw-r--r-- | src/fcgy.h | 2 | ||||
-rw-r--r-- | src/fconn_fastcgi.c | 97 | ||||
-rw-r--r-- | src/fconn_fastcgi.h | 9 | ||||
-rw-r--r-- | src/req.c | 52 | ||||
-rw-r--r-- | src/req.h | 57 | ||||
-rw-r--r-- | test/fastcgi_param_parse.c | 2 |
9 files changed, 268 insertions, 26 deletions
diff --git a/Makefile.am b/Makefile.am index 47ce969..c2a038e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,6 +42,7 @@ fcgy_SOURCES=\ src/fastcgi.c\ src/fconn_fastcgi.c\ src/main.c\ + src/req.c\ src/util.c nodist_fcgy_SOURCES=version.c @@ -52,6 +53,7 @@ noinst_HEADERS=\ src/fcgy.h\ src/fconn_fastcgi.h\ src/main.h\ + src/req.h\ src/util.h MOSTLYCLEANFILES+=version.c diff --git a/src/fastcgi.c b/src/fastcgi.c index 44247ef..1fb352b 100644 --- a/src/fastcgi.c +++ b/src/fastcgi.c @@ -23,17 +23,17 @@ #include "fcgy.h" -/* Returns -1 if no complete packet has been received, otherwise writes to *h - * and returns the length of the first packet in the buffer. */ +/* Returns -1 if no complete record has been received, otherwise writes to *h + * and returns the length of the first record in the buffer. */ static ssize_t parse_header(fastcgi_header *h, char *buf, size_t buflen) { - if(buflen < sizeof(fastcgi_header)) + if(buflen < FCGI_HEADER_LEN) return -1; - memcpy(h, buf, sizeof(fastcgi_header)); + memcpy(h, buf, FCGI_HEADER_LEN); /* TODO: A proper byteswap in the struct itself seems more efficient and clear */ h->requestId = (((uint8_t)buf[2])<<8) + ((uint8_t)buf[3]); h->contentLength = (((uint8_t)buf[4])<<8) + ((uint8_t)buf[5]); - size_t len = sizeof(fastcgi_header) + h->contentLength + h->paddingLength; + size_t len = FCGI_HEADER_LEN + h->contentLength + h->paddingLength; return buflen < len ? -1 : (ssize_t)len; } @@ -41,22 +41,30 @@ static ssize_t parse_header(fastcgi_header *h, char *buf, size_t buflen) { static void reader_consume(fastcgi_reader *r) { size_t len = r->buf_len; char *buf = r->buf; - while(true) { + + r->processing = true; + + while(r->cb) { fastcgi_header h; ssize_t n = parse_header(&h, buf, len); if(n < 0) break; - r->cb(r, n, h, buf); + r->cb(r, n, h, buf+FCGI_HEADER_LEN); len -= n; buf += n; } memmove(r->buf, buf, len); r->buf_len = len; + + r->processing = false; + if(!r->cb) + fastcgi_reader_destroy(r); } static void reader_io_cb(EV_P_ ev_io *w, int revents) { fastcgi_reader *r = w->data; + assert(r->cb != NULL); ssize_t n = read(r->fd, r->buf+r->buf_len, sizeof(r->buf)-r->buf_len); if(n < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)) @@ -75,13 +83,24 @@ static void reader_io_cb(EV_P_ ev_io *w, int revents) { } -void fastcgi_reader_init(fastcgi_reader *r, int fd, fastcgi_reader_cb cb, void *data) { +fastcgi_reader *fastcgi_reader_create(int fd, fastcgi_reader_cb cb, void *data) { + fastcgi_reader *r = malloc(sizeof(fastcgi_reader)); r->fd = fd; r->buf_len = 0; r->data = data; r->cb = cb; ev_io_init(r->io, reader_io_cb, r->fd, EV_READ); r->io->data = r; + return r; +} + + +void fastcgi_reader_destroy(fastcgi_reader *r) { + r->cb = NULL; + if(r->processing) + return; + ev_io_stop(EV_DEFAULT_UC_ r->io); + free(r); } @@ -134,6 +153,7 @@ ssize_t fastcgi_param_parse(fastcgi_param_parser *p, const char *buf, size_t len case 2: /* Name data */ if(c == 0 || c == '=') { free(p->buf); + p->buf = NULL; return -1; } p->buf[p->off++] = c; @@ -152,6 +172,7 @@ ssize_t fastcgi_param_parse(fastcgi_param_parser *p, const char *buf, size_t len case 3: /* Value data */ if(c == 0) { free(p->buf); + p->buf = NULL; return -1; } p->buf[p->off++] = c; diff --git a/src/fastcgi.h b/src/fastcgi.h index b3c1c3e..c9028cc 100644 --- a/src/fastcgi.h +++ b/src/fastcgi.h @@ -23,6 +23,22 @@ #ifndef FCGY_FASTCGI_H #define FCGY_FASTCGI_H + +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 +#define FCGI_UNKNOWN_TYPE 11 +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +#define FCGI_HEADER_LEN 8 + typedef struct { uint8_t version; uint8_t type; @@ -34,29 +50,33 @@ typedef struct { typedef struct fastcgi_reader fastcgi_reader; -/* Called when a complete FastCGI packet has arrived. Arguments: - * ssize_t -> -1 on error (see errno), otherwise total length of the packet. - * char * -> packet buffer, including header. +/* Called when a complete FastCGI record has arrived. Arguments: + * ssize_t -> -1 on error (see errno), otherwise total length of the record. + * (XXX: Is the total length useful?) + * char * -> buffer containing the record contents (after the header) */ typedef void(*fastcgi_reader_cb)(fastcgi_reader *, ssize_t, fastcgi_header, const char *); struct fastcgi_reader { int fd; + bool processing; /* Currently processing the buffer */ + ev_io io[1]; + fastcgi_reader_cb cb; /* Set to NULL when _destroy()'ed while processing */ + void *data; size_t buf_len; - /* Read buffer. Large enough to hold a single FastCGI packet. + /* Read buffer. Large enough to hold a single FastCGI record. * (should this be a dynamic buffer? Might save some memory and/or optimize read() calls) */ char buf[8+65535+255]; - ev_io io[1]; - fastcgi_reader_cb cb; - void *data; }; -void fastcgi_reader_init(fastcgi_reader *, int, fastcgi_reader_cb, void *); +fastcgi_reader *fastcgi_reader_create(int, fastcgi_reader_cb, void *); +void fastcgi_reader_destroy(fastcgi_reader *); static inline void fastcgi_reader_start(fastcgi_reader *r) { ev_io_start(EV_DEFAULT_UC_ r->io); } static inline void fastcgi_reader_stop (fastcgi_reader *r) { ev_io_stop (EV_DEFAULT_UC_ r->io); } + typedef struct { uint32_t state; uint32_t namelen; @@ -58,6 +58,7 @@ extern char *fcgy_version; typedef enum fcgy_front_type fcgy_front_type; typedef struct fcgy_app fcgy_app; typedef struct fcgy_front fcgy_front; +typedef struct fcgy_req fcgy_req; typedef struct fconn_fastcgi fconn_fastcgi; #include "util.h" @@ -65,5 +66,6 @@ typedef struct fconn_fastcgi fconn_fastcgi; #include "main.h" #include "app.h" #include "fconn_fastcgi.h" +#include "req.h" #endif diff --git a/src/fconn_fastcgi.c b/src/fconn_fastcgi.c index bba353e..1d21603 100644 --- a/src/fconn_fastcgi.c +++ b/src/fconn_fastcgi.c @@ -22,36 +22,117 @@ #include "fcgy.h" +/* Comparison for use with the c->reqs vector */ +#define req_cmp(c, val) (((int)(c)->reqs.a[i]->fconn_id) - ((int)val)) -void read_cb(fastcgi_reader *r, ssize_t len, fastcgi_header h, const char *buf) { + +static void handle_begin_request(fconn_fastcgi *c, uint16_t id, size_t len, const char *buf) { + if(id == 0 || len < 8 || *buf != 0 || buf[1] < 1 || buf[1] > 3) { + fprintf(stderr, "Invalid BEGIN_REQUEST record\n"); + fconn_fastcgi_destroy(c); + return; + } + + size_t n; + vec_search_insert(c->reqs, n, req_cmp(c, id)); + if(n < c->reqs.n && c->reqs.a[n]->fconn_id == id) { + fprintf(stderr, "BEGIN_REQUEST record for existing request ID\n"); + fconn_fastcgi_destroy(c); + return; + } + *vec_insert_orderp(c->reqs, n) = req_create(c, id); +} + + +static void handle_params(fconn_fastcgi *c, fcgy_req *req, size_t len, const char *buf) { + if(!len) /* End of param list. Should signal this to fcgy_req? */ + return; + + ssize_t n; + while(len > 0) { + n = fastcgi_param_parse(c->pp, buf, len); + if(n < 0) { + fprintf(stderr, "Invalid parameter\n"); + fconn_fastcgi_destroy(c); + return; + } + if(n == 0) + break; + + req_set_param(req, c->pp->namelen, c->pp->valuelen, c->pp->buf); + c->pp->buf = NULL; /* To avoid a double-free in fconn_fastcgi_destroy() */ + len -= n; + buf += n; + } +} + + +static fcgy_req *get_request(fconn_fastcgi *c, uint16_t id) { + vec_search(c->reqs, req_cmp(c, id), return c->reqs.a[i]); + return NULL; +} + + +static void read_cb(fastcgi_reader *r, ssize_t len, fastcgi_header h, const char *buf) { fconn_fastcgi *c = r->data; if(len < 0) { - fconn_fastcgi_destroy(c); fprintf(stderr, "Read error: %s\n", strerror(errno)); + fconn_fastcgi_destroy(c); return; } - fprintf(stderr, "Got FastCGI packet: len = %4u, version = %u, type = %2u, requestId = %2u, contentLength = %4u\n", + fprintf(stderr, "Got FastCGI record: len = %4u, version = %u, type = %2u, requestId = %2u, contentLength = %4u\n", (unsigned)len, (unsigned)h.version, (unsigned)h.type, (unsigned)h.requestId, (unsigned)h.contentLength); + + /* TODO: Handle management records here */ + + if(h.type == FCGI_BEGIN_REQUEST) { + handle_begin_request(c, h.requestId, h.contentLength, buf); + return; + } + + fcgy_req *req = get_request(c, h.requestId); + if(!req) { + fprintf(stderr, "Received record for unknown request id (%u)\n", (unsigned)h.requestId); + fconn_fastcgi_destroy(c); + return; + } + + if(h.type == FCGI_PARAMS) + handle_params(c, req, h.contentLength, buf); + + /* TODO: Handle other request records here */ } void fconn_fastcgi_create(fcgy_app *app, int rfd, int wfd) { - fconn_fastcgi *c = malloc(sizeof(fconn_fastcgi)); + fconn_fastcgi *c = calloc(1, sizeof(fconn_fastcgi)); c->app = app; + c->rfd = rfd; + c->wfd = wfd; - fastcgi_reader_init(c->rd, rfd, read_cb, c); + c->rd = fastcgi_reader_create(c->rfd, read_cb, c); fastcgi_reader_start(c->rd); - c->wfd = wfd; - hlist_prepend(app->fconns, c); } void fconn_fastcgi_destroy(fconn_fastcgi *c) { - fastcgi_reader_stop(c->rd); + fastcgi_reader_destroy(c->rd); hlist_remove(c->app->fconns, c); + + free(c->pp->buf); + + size_t i; + for(i=0; i<c->reqs.n; i++) + req_unset_front(c->reqs.a[i]); + free(c->reqs.a); + + close(c->wfd); + if(c->wfd != c->rfd) + close(c->rfd); + free(c); } diff --git a/src/fconn_fastcgi.h b/src/fconn_fastcgi.h index 8a313af..ea049ae 100644 --- a/src/fconn_fastcgi.h +++ b/src/fconn_fastcgi.h @@ -26,9 +26,14 @@ struct fconn_fastcgi { fconn_fastcgi *next, *prev; fcgy_app *app; - int wfd; + int wfd, + rfd; - fastcgi_reader rd[1]; + fastcgi_reader *rd; + fastcgi_param_parser pp[1]; + + /* List of active requests for this connection. Ordered by fconn_id. */ + vec_t(fcgy_req *) reqs; }; diff --git a/src/req.c b/src/req.c new file mode 100644 index 0000000..12a0267 --- /dev/null +++ b/src/req.c @@ -0,0 +1,52 @@ +/* Copyright (c) 2015-2016 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "fcgy.h" + +fcgy_req *req_create(fconn_fastcgi *fconn, uint16_t id) { + fcgy_req *r = calloc(1, sizeof(fcgy_req)); + r->front = fconn; + r->fconn_id = id; + return r; +} + +void req_unset_front(fcgy_req *r) { + r->front = NULL; + + /* No backends implemented yet. The free() needs to be deferred until both + * front and back are NULL */ + assert(!r->back); + + size_t i; + for(i=0; i<r->params.n; i++) + free(r->params.a[i].buf); + free(r->params.a); + free(r); +} + +void req_set_param(fcgy_req *r, uint32_t namelen, uint32_t valuelen, char *buf) { + fcgy_req_param *p = vec_appendp(r->params); + p->namelen = namelen; + p->valuelen = valuelen; + p->buf = buf; + fprintf(stderr, "Parameter: %s=%s\n", buf, buf+namelen+1); +} diff --git a/src/req.h b/src/req.h new file mode 100644 index 0000000..34cc127 --- /dev/null +++ b/src/req.h @@ -0,0 +1,57 @@ +/* Copyright (c) 2015-2016 Yoran Heling + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef FCGY_REQ_H +#define FCGY_REQ_H + + +typedef struct { + uint32_t namelen, + valuelen; + char *buf; +} fcgy_req_param; + + +/* The front and back pointers also serve as a ref count. If both are NULL, the + * request object is not referenced anywhere and may be freed. */ +struct fcgy_req { + fconn_fastcgi *front; + uint16_t fconn_id; + + void *back; + + /* Order is unspecified for now */ + vec_t(fcgy_req_param) params; +}; + + +fcgy_req *req_create(fconn_fastcgi *, uint16_t); + +/* Lost connection with frontend */ +void req_unset_front(fcgy_req *); + +/* Set a parameter. Buffer follows the same format as given by + * fastcgi_param_parse(). Ownership of the buffer is passed to the fcgy_req + * object and will be free()'d. */ +void req_set_param(fcgy_req *, uint32_t, uint32_t, char *); + +#endif diff --git a/test/fastcgi_param_parse.c b/test/fastcgi_param_parse.c index 1f702c2..a121d25 100644 --- a/test/fastcgi_param_parse.c +++ b/test/fastcgi_param_parse.c @@ -49,6 +49,7 @@ #define F(in) do {\ fastcgi_param_parser p = {0};\ assert(fastcgi_param_parse(&p, in, sizeof(in)-1) == -1);\ + assert(p.buf == NULL);\ p.state = 0;\ p.off = 0;\ size_t off = 0;\ @@ -57,6 +58,7 @@ if((n = fastcgi_param_parse(&p, in+off, 1)) != 0)\ break;\ assert(n == -1);\ + assert(p.buf == NULL);\ } while(0) #define x127 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |