From 6773a59721365a793bfbf8da91c512a449bdaf4f Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Sun, 25 Nov 2007 21:32:26 +0000 Subject: [PATCH] r14953@tombo: nickm | 2007-11-25 15:56:40 -0500 Replace evbuffer_readline with a more powerful evbuffer_readln that can handle more EOL styles, and that can give useful results when there are NUL characters inside the returned values. Includes regression tests. svn:r550 --- buffer.c | 96 +++++++++++++++++++++++++++++++----------- event.h | 28 +++++++++++-- http.c | 4 +- test/regress.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 30 deletions(-) diff --git a/buffer.c b/buffer.c index f1b62e21..6dacca8f 100644 --- a/buffer.c +++ b/buffer.c @@ -211,45 +211,91 @@ evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen) * Reads a line terminated by either '\r\n', '\n\r' or '\r' or '\n'. * The returned buffer needs to be freed by the called. */ - char * evbuffer_readline(struct evbuffer *buffer) +{ + return evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY); +} + +char * +evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out, + enum evbuffer_eol_style eol_style) { u_char *data = EVBUFFER_DATA(buffer); + u_char *start_of_eol, *end_of_eol; size_t len = EVBUFFER_LENGTH(buffer); char *line; - unsigned int i; - - for (i = 0; i < len; i++) { - if (data[i] == '\r' || data[i] == '\n') - break; + unsigned int i, n_to_copy, n_to_drain; + + /* depending on eol_style, set start_of_eol to the first character + * in the newline, and end_of_eol to one after the last character. */ + switch (eol_style) { + case EVBUFFER_EOL_ANY: + for (i = 0; i < len; i++) { + if (data[i] == '\r' || data[i] == '\n') + break; + } + if (i == len) + return (NULL); + start_of_eol = data+i; + ++i; + for ( ; i < len; i++) { + if (data[i] != '\r' && data[i] != '\n') + break; + } + end_of_eol = data+i; + break; + case EVBUFFER_EOL_CRLF: + end_of_eol = memchr(data, '\n', len); + if (!end_of_eol) + return (NULL); + if (end_of_eol > data && *(end_of_eol-1) == '\r') + start_of_eol = end_of_eol - 1; + else + start_of_eol = end_of_eol; + end_of_eol++; /*point to one after the LF. */ + break; + case EVBUFFER_EOL_CRLF_STRICT: { + u_char *cp = data; + while ((cp = memchr(cp, '\r', len-(cp-data)))) { + if (cp < data+len-1 && *(cp+1) == '\n') + break; + if (++cp >= data+len) { + cp = NULL; + break; + } + } + if (!cp) + return (NULL); + start_of_eol = cp; + end_of_eol = cp+2; + break; } - - if (i == len) + case EVBUFFER_EOL_LF: + start_of_eol = memchr(data, '\n', len); + if (!start_of_eol) + return (NULL); + end_of_eol = start_of_eol + 1; + break; + default: return (NULL); + } - if ((line = event_malloc(i + 1)) == NULL) { + n_to_copy = start_of_eol - data; + n_to_drain = end_of_eol - data; + + if ((line = event_malloc(n_to_copy+1)) == NULL) { fprintf(stderr, "%s: out of memory\n", __func__); - evbuffer_drain(buffer, i); + evbuffer_drain(buffer, n_to_drain); return (NULL); } - memcpy(line, data, i); - line[i] = '\0'; - - /* - * Some protocols terminate a line with '\r\n', so check for - * that, too. - */ - if ( i < len - 1 ) { - char fch = data[i], sch = data[i+1]; - - /* Drain one more character if needed */ - if ( (sch == '\r' || sch == '\n') && sch != fch ) - i += 1; - } + memcpy(line, data, n_to_copy); + line[n_to_copy] = '\0'; - evbuffer_drain(buffer, i + 1); + evbuffer_drain(buffer, n_to_drain); + if (n_read_out) + *n_read_out = (size_t)n_to_copy; return (line); } diff --git a/event.h b/event.h index bbbbcacc..89cf75ff 100644 --- a/event.h +++ b/event.h @@ -952,18 +952,40 @@ int evbuffer_add(struct evbuffer *, const void *, size_t); */ int evbuffer_remove(struct evbuffer *, void *, size_t); +/** Used to tell evbuffer_readln what kind of line-ending to look for. + */ +enum evbuffer_eol_style { + /** Any sequence of CR and LF characters is acceptable as an EOL. */ + EVBUFFER_EOL_ANY, + /** An EOL is an LF, optionally preceded by a CR. This style is + * most useful for implementing text-based internet protocols. */ + EVBUFFER_EOL_CRLF, + /** An EOL is a CR followed by an LF. */ + EVBUFFER_EOL_CRLF_STRICT, + /** An EOL is a LF. */ + EVBUFFER_EOL_LF +}; /** * Read a single line from an event buffer. * - * Reads a line terminated by either '\r\n', '\n\r' or '\r' or '\n'. - * The returned buffer needs to be freed by the caller. + * Reads a line terminated by an EOL as determined by the evbuffer_eol_style + * argument. Returns a newly allocated nul-terminated string; the caller must + * free the returned value. The EOL is not included in the returned string. * * @param buffer the evbuffer to read from + * @param n_read_out if non-NULL, points to a size_t that is set to the + * number of characters in the returned string. This is useful for + * strings that can contain NUL characters. + * @param eol_style the style of line-ending to use. * @return pointer to a single line, or NULL if an error occurred */ -char *evbuffer_readline(struct evbuffer *); +char *evbuffer_readln(struct evbuffer *, size_t *, enum evbuffer_eol_style); +/** + Obsolete alias for evbuffer_readln(buffer, NULL, EOL_STYLE_ANY). + **/ +char *evbuffer_readline(struct evbuffer *); /** Move data from one evbuffer into another evbuffer. diff --git a/http.c b/http.c index 7390883b..2017f193 100644 --- a/http.c +++ b/http.c @@ -685,7 +685,7 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) while ((len = EVBUFFER_LENGTH(buf)) > 0) { if (req->ntoread < 0) { /* Read chunk size */ - char *p = evbuffer_readline(buf); + char *p = evbuffer_readln(buf, NULL, EVBUFFER_EOL_CRLF); char *endp; int error; if (p == NULL) @@ -1239,7 +1239,7 @@ evhttp_parse_lines(struct evhttp_request *req, struct evbuffer* buffer) int done = 0; struct evkeyvalq* headers = req->input_headers; - while ((line = evbuffer_readline(buffer)) != NULL) { + while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF)) != NULL) { char *skey, *svalue; if (*line == '\0') { /* Last header - Done */ diff --git a/test/regress.c b/test/regress.c index 3e5378a0..58714587 100644 --- a/test/regress.c +++ b/test/regress.c @@ -808,6 +808,115 @@ test_evbuffer(void) { cleanup_test(); } +void +test_evbuffer_readln(void) +{ + struct evbuffer *evb = evbuffer_new(); + const char *s; + char *cp = NULL; + size_t sz; + setup_test("Testing evbuffer_readln(): "); + + /* Test EOL_ANY. */ + s = "complex silly newline\r\n\n\r\n\n\rmore\0\n"; + evbuffer_add(evb, s, strlen(s)+2); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + if (!cp || sz != strlen(cp) || strcmp(cp, "complex silly newline")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + if (!cp || sz != 5 || memcmp(cp, "more\0\0", 6)) + goto done; + if (EVBUFFER_LENGTH(evb) != 0) + goto done; + s = "\nno newline"; + evbuffer_add(evb, s, strlen(s)); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + if (!cp || sz || strcmp(cp, "")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + if (cp) + goto done; + evbuffer_drain(evb, EVBUFFER_LENGTH(evb)); + if (EVBUFFER_LENGTH(evb) != 0) + goto done; + + /* Test EOL_CRLF */ + s = "Line with\rin the middle\nLine with good crlf\r\n\nfinal\n"; + evbuffer_add(evb, s, strlen(s)); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + if (!cp || sz != strlen(cp) || strcmp(cp, "Line with\rin the middle")) + goto done; + + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + if (!cp || sz != strlen(cp) || strcmp(cp, "Line with good crlf")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + if (!cp || sz != strlen(cp) || strcmp(cp, "")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + if (!cp || sz != strlen(cp) || strcmp(cp, "final")) + goto done; + s = "x"; + evbuffer_add(evb, s, 1); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + if (cp) + goto done; + + /* Test CRLF_STRICT */ + s = " and a bad crlf\nand a good one\r\n\r\nMore\r"; + evbuffer_add(evb, s, strlen(s)); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + if (!cp || sz != strlen(cp) || + strcmp(cp, "x and a bad crlf\nand a good one")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + if (!cp || sz != strlen(cp) || strcmp(cp, "")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + if (cp) + goto done; + evbuffer_add(evb, "\n", 1); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + if (!cp || sz != strlen(cp) || strcmp(cp, "More")) + goto done; + if (EVBUFFER_LENGTH(evb) != 0) + goto done; + + /* Test LF */ + s = "An\rand a nl\n\nText"; + evbuffer_add(evb, s, strlen(s)); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + if (!cp || sz != strlen(cp) || strcmp(cp, "An\rand a nl")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + if (!cp || sz != strlen(cp) || strcmp(cp, "")) + goto done; + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + if (cp) + goto done; + evbuffer_add(evb, "\n", 1); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + if (!cp || sz != strlen(cp) || strcmp(cp, "Text")) + goto done; + + test_ok = 1; + done: + evbuffer_free(evb); + if (cp) free(cp); + cleanup_test(); +} + void test_evbuffer_find(void) { @@ -1263,6 +1372,7 @@ main (int argc, char **argv) test_priorities(3); test_evbuffer(); + test_evbuffer_readln(); test_evbuffer_find(); test_bufferevent(); -- 2.40.0