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
* 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);
}
*/
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.
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)
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 */
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)
{
test_priorities(3);
test_evbuffer();
+ test_evbuffer_readln();
test_evbuffer_find();
test_bufferevent();