summaryrefslogtreecommitdiff
path: root/buckets/buckets.c
diff options
context:
space:
mode:
Diffstat (limited to 'buckets/buckets.c')
-rw-r--r--buckets/buckets.c538
1 files changed, 538 insertions, 0 deletions
diff --git a/buckets/buckets.c b/buckets/buckets.c
new file mode 100644
index 0000000..29175ff
--- /dev/null
+++ b/buckets/buckets.c
@@ -0,0 +1,538 @@
+/* Copyright 2002-2004 Justin Erenkrantz and Greg Stein
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <apr_pools.h>
+
+#include "serf.h"
+#include "serf_bucket_util.h"
+
+
+serf_bucket_t *serf_bucket_create(
+ const serf_bucket_type_t *type,
+ serf_bucket_alloc_t *allocator,
+ void *data)
+{
+ serf_bucket_t *bkt = serf_bucket_mem_alloc(allocator, sizeof(*bkt));
+
+ bkt->type = type;
+ bkt->data = data;
+ bkt->allocator = allocator;
+
+ return bkt;
+}
+
+
+apr_status_t serf_default_read_iovec(
+ serf_bucket_t *bucket,
+ apr_size_t requested,
+ int vecs_size,
+ struct iovec *vecs,
+ int *vecs_used)
+{
+ const char *data;
+ apr_size_t len;
+
+ /* Read some data from the bucket.
+ *
+ * Because we're an internal 'helper' to the bucket, we can't call the
+ * normal serf_bucket_read() call because the debug allocator tracker will
+ * end up marking the bucket as read *twice* - once for us and once for
+ * our caller - which is reading the same bucket. This leads to premature
+ * abort()s if we ever see EAGAIN. Instead, we'll go directly to the
+ * vtable and bypass the debug tracker.
+ */
+ apr_status_t status = bucket->type->read(bucket, requested, &data, &len);
+
+ /* assert that vecs_size >= 1 ? */
+
+ /* Return that data as a single iovec. */
+ if (len) {
+ vecs[0].iov_base = (void *)data; /* loses the 'const' */
+ vecs[0].iov_len = len;
+ *vecs_used = 1;
+ }
+ else {
+ *vecs_used = 0;
+ }
+
+ return status;
+}
+
+
+apr_status_t serf_default_read_for_sendfile(
+ serf_bucket_t *bucket,
+ apr_size_t requested,
+ apr_hdtr_t *hdtr,
+ apr_file_t **file,
+ apr_off_t *offset,
+ apr_size_t *len)
+{
+ /* Read a bunch of stuff into the headers.
+ *
+ * See serf_default_read_iovec as to why we call into the vtable
+ * directly.
+ */
+ apr_status_t status = bucket->type->read_iovec(bucket, requested,
+ hdtr->numheaders,
+ hdtr->headers,
+ &hdtr->numheaders);
+
+ /* There isn't a file, and there are no trailers. */
+ *file = NULL;
+ hdtr->numtrailers = 0;
+
+ return status;
+}
+
+
+serf_bucket_t *serf_default_read_bucket(
+ serf_bucket_t *bucket,
+ const serf_bucket_type_t *type)
+{
+ return NULL;
+}
+
+
+void serf_default_destroy(serf_bucket_t *bucket)
+{
+#ifdef SERF_DEBUG_BUCKET_USE
+ serf_debug__bucket_destroy(bucket);
+#endif
+
+ serf_bucket_mem_free(bucket->allocator, bucket);
+}
+
+
+void serf_default_destroy_and_data(serf_bucket_t *bucket)
+{
+ serf_bucket_mem_free(bucket->allocator, bucket->data);
+ serf_default_destroy(bucket);
+}
+
+
+/* ==================================================================== */
+
+
+char *serf_bstrmemdup(serf_bucket_alloc_t *allocator,
+ const char *str,
+ apr_size_t size)
+{
+ char *newstr = serf_bucket_mem_alloc(allocator, size + 1);
+ memcpy(newstr, str, size);
+ newstr[size] = '\0';
+ return newstr;
+}
+
+
+void *serf_bmemdup(serf_bucket_alloc_t *allocator,
+ const void *mem,
+ apr_size_t size)
+{
+ void *newmem = serf_bucket_mem_alloc(allocator, size);
+ memcpy(newmem, mem, size);
+ return newmem;
+}
+
+
+char *serf_bstrdup(serf_bucket_alloc_t *allocator,
+ const char *str)
+{
+ apr_size_t size = strlen(str) + 1;
+ char *newstr = serf_bucket_mem_alloc(allocator, size);
+ memcpy(newstr, str, size);
+ return newstr;
+}
+
+
+/* ==================================================================== */
+
+
+static void find_crlf(const char **data, apr_size_t *len, int *found)
+{
+ const char *start = *data;
+ const char *end = start + *len;
+
+ while (start < end) {
+ const char *cr = memchr(start, '\r', *len);
+
+ if (cr == NULL) {
+ break;
+ }
+ ++cr;
+
+ if (cr < end && cr[0] == '\n') {
+ *len -= cr + 1 - start;
+ *data = cr + 1;
+ *found = SERF_NEWLINE_CRLF;
+ return;
+ }
+ if (cr == end) {
+ *len = 0;
+ *data = end;
+ *found = SERF_NEWLINE_CRLF_SPLIT;
+ return;
+ }
+
+ /* It was a bare CR without an LF. Just move past it. */
+ *len -= cr - start;
+ start = cr;
+ }
+
+ *data = start + *len;
+ *len -= *data - start;
+ *found = SERF_NEWLINE_NONE;
+}
+
+
+void serf_util_readline(
+ const char **data,
+ apr_size_t *len,
+ int acceptable,
+ int *found)
+{
+ const char *start;
+ const char *cr;
+ const char *lf;
+ int want_cr;
+ int want_crlf;
+ int want_lf;
+
+ /* If _only_ CRLF is acceptable, then the scanning needs a loop to
+ * skip false hits on CR characters. Use a separate function.
+ */
+ if (acceptable == SERF_NEWLINE_CRLF) {
+ find_crlf(data, len, found);
+ return;
+ }
+
+ start = *data;
+ cr = lf = NULL;
+ want_cr = acceptable & SERF_NEWLINE_CR;
+ want_crlf = acceptable & SERF_NEWLINE_CRLF;
+ want_lf = acceptable & SERF_NEWLINE_LF;
+
+ if (want_cr || want_crlf) {
+ cr = memchr(start, '\r', *len);
+ }
+ if (want_lf) {
+ lf = memchr(start, '\n', *len);
+ }
+
+ if (cr != NULL) {
+ if (lf != NULL) {
+ if (cr + 1 == lf)
+ *found = want_crlf ? SERF_NEWLINE_CRLF : SERF_NEWLINE_CR;
+ else if (want_cr && cr < lf)
+ *found = SERF_NEWLINE_CR;
+ else
+ *found = SERF_NEWLINE_LF;
+ }
+ else if (cr == start + *len - 1) {
+ /* the CR occurred in the last byte of the buffer. this could be
+ * a CRLF split across the data boundary.
+ * ### FIX THIS LOGIC? does caller need to detect?
+ */
+ *found = want_crlf ? SERF_NEWLINE_CRLF_SPLIT : SERF_NEWLINE_CR;
+ }
+ else if (want_cr)
+ *found = SERF_NEWLINE_CR;
+ else /* want_crlf */
+ *found = SERF_NEWLINE_NONE;
+ }
+ else if (lf != NULL)
+ *found = SERF_NEWLINE_LF;
+ else
+ *found = SERF_NEWLINE_NONE;
+
+ switch (*found) {
+ case SERF_NEWLINE_LF:
+ *data = lf + 1;
+ break;
+ case SERF_NEWLINE_CR:
+ case SERF_NEWLINE_CRLF:
+ case SERF_NEWLINE_CRLF_SPLIT:
+ *data = cr + 1 + (*found == SERF_NEWLINE_CRLF);
+ break;
+ case SERF_NEWLINE_NONE:
+ *data += *len;
+ break;
+ default:
+ /* Not reachable */
+ return;
+ }
+
+ *len -= *data - start;
+}
+
+
+/* ==================================================================== */
+
+
+void serf_databuf_init(serf_databuf_t *databuf)
+{
+ /* nothing is sitting in the buffer */
+ databuf->remaining = 0;
+
+ /* avoid thinking we have hit EOF */
+ databuf->status = APR_SUCCESS;
+}
+
+/* Ensure the buffer is prepared for reading. Will return APR_SUCCESS,
+ * APR_EOF, or some failure code. *len is only set for EOF. */
+static apr_status_t common_databuf_prep(serf_databuf_t *databuf,
+ apr_size_t *len)
+{
+ apr_size_t readlen;
+ apr_status_t status;
+
+ /* if there is data in the buffer, then we're happy. */
+ if (databuf->remaining > 0)
+ return APR_SUCCESS;
+
+ /* if we already hit EOF, then keep returning that. */
+ if (APR_STATUS_IS_EOF(databuf->status)) {
+ /* *data = NULL; ?? */
+ *len = 0;
+ return APR_EOF;
+ }
+
+ /* refill the buffer */
+ status = (*databuf->read)(databuf->read_baton, sizeof(databuf->buf),
+ databuf->buf, &readlen);
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ return status;
+ }
+
+ databuf->current = databuf->buf;
+ databuf->remaining = readlen;
+ databuf->status = status;
+
+ return APR_SUCCESS;
+}
+
+
+apr_status_t serf_databuf_read(
+ serf_databuf_t *databuf,
+ apr_size_t requested,
+ const char **data,
+ apr_size_t *len)
+{
+ apr_status_t status = common_databuf_prep(databuf, len);
+ if (status)
+ return status;
+
+ /* peg the requested amount to what we have remaining */
+ if (requested == SERF_READ_ALL_AVAIL || requested > databuf->remaining)
+ requested = databuf->remaining;
+
+ /* return the values */
+ *data = databuf->current;
+ *len = requested;
+
+ /* adjust our internal state to note we've consumed some data */
+ databuf->current += requested;
+ databuf->remaining -= requested;
+
+ /* If we read everything, then we need to return whatever the data
+ * read returned to us. This is going to be APR_EOF or APR_EGAIN.
+ * If we have NOT read everything, then return APR_SUCCESS to indicate
+ * that we're ready to return some more if asked.
+ */
+ return databuf->remaining ? APR_SUCCESS : databuf->status;
+}
+
+
+apr_status_t serf_databuf_readline(
+ serf_databuf_t *databuf,
+ int acceptable,
+ int *found,
+ const char **data,
+ apr_size_t *len)
+{
+ apr_status_t status = common_databuf_prep(databuf, len);
+ if (status)
+ return status;
+
+ /* the returned line will start at the current position. */
+ *data = databuf->current;
+
+ /* read a line from the buffer, and adjust the various pointers. */
+ serf_util_readline(&databuf->current, &databuf->remaining, acceptable,
+ found);
+
+ /* the length matches the amount consumed by the readline */
+ *len = databuf->current - *data;
+
+ /* see serf_databuf_read's return condition */
+ return databuf->remaining ? APR_SUCCESS : databuf->status;
+}
+
+
+apr_status_t serf_databuf_peek(
+ serf_databuf_t *databuf,
+ const char **data,
+ apr_size_t *len)
+{
+ apr_status_t status = common_databuf_prep(databuf, len);
+ if (status)
+ return status;
+
+ /* return everything we have */
+ *data = databuf->current;
+ *len = databuf->remaining;
+
+ /* If the last read returned EOF, then the peek should return the same.
+ * The other possibility in databuf->status is APR_EAGAIN, which we
+ * should never return. Thus, just return APR_SUCCESS for non-EOF cases.
+ */
+ if (APR_STATUS_IS_EOF(databuf->status))
+ return APR_EOF;
+ return APR_SUCCESS;
+}
+
+
+/* ==================================================================== */
+
+
+void serf_linebuf_init(serf_linebuf_t *linebuf)
+{
+ linebuf->state = SERF_LINEBUF_EMPTY;
+ linebuf->used = 0;
+}
+
+
+apr_status_t serf_linebuf_fetch(
+ serf_linebuf_t *linebuf,
+ serf_bucket_t *bucket,
+ int acceptable)
+{
+ /* If we had a complete line, then assume the caller has used it, so
+ * we can now reset the state.
+ */
+ if (linebuf->state == SERF_LINEBUF_READY) {
+ linebuf->state = SERF_LINEBUF_EMPTY;
+
+ /* Reset the line_used, too, so we don't have to test the state
+ * before using this value.
+ */
+ linebuf->used = 0;
+ }
+
+ while (1) {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ if (linebuf->state == SERF_LINEBUF_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(bucket, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return status;
+
+ 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.
+ */
+ /* ### check status */
+ (void) serf_bucket_read(bucket, 1, &data, &len);
+ }
+ /* else:
+ * We saw 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. */
+ linebuf->state = SERF_LINEBUF_READY;
+ }
+ /* ### we need data. gotta check this char. bail if zero?! */
+ /* else len == 0 */
+
+ /* ### status */
+ }
+ else {
+ int found;
+
+ status = serf_bucket_readline(bucket, acceptable, &found,
+ &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status)) {
+ return status;
+ }
+ /* Some bucket types (socket) might need an extra read to find
+ out EOF state, so they'll return no data in that read. This
+ means we're done reading, return what we got. */
+ if (APR_STATUS_IS_EOF(status) && len == 0) {
+ return status;
+ }
+ if (linebuf->used + len > sizeof(linebuf->line)) {
+ /* ### need a "line too long" error */
+ return APR_EGENERAL;
+ }
+
+ /* Note: our logic doesn't change for SERF_LINEBUF_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) {
+ linebuf->state = SERF_LINEBUF_PARTIAL;
+ }
+ else if (found == SERF_NEWLINE_CRLF_SPLIT) {
+ linebuf->state = SERF_LINEBUF_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);
+
+ linebuf->state = SERF_LINEBUF_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(&linebuf->line[linebuf->used], data, len);
+ linebuf->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 || linebuf->state == SERF_LINEBUF_READY)
+ return status;
+
+ /* We got APR_SUCCESS and the line buffer is not complete. Let's
+ * loop to read some more data.
+ */
+ }
+ /* NOTREACHED */
+}