Backport evbuffer_readln().

This is necessary because it is not actually possible to use
evbuffer_readline() safely: it will treat "A\r\n" as 'A' EOL if it
reads it all at once, and as 'A' EOL EOL if there is a delay between
reading the \r and the \n.

Nicholas Marriott's comments on this patch:

   Gilles is too busy so I've had a go at this, please see the diff
   below.  Rather than try to backport directly from 2.0 where the
   evbuffer code is quite different, I've backported the _readln
   function from when it was initially added in buffer.c r550. I can't
   see any relevant bug fixes after this point so the function is
   pretty much just copied in directly from that revision.
This commit is contained in:
Nicholas Marriott 2009-11-23 12:55:50 -05:00 committed by Nick Mathewson
parent d014edb2c0
commit b04cc60f13
3 changed files with 316 additions and 0 deletions

View File

@ -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

32
event.h
View File

@ -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.

View File

@ -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();