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
This commit is contained in:
Nick Mathewson 2007-11-25 21:32:26 +00:00
parent ab010e161f
commit 6773a59721
4 changed files with 207 additions and 29 deletions

View File

@ -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;
unsigned int i, n_to_copy, n_to_drain;
for (i = 0; i < len; i++) {
if (data[i] == '\r' || data[i] == '\n')
break;
/* 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);
}
if (i == len)
return (NULL);
n_to_copy = start_of_eol - data;
n_to_drain = end_of_eol - data;
if ((line = event_malloc(i + 1)) == NULL) {
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';
memcpy(line, data, n_to_copy);
line[n_to_copy] = '\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;
}
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);
}

28
event.h
View File

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

4
http.c
View File

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

View File

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