Add an HTTP response bucket type.

Note: this code merely compiles; it doesn't even pretend to work. I'm
simply checking this in now cuz it was sitting around my working copy.

* serf_bucket_types.h: combine the read/parse of the Status-Line into a
    single parsing function returning a structure.

* buckets/respones_buckets.c: initial draft of code to implement the new
    RESPONSE bucket type. it uses a state machine to keep track of what it
    is doing as it reads content from the lower-level bucket [a socket]

* Makefile: compile the new response_buckets.c file


git-svn-id: https://svn.apache.org/repos/asf/commons/serf/branches/gen2@36203 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/Makefile b/Makefile
index ca303bc..50d1317 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@
 OBJECTS = buckets/aggregate_buckets.o buckets/request_buckets.o context.o \
           buckets/buckets.o buckets/simple_buckets.o buckets/file_buckets.o \
           buckets/mmap_buckets.o buckets/socket_buckets.o \
+          buckets/response_buckets.o \
           test/serf_get.o
 
 # Place apr-config and apu-config in your PATH.
diff --git a/buckets/response_buckets.c b/buckets/response_buckets.c
new file mode 100644
index 0000000..7f94913
--- /dev/null
+++ b/buckets/response_buckets.c
@@ -0,0 +1,256 @@
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2003 The Apache Software Foundation.  All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Apache" and "Apache Software Foundation" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ *    nor may "Apache" appear in their name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ */
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+
+/* the limit on the length of a line in the status-line or headers */
+#define LINE_LIMIT 8000
+
+typedef struct {
+    serf_bucket_t *stream;
+
+    enum {
+        STATE_STATUS_LINE,      /* reading status line */
+        STATE_HEADERS,          /* reading headers */
+        STATE_BODY              /* reading body */
+    } state;
+
+    enum {
+        LINE_EMPTY,
+        LINE_READY,
+        LINE_PARTIAL,
+        LINE_CRLF_SPLIT
+    } lstate;
+    apr_size_t line_used;
+    char line[LINE_LIMIT];
+
+} response_context_t;
+
+
+SERF_DECLARE(serf_bucket_t *) serf_bucket_response_create(
+    serf_bucket_t *stream,
+    serf_bucket_alloc_t *allocator)
+{
+    response_context_t *ctx;
+
+    ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx));
+    ctx->stream = stream;
+
+    return serf_bucket_create(&serf_bucket_type_response, allocator, ctx);
+}
+
+static apr_status_t fetch_line(response_context_t *ctx,
+                               serf_bucket_t *bkt)
+{
+    /* If we had a complete line, then assume the caller has used it, so
+     * we can now reset the state.
+     */
+    if (ctx->lstate == LINE_READY) {
+        ctx->lstate = LINE_EMPTY;
+
+        /* Reset the line_used, too, so we don't have to test the state
+         * before using this value.
+         */
+        ctx->line_used = 0;
+    }
+
+    while (1) {
+        apr_status_t status;
+        const char *data;
+        apr_size_t len;
+
+        if (ctx->lstate == LINE_CRLF_SPLIT) {
+            /* On the previous read, we received just a CR. The LF might
+             * be present, but the bucket couldn't see it. We need to
+             * examine a single character to determine how to handle the
+             * split CRLF.
+             */
+
+            status = serf_bucket_peek(bkt, &data, &len);
+            if (len > 0) {
+                if (*data == '\n') {
+                    /* We saw the second part of CRLF. We don't need to
+                     * save that character, so do an actual read to suck
+                     * up that character.
+                     */
+                    (void) serf_bucket_read(bkt, 1, &data, &len);
+                }
+                /* else:
+                 *   We got the first character of the next line. Thus,
+                 *   the current line is terminated by the CR. Just
+                 *   ignore whatever we peeked at. The next reader will
+                 *   see it and handle it as appropriate.
+                 */
+
+                /* Whatever was read, the line is now ready for use. */
+                ctx->lstate = LINE_READY;
+            }
+            /* else len == 0 */
+
+            /* ### status */
+        }
+        else {
+            int found;
+
+            /* RFC 2616 says that CRLF is the only line ending, but we
+             * can easily accept any kind of line ending.
+             */
+            status = serf_bucket_readline(bkt, SERF_NEWLINE_ANY, &found,
+                                          &data, &len);
+
+            if (ctx->line_used + len > sizeof(ctx->line)) {
+                /* ### need a "line too long" error */
+                return APR_EGENERAL;
+            }
+
+            /* Note: our logic doesn't change for LINE_PARTIAL. That only
+             * affects how we fill the buffer. It is a communication to our
+             * caller on whether the line is ready or not.
+             */
+
+            /* If we didn't see a newline, then we should mark the line
+             * buffer as partially complete.
+             */
+            if (found == SERF_NEWLINE_NONE) {
+                ctx->lstate = LINE_PARTIAL;
+            }
+            else if (found == SERF_NEWLINE_CRLF_SPLIT) {
+                ctx->lstate = LINE_CRLF_SPLIT;
+
+                /* Toss the partial CR. We won't ever need it. */
+                --len;
+            }
+            else {
+                /* We got a newline (of some form). We don't need it
+                 * in the line buffer, so back up the length. Then
+                 * mark the line as ready.
+                 */
+                len -= 1 + (found == SERF_NEWLINE_CRLF);
+
+                ctx->lstate = LINE_READY;
+            }
+
+            /* ### it would be nice to avoid this copy if at all possible,
+               ### and just return the a data/len pair to the caller. we're
+               ### keeping it simple for now. */
+            memcpy(&ctx->line[ctx->line_used], data, len);
+            ctx->line_used += len;
+        }
+
+        /* If we saw anything besides "success. please read again", then
+         * we should return that status. If the line was completed, then
+         * we should also return.
+         */
+        if (status || ctx->lstate == LINE_READY)
+            return status;
+
+        /* We got APR_SUCCESS and the line buffer is not complete. Let's
+         * loop to read some more data.
+         */
+    }
+    /* NOTREACHED */
+}
+
+static apr_status_t run_machine(response_context_t *ctx)
+{
+    return APR_SUCCESS;
+}
+
+static apr_status_t wait_for_sline(response_context_t *ctx)
+{
+    /* Keep looping while we're still working on the Status-Line and we
+     * don't have any issues reading from the input stream.
+     */
+    while (ctx->state == STATE_STATUS_LINE) {
+        apr_status_t status = run_machine(ctx);
+        if (status) {
+            /* we stop any anthing. */
+            return status;
+        }
+    }
+
+    return APR_SUCCESS;
+}
+
+SERF_DECLARE(apr_status_t) serf_bucket_response_status(
+    serf_bucket_t *bkt,
+    serf_status_line *sline)
+{
+    response_context_t *ctx = bkt->data;
+    apr_status_t status;
+
+    if ((status = wait_for_sline(ctx)) != APR_SUCCESS)
+        return status;
+
+    return APR_SUCCESS;
+}
+
+
+/* ### need to implement */
+#define serf_response_read NULL
+#define serf_response_readline NULL
+#define serf_response_read_iovec NULL
+#define serf_response_read_for_sendfile NULL
+#define serf_response_peek NULL
+
+SERF_DECLARE_DATA const serf_bucket_type_t serf_bucket_type_response = {
+    "RESPONSE",
+    serf_response_read,
+    serf_response_readline,
+    serf_response_read_iovec,
+    serf_response_read_for_sendfile,
+    serf_default_read_bucket,
+    serf_response_peek,
+    serf_default_get_metadata,
+    serf_default_set_metadata,
+    serf_default_destroy_and_data,
+};
diff --git a/serf_bucket_types.h b/serf_bucket_types.h
index 5e61213..73751c5 100644
--- a/serf_bucket_types.h
+++ b/serf_bucket_types.h
@@ -96,16 +96,19 @@
     serf_bucket_t *stream,
     serf_bucket_alloc_t *allocator);
 
-/* ### hmm. these need to return APR_EAGAIN somehow. maybe 0 for the
-   ### integer functions and NULL for the reason? hmm. should probably
-   ### switch to apr_status_t so that we can return *any* network-related
-   ### error. (or parsing error or whatever)
-*/
-SERF_DECLARE(int) serf_bucket_response_status(serf_bucket_t *bkt);
+#define SERF_HTTP_VERSION(major, minor)  ((major) * 1000 + (minor))
+#define SERF_HTTP_11 SERF_HTTP_VERSION(1, 1)
+#define SERF_HTTP_10 SERF_HTTP_VERSION(1, 0)
 
-SERF_DECLARE(const char *) serf_bucket_response_reason(serf_bucket_t *bkt);
+typedef struct {
+    int version;
+    int code;
+    const char *reason;
+} serf_status_line;
 
-SERF_DECLARE(int) serf_bucket_response_protocol(serf_bucket_t *bkt);
+SERF_DECLARE(apr_status_t) serf_bucket_response_status(
+    serf_bucket_t *bkt,
+    serf_status_line *sline);
 
 
 /* ==================================================================== */