mirror of
https://github.com/cuberite/libevent.git
synced 2025-09-09 12:28:19 -04:00
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:
parent
d014edb2c0
commit
b04cc60f13
84
buffer.c
84
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
|
||||
|
32
event.h
32
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.
|
||||
|
||||
|
200
test/regress.c
200
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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user