]> granicus.if.org Git - libevent/commitdiff
r14953@tombo: nickm | 2007-11-25 15:56:40 -0500
authorNick Mathewson <nickm@torproject.org>
Sun, 25 Nov 2007 21:32:26 +0000 (21:32 +0000)
committerNick Mathewson <nickm@torproject.org>
Sun, 25 Nov 2007 21:32:26 +0000 (21:32 +0000)
 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

buffer.c
event.h
http.c
test/regress.c

index f1b62e214945b9e9cbd71a908824720f736fd324..6dacca8f241eb095aeefa18c0b002849ff048bd6 100644 (file)
--- a/buffer.c
+++ b/buffer.c
@@ -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;
-
-       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);
 }
diff --git a/event.h b/event.h
index bbbbcaccb7d0f7712db7fee6eb0740029783894d..89cf75ffce066853abb5640bdeae8711faff9cf7 100644 (file)
--- a/event.h
+++ b/event.h
@@ -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.
diff --git a/http.c b/http.c
index 7390883b7ee320f22a74ba097dbf798ff9ea7331..2017f193122d26a3639468ad4ce08b6bb719fbec 100644 (file)
--- a/http.c
+++ b/http.c
@@ -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 */
index 3e5378a0d4e554fb5e302f243861bf3a0a7e0b19..58714587961a73ced3c00359e8d184ffe693e091 100644 (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();