static apr_status_t ap_fgetline_core()

in server/protocol.c [213:526]


static apr_status_t ap_fgetline_core(char **s, apr_size_t n,
                                     apr_size_t *read, ap_filter_t *f,
                                     int flags, apr_bucket_brigade *bb,
                                     apr_pool_t *p)
{
    apr_status_t rv;
    apr_bucket *e;
    apr_size_t bytes_handled = 0, current_alloc = 0;
    char *pos, *last_char = *s;
    int do_alloc = (*s == NULL), saw_eos = 0;
    int fold = flags & AP_GETLINE_FOLD;
    int crlf = flags & AP_GETLINE_CRLF;
    int nospc_eol = flags & AP_GETLINE_NOSPC_EOL;
    int saw_eol = 0, saw_nospc = 0;
    apr_read_type_e block;

    if (!n) {
        /* Needs room for NUL byte at least */
        *read = 0;
        return APR_BADARG;
    }

    block = (flags & AP_GETLINE_NONBLOCK) ? APR_NONBLOCK_READ
                                          : APR_BLOCK_READ;

    /*
     * Initialize last_char as otherwise a random value will be compared
     * against APR_ASCII_LF at the end of the loop if bb only contains
     * zero-length buckets.
     */
    if (last_char)
        *last_char = '\0';

    do {
        apr_brigade_cleanup(bb);
        rv = ap_get_brigade(f, bb, AP_MODE_GETLINE, block, 0);
        if (rv != APR_SUCCESS) {
            goto cleanup;
        }

        /* Something horribly wrong happened.  Someone didn't block! 
         * (this also happens at the end of each keepalive connection)
         * (this also happens when non-blocking is asked too, not that wrong)
         */
        if (APR_BRIGADE_EMPTY(bb)) {
            if (block != APR_NONBLOCK_READ) {
                rv = APR_EGENERAL;
            }
            else {
                rv = APR_EAGAIN;
            }
            goto cleanup;
        }

        for (e = APR_BRIGADE_FIRST(bb);
             e != APR_BRIGADE_SENTINEL(bb);
             e = APR_BUCKET_NEXT(e))
        {
            const char *str;
            apr_size_t len;

            /* If we see an EOS, don't bother doing anything more. */
            if (APR_BUCKET_IS_EOS(e)) {
                saw_eos = 1;
                break;
            }

            rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
            if (rv != APR_SUCCESS) {
                goto cleanup;
            }

            if (len == 0) {
                /* no use attempting a zero-byte alloc (hurts when
                 * using --with-efence --enable-pool-debug) or
                 * doing any of the other logic either
                 */
                continue;
            }

            /* Would this overrun our buffer?  If so, we'll die. */
            if (n < bytes_handled + len) {
                /* Before we die, let's fill the buffer up to its limit (i.e.
                 * fall through with the remaining length, if any), setting
                 * saw_eol on LF to stop the outer loop appropriately; we may
                 * come back here once the buffer is filled (no LF seen), and
                 * either be done at that time or continue to wait for LF here
                 * if nospc_eol is set.
                 *
                 * But there is also a corner cases which we want to address,
                 * namely if the buffer is overrun by the final LF only (i.e.
                 * the CR fits in); this is not really an overrun since we'll
                 * strip the CR finally (and use it for NUL byte), but anyway
                 * we have to handle the case so that it's not returned to the
                 * caller as part of the truncated line (it's not!). This is
                 * easier to consider that LF is out of counting and thus fall
                 * through with no error (saw_eol is set to 2 so that we later
                 * ignore LF handling already done here), while folding and
                 * nospc_eol logics continue to work (or fail) appropriately.
                 */
                saw_eol = (str[len - 1] == APR_ASCII_LF);
                if (/* First time around */
                    saw_eol && !saw_nospc
                    /*  Single LF completing the buffered CR, */
                    && ((len == 1 && ((*s)[bytes_handled - 1] == APR_ASCII_CR))
                    /*  or trailing CRLF overuns by LF only */
                        || (len > 1 && str[len - 2] == APR_ASCII_CR
                            && n - bytes_handled + 1 == len))) {
                    /* In both cases *last_char is (to be) the CR stripped by
                     * later 'bytes_handled = last_char - *s'.
                     */
                    saw_eol = 2;
                }
                else {
                    /* In any other case we'd lose data. */
                    rv = APR_ENOSPC;
                    saw_nospc = 1;
                }
                len = n - bytes_handled;
                if (!len) {
                    if (saw_eol) {
                        break;
                    }
                    if (nospc_eol) {
                        continue;
                    }
                    goto cleanup;
                }
            }

            /* Do we have to handle the allocation ourselves? */
            if (do_alloc) {
                /* We'll assume the common case where one bucket is enough. */
                if (!*s) {
                    current_alloc = len;
                    *s = apr_palloc(p, current_alloc + 1);
                }
                else if (bytes_handled + len > current_alloc) {
                    /* Increase the buffer size */
                    apr_size_t new_size = current_alloc * 2;
                    char *new_buffer;

                    if (bytes_handled + len > new_size) {
                        new_size = (bytes_handled + len) * 2;
                    }

                    new_buffer = apr_palloc(p, new_size + 1);

                    /* Copy what we already had. */
                    memcpy(new_buffer, *s, bytes_handled);
                    current_alloc = new_size;
                    *s = new_buffer;
                }
            }

            /* Just copy the rest of the data to the end of the old buffer. */
            pos = *s + bytes_handled;
            memcpy(pos, str, len);
            last_char = pos + len - 1;

            /* We've now processed that new data - update accordingly. */
            bytes_handled += len;
        }

        /* If we got a full line of input, stop reading */
        if (last_char && (*last_char == APR_ASCII_LF)) {
            saw_eol = 1;
        }
    } while (!saw_eol);

    if (rv != APR_SUCCESS) {
        /* End of line after APR_ENOSPC above */
        goto cleanup;
    }

    /* Now terminate the string at the end of the line;
     * if the last-but-one character is a CR, terminate there.
     * LF is handled above (not accounted) when saw_eol == 2,
     * the last char is CR to terminate at still.
     */
    if (saw_eol < 2) {
        if (last_char > *s && last_char[-1] == APR_ASCII_CR) {
            last_char--;
        }
        else if (crlf) {
            rv = APR_EINVAL;
            goto cleanup;
        }
    }
    bytes_handled = last_char - *s;

    /* If we're folding, we have more work to do.
     *
     * Note that if an EOS was seen, we know we can't have another line.
     */
    if (fold && bytes_handled && !saw_eos) {
        for (;;) {
            const char *str;
            apr_size_t len;
            char c;

            /* Clear the temp brigade for this filter read. */
            apr_brigade_cleanup(bb);

            /* We only care about the first byte. */
            rv = ap_get_brigade(f, bb, AP_MODE_SPECULATIVE, block, 1);
            if (rv != APR_SUCCESS) {
                goto cleanup;
            }

            if (APR_BRIGADE_EMPTY(bb)) {
                break;
            }

            e = APR_BRIGADE_FIRST(bb);

            /* If we see an EOS, don't bother doing anything more. */
            if (APR_BUCKET_IS_EOS(e)) {
                break;
            }

            rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
            if (rv != APR_SUCCESS) {
                apr_brigade_cleanup(bb);
                goto cleanup;
            }

            /* Found one, so call ourselves again to get the next line.
             *
             * FIXME: If the folding line is completely blank, should we
             * stop folding?  Does that require also looking at the next
             * char?
             */
            /* When we call destroy, the buckets are deleted, so save that
             * one character we need.  This simplifies our execution paths
             * at the cost of one character read.
             */
            c = *str;
            if (c == APR_ASCII_BLANK || c == APR_ASCII_TAB) {
                /* Do we have enough space? We may be full now. */
                if (bytes_handled >= n) {
                    rv = APR_ENOSPC;
                    goto cleanup;
                }
                else {
                    apr_size_t next_size, next_len;
                    char *tmp;

                    /* If we're doing the allocations for them, we have to
                     * give ourselves a NULL and copy it on return.
                     */
                    if (do_alloc) {
                        tmp = NULL;
                    }
                    else {
                        tmp = last_char;
                    }

                    next_size = n - bytes_handled;

                    rv = ap_fgetline_core(&tmp, next_size, &next_len, f,
                                          flags & ~AP_GETLINE_FOLD, bb, p);
                    if (rv != APR_SUCCESS) {
                        goto cleanup;
                    }

                    if (do_alloc && next_len > 0) {
                        char *new_buffer;
                        apr_size_t new_size = bytes_handled + next_len + 1;

                        /* we need to alloc an extra byte for a null */
                        new_buffer = apr_palloc(p, new_size);

                        /* Copy what we already had. */
                        memcpy(new_buffer, *s, bytes_handled);

                        /* copy the new line, including the trailing null */
                        memcpy(new_buffer + bytes_handled, tmp, next_len);
                        *s = new_buffer;
                    }

                    last_char += next_len;
                    bytes_handled += next_len;
                }
            }
            else { /* next character is not tab or space */
                break;
            }
        }
    }

cleanup:
    if (bytes_handled >= n) {
        bytes_handled = n - 1;
    }

    *read = bytes_handled;
    if (*s) {
        /* ensure the string is NUL terminated */
        (*s)[*read] = '\0';

        /* PR#43039: We shouldn't accept NULL bytes within the line */
        bytes_handled = strlen(*s);
        if (bytes_handled < *read) {
            ap_log_data(APLOG_MARK, APLOG_DEBUG, ap_server_conf,
                        "NULL bytes in header", *s, *read, 0);
            *read = bytes_handled;
            if (rv == APR_SUCCESS) {
                rv = APR_EINVAL;
            }
        }
    }
    return rv;
}