diff --git a/buffer.c b/buffer.c index dfaca5d6..d2ae5cb8 100644 --- a/buffer.c +++ b/buffer.c @@ -248,6 +248,90 @@ evbuffer_readline(struct evbuffer *buffer) return (line); } + +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, 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; + } + 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); + } + + n_to_copy = start_of_eol - data; + n_to_drain = end_of_eol - data; + + if ((line = malloc(n_to_copy+1)) == NULL) { + fprintf(stderr, "%s: out of memory\n", __func__); + evbuffer_drain(buffer, n_to_drain); + return (NULL); + } + + memcpy(line, data, n_to_copy); + line[n_to_copy] = '\0'; + + evbuffer_drain(buffer, n_to_drain); + if (n_read_out) + *n_read_out = (size_t)n_to_copy; + + return (line); +} + /* Adds data to an event buffer */ static void diff --git a/event.h b/event.h index cfa0fc3f..d1f5d9e8 100644 --- a/event.h +++ b/event.h @@ -1030,6 +1030,38 @@ int evbuffer_remove(struct evbuffer *, void *, size_t); char *evbuffer_readline(struct evbuffer *); +/** 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 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_readln(struct evbuffer *buffer, size_t *n_read_out, + enum evbuffer_eol_style eol_style); + + /** Move data from one evbuffer into another evbuffer. diff --git a/test/regress.c b/test/regress.c index 0b7517d3..cce7d7dc 100644 --- a/test/regress.c +++ b/test/regress.c @@ -1019,6 +1019,205 @@ test_evbuffer(void) { cleanup_test(); } +static void +test_evbuffer_readln(void) +{ + struct evbuffer *evb = evbuffer_new(); + struct evbuffer *evb_tmp = evbuffer_new(); + const char *s; + char *cp = NULL; + size_t sz; + +#define tt_line_eq(content) \ + if (!cp || sz != strlen(content) || strcmp(cp, content)) { \ + fprintf(stdout, "FAILED\n"); \ + exit(1); \ + } +#define tt_assert(expression) \ + if (!(expression)) { \ + fprintf(stdout, "FAILED\n"); \ + exit(1); \ + } \ + + /* Test EOL_ANY. */ + fprintf(stdout, "Testing evbuffer_readln 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); + tt_line_eq("complex silly newline"); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + if (!cp || sz != 5 || memcmp(cp, "more\0\0", 6)) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + if (evb->totallen == 0) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + s = "\nno newline"; + evbuffer_add(evb, s, strlen(s)); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_line_eq(""); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_ANY); + tt_assert(!cp); + evbuffer_drain(evb, EVBUFFER_LENGTH(evb)); + tt_assert(EVBUFFER_LENGTH(evb) == 0); + + fprintf(stdout, "OK\n"); + + /* Test EOL_CRLF */ + fprintf(stdout, "Testing evbuffer_readln 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); + tt_line_eq("Line with\rin the middle"); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("Line with good crlf"); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq(""); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_line_eq("final"); + s = "x"; + evbuffer_add(evb, s, 1); + free(cp); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF); + tt_assert(!cp); + + fprintf(stdout, "OK\n"); + + /* Test CRLF_STRICT */ + fprintf(stdout, "Testing evbuffer_readln 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); + tt_line_eq("x and a bad crlf\nand a good one"); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(""); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + evbuffer_add(evb, "\n", 1); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("More"); + free(cp); + tt_assert(EVBUFFER_LENGTH(evb) == 0); + + s = "An internal CR\r is not an eol\r\nNor is a lack of one"; + evbuffer_add(evb, s, strlen(s)); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("An internal CR\r is not an eol"); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + + evbuffer_add(evb, "\r\n", 2); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("Nor is a lack of one"); + free(cp); + tt_assert(EVBUFFER_LENGTH(evb) == 0); + + fprintf(stdout, "OK\n"); + + /* Test LF */ + fprintf(stdout, "Testing evbuffer_readln LF: "); + + s = "An\rand a nl\n\nText"; + evbuffer_add(evb, s, strlen(s)); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("An\rand a nl"); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq(""); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_assert(!cp); + free(cp); + evbuffer_add(evb, "\n", 1); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("Text"); + free(cp); + + fprintf(stdout, "OK\n"); + + /* Test CRLF_STRICT - across boundaries */ + fprintf(stdout, + "Testing evbuffer_readln CRLF_STRICT across boundaries: "); + + s = " and a bad crlf\nand a good one\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_add_buffer(evb, evb_tmp); + s = "\n\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_add_buffer(evb, evb_tmp); + s = "\nMore\r"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_add_buffer(evb, evb_tmp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(" and a bad crlf\nand a good one"); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq(""); + free(cp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_assert(!cp); + free(cp); + evbuffer_add(evb, "\n", 1); + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_CRLF_STRICT); + tt_line_eq("More"); + free(cp); cp = NULL; + if (EVBUFFER_LENGTH(evb) != 0) { + fprintf(stdout, "FAILED\n"); + exit(1); + } + + fprintf(stdout, "OK\n"); + + /* Test memory problem */ + fprintf(stdout, "Testing evbuffer_readln memory problem: "); + + s = "one line\ntwo line\nblue line"; + evbuffer_add(evb_tmp, s, strlen(s)); + evbuffer_add_buffer(evb, evb_tmp); + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("one line"); + free(cp); cp = NULL; + + cp = evbuffer_readln(evb, &sz, EVBUFFER_EOL_LF); + tt_line_eq("two line"); + free(cp); cp = NULL; + + fprintf(stdout, "OK\n"); + + test_ok = 1; + evbuffer_free(evb); + evbuffer_free(evb_tmp); + if (cp) free(cp); +} + static void test_evbuffer_find(void) { @@ -1640,6 +1839,7 @@ main (int argc, char **argv) test_evbuffer(); test_evbuffer_find(); + test_evbuffer_readln(); test_bufferevent(); test_bufferevent_watermarks();