view src/http/modules/perl/nginx.xs @ 5324:8ef1722143dc

SPDY: do not reject headers with empty value (ticket #396). A quote from SPDY draft 2 specification: "The length of each name and value must be greater than zero. A receiver of a zero-length name or value must send a RST_STREAM with code PROTOCOL error." But it appears that Chrome browser allows sending requests over SPDY/2 connection using JavaScript that contain headers with empty values. For better compatibility across SPDY clients and to be compliant with HTTP, such headers are no longer rejected. Also, it is worth noting that in SPDY draft 3 the statement has been changed so that it permits empty values for headers.
author Valentin Bartenev <vbart@nginx.com>
date Thu, 15 Aug 2013 19:16:09 +0400
parents 43900b822890
children 5f2a0739da19
line wrap: on
line source


/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#define PERL_NO_GET_CONTEXT

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_http_perl_module.h>

#include "XSUB.h"


#define ngx_http_perl_set_request(r)                                          \
    r = INT2PTR(ngx_http_request_t *, SvIV((SV *) SvRV(ST(0))))


#define ngx_http_perl_set_targ(p, len)                                        \
                                                                              \
    SvUPGRADE(TARG, SVt_PV);                                                  \
    SvPOK_on(TARG);                                                           \
    sv_setpvn(TARG, (char *) p, len)


static ngx_int_t
ngx_http_perl_sv2str(pTHX_ ngx_http_request_t *r, ngx_str_t *s, SV *sv)
{
    u_char  *p;
    STRLEN   len;

    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
        sv = SvRV(sv);
    }

    p = (u_char *) SvPV(sv, len);

    s->len = len;

    if (SvREADONLY(sv) && SvPOK(sv)) {
        s->data = p;

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);

        return NGX_OK;
    }

    s->data = ngx_pnalloc(r->pool, len);
    if (s->data == NULL) {
        return NGX_ERROR;
    }

    ngx_memcpy(s->data, p, len);

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "perl sv2str: %08XD \"%V\"", sv->sv_flags, s);

    return NGX_OK;
}


static ngx_int_t
ngx_http_perl_output(ngx_http_request_t *r, ngx_buf_t *b)
{
    ngx_chain_t           out;
#if (NGX_HTTP_SSI)
    ngx_chain_t          *cl;
    ngx_http_perl_ctx_t  *ctx;

    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);

    if (ctx->ssi) {
        cl = ngx_alloc_chain_link(r->pool);
        if (cl == NULL) {
            return NGX_ERROR;
        }

        cl->buf = b;
        cl->next = NULL;
        *ctx->ssi->last_out = cl;
        ctx->ssi->last_out = &cl->next;

        return NGX_OK;
    }
#endif

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}


MODULE = nginx    PACKAGE = nginx


void
status(r, code)
    CODE:

    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);

    r->headers_out.status = SvIV(ST(1));

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "perl status: %d", r->headers_out.status);

    XSRETURN_UNDEF;


void
send_http_header(r, ...)
    CODE:

    ngx_http_request_t  *r;
    SV                  *sv;

    ngx_http_perl_set_request(r);

    if (r->headers_out.status == 0) {
        r->headers_out.status = NGX_HTTP_OK;
    }

    if (items != 1) {
        sv = ST(1);

        if (ngx_http_perl_sv2str(aTHX_ r, &r->headers_out.content_type, sv)
            != NGX_OK)
        {
            XSRETURN_EMPTY;
        }

        r->headers_out.content_type_len = r->headers_out.content_type.len;

    } else {
        if (ngx_http_set_content_type(r) != NGX_OK) {
            XSRETURN_EMPTY;
        }
    }

    (void) ngx_http_send_header(r);


void
header_only(r)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);

    sv_upgrade(TARG, SVt_IV);
    sv_setiv(TARG, r->header_only);

    ST(0) = TARG;


void
uri(r)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);
    ngx_http_perl_set_targ(r->uri.data, r->uri.len);

    ST(0) = TARG;


void
args(r)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);
    ngx_http_perl_set_targ(r->args.data, r->args.len);

    ST(0) = TARG;


void
request_method(r)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);
    ngx_http_perl_set_targ(r->method_name.data, r->method_name.len);

    ST(0) = TARG;


void
remote_addr(r)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);
    ngx_http_perl_set_targ(r->connection->addr_text.data,
                           r->connection->addr_text.len);

    ST(0) = TARG;


void
header_in(r, key)
    CODE:

    dXSTARG;
    ngx_http_request_t         *r;
    SV                         *key;
    u_char                     *p, *lowcase_key, *value, sep;
    STRLEN                      len;
    ssize_t                     size;
    ngx_uint_t                  i, n, hash;
    ngx_array_t                *a;
    ngx_list_part_t            *part;
    ngx_table_elt_t            *h, **ph;
    ngx_http_header_t          *hh;
    ngx_http_core_main_conf_t  *cmcf;

    ngx_http_perl_set_request(r);

    key = ST(1);

    if (SvROK(key) && SvTYPE(SvRV(key)) == SVt_PV) {
        key = SvRV(key);
    }

    p = (u_char *) SvPV(key, len);

    /* look up hashed headers */

    lowcase_key = ngx_pnalloc(r->pool, len);
    if (lowcase_key == NULL) {
        XSRETURN_UNDEF;
    }

    hash = ngx_hash_strlow(lowcase_key, p, len);

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    hh = ngx_hash_find(&cmcf->headers_in_hash, hash, lowcase_key, len);

    if (hh) {

        if (hh->offset == offsetof(ngx_http_headers_in_t, cookies)) {
            sep = ';';
            goto multi;
        }
#if (NGX_HTTP_X_FORWARDED_FOR)
        if (hh->offset == offsetof(ngx_http_headers_in_t, x_forwarded_for)) {
            sep = ',';
            goto multi;
        }
#endif

        if (hh->offset) {

            ph = (ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset);

            if (*ph) {
                ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);

                goto done;
            }

            XSRETURN_UNDEF;
        }

    multi:

        /* Cookie, X-Forwarded-For */

        a = (ngx_array_t *) ((char *) &r->headers_in + hh->offset);

        n = a->nelts;

        if (n == 0) {
            XSRETURN_UNDEF;
        }

        ph = a->elts;

        if (n == 1) {
            ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len);

            goto done;
        }

        size = - (ssize_t) (sizeof("; ") - 1);

        for (i = 0; i < n; i++) {
            size += ph[i]->value.len + sizeof("; ") - 1;
        }

        value = ngx_pnalloc(r->pool, size);
        if (value == NULL) {
            XSRETURN_UNDEF;
        }

        p = value;

        for (i = 0; /* void */ ; i++) {
            p = ngx_copy(p, ph[i]->value.data, ph[i]->value.len);

            if (i == n - 1) {
                break;
            }

            *p++ = sep; *p++ = ' ';
        }

        ngx_http_perl_set_targ(value, size);

        goto done;
    }

    /* iterate over all headers */

    part = &r->headers_in.headers.part;
    h = part->elts;

    for (i = 0; /* void */ ; i++) {

        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            h = part->elts;
            i = 0;
        }

        if (len != h[i].key.len
            || ngx_strcasecmp(p, h[i].key.data) != 0)
        {
            continue;
        }

        ngx_http_perl_set_targ(h[i].value.data, h[i].value.len);

        goto done;
    }

    XSRETURN_UNDEF;

    done:

    ST(0) = TARG;


void
has_request_body(r, next)
    CODE:

    dXSTARG;
    ngx_http_request_t   *r;
    ngx_http_perl_ctx_t  *ctx;

    ngx_http_perl_set_request(r);

    if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) {
        XSRETURN_UNDEF;
    }

    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
    ctx->next = SvRV(ST(1));

    r->request_body_in_single_buf = 1;
    r->request_body_in_persistent_file = 1;
    r->request_body_in_clean_file = 1;

    if (r->request_body_in_file_only) {
        r->request_body_file_log_level = 0;
    }

    ngx_http_read_client_request_body(r, ngx_http_perl_handle_request);

    sv_upgrade(TARG, SVt_IV);
    sv_setiv(TARG, 1);

    ST(0) = TARG;


void
request_body(r)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;
    u_char              *p, *data;
    size_t               len;
    ngx_buf_t           *buf;
    ngx_chain_t         *cl;

    ngx_http_perl_set_request(r);

    if (r->request_body == NULL
        || r->request_body->temp_file
        || r->request_body->bufs == NULL)
    {
        XSRETURN_UNDEF;
    }

    cl = r->request_body->bufs;
    buf = cl->buf;

    if (cl->next == NULL) {
        len = buf->last - buf->pos;
        data = buf->pos;
        goto done;
    }

    len = buf->last - buf->pos;
    cl = cl->next;

    for ( /* void */ ; cl; cl = cl->next) {
        buf = cl->buf;
        len += buf->last - buf->pos;
    }

    p = ngx_pnalloc(r->pool, len);
    if (p == NULL) {
        XSRETURN_UNDEF;
    }

    data = p;
    cl = r->request_body->bufs;

    for ( /* void */ ; cl; cl = cl->next) {
        buf = cl->buf;
        p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
    }

    done:

    if (len == 0) {
        XSRETURN_UNDEF;
    }

    ngx_http_perl_set_targ(data, len);

    ST(0) = TARG;


void
request_body_file(r)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);

    if (r->request_body == NULL || r->request_body->temp_file == NULL) {
        XSRETURN_UNDEF;
    }

    ngx_http_perl_set_targ(r->request_body->temp_file->file.name.data,
                           r->request_body->temp_file->file.name.len);

    ST(0) = TARG;


void
discard_request_body(r)
    CODE:

    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);

    ngx_http_discard_request_body(r);


void
header_out(r, key, value)
    CODE:

    ngx_http_request_t  *r;
    SV                  *key;
    SV                  *value;
    ngx_table_elt_t     *header;

    ngx_http_perl_set_request(r);

    key = ST(1);
    value = ST(2);

    header = ngx_list_push(&r->headers_out.headers);
    if (header == NULL) {
        XSRETURN_EMPTY;
    }

    header->hash = 1;

    if (ngx_http_perl_sv2str(aTHX_ r, &header->key, key) != NGX_OK) {
        XSRETURN_EMPTY;
    }

    if (ngx_http_perl_sv2str(aTHX_ r, &header->value, value) != NGX_OK) {
        XSRETURN_EMPTY;
    }

    if (header->key.len == sizeof("Content-Length") - 1
        && ngx_strncasecmp(header->key.data, (u_char *) "Content-Length",
                           sizeof("Content-Length") - 1) == 0)
    {
        r->headers_out.content_length_n = (off_t) SvIV(value);
        r->headers_out.content_length = header;
    }

    if (header->key.len == sizeof("Content-Encoding") - 1
        && ngx_strncasecmp(header->key.data, (u_char *) "Content-Encoding",
                           sizeof("Content-Encoding") - 1) == 0)
    {
        r->headers_out.content_encoding = header;
    }


void
filename(r)
    CODE:

    dXSTARG;
    size_t                root;
    ngx_http_request_t   *r;
    ngx_http_perl_ctx_t  *ctx;

    ngx_http_perl_set_request(r);

    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);
    if (ctx->filename.data) {
        goto done;
    }

    if (ngx_http_map_uri_to_path(r, &ctx->filename, &root, 0) == NULL) {
        XSRETURN_UNDEF;
    }

    ctx->filename.len--;
    sv_setpv(PL_statname, (char *) ctx->filename.data);

    done:

    ngx_http_perl_set_targ(ctx->filename.data, ctx->filename.len);

    ST(0) = TARG;


void
print(r, ...)
    CODE:

    ngx_http_request_t  *r;
    SV                  *sv;
    int                  i;
    u_char              *p;
    size_t               size;
    STRLEN               len;
    ngx_buf_t           *b;

    ngx_http_perl_set_request(r);

    if (items == 2) {

        /*
         * do zero copy for prolate single read-only SV:
         *     $r->print("some text\n");
         */

        sv = ST(1);

        if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
            sv = SvRV(sv);
        }

        if (SvREADONLY(sv) && SvPOK(sv)) {

            p = (u_char *) SvPV(sv, len);

            if (len == 0) {
                XSRETURN_EMPTY;
            }

            b = ngx_calloc_buf(r->pool);
            if (b == NULL) {
                XSRETURN_EMPTY;
            }

            b->memory = 1;
            b->pos = p;
            b->last = p + len;
            b->start = p;
            b->end = b->last;

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                           "$r->print: read-only SV: %z", len);

            goto out;
        }
    }

    size = 0;

    for (i = 1; i < items; i++) {

        sv = ST(i);

        if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
            sv = SvRV(sv);
        }

        (void) SvPV(sv, len);

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "$r->print: copy SV: %z", len);

        size += len;
    }

    if (size == 0) {
        XSRETURN_EMPTY;
    }

    b = ngx_create_temp_buf(r->pool, size);
    if (b == NULL) {
        XSRETURN_EMPTY;
    }

    for (i = 1; i < items; i++) {
        sv = ST(i);

        if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PV) {
            sv = SvRV(sv);
        }

        p = (u_char *) SvPV(sv, len);
        b->last = ngx_cpymem(b->last, p, len);
    }

    out:

    (void) ngx_http_perl_output(r, b);


void
sendfile(r, filename, offset = -1, bytes = 0)
    CODE:

    ngx_http_request_t        *r;
    char                      *filename;
    off_t                      offset;
    size_t                     bytes;
    ngx_str_t                  path;
    ngx_buf_t                 *b;
    ngx_open_file_info_t       of;
    ngx_http_core_loc_conf_t  *clcf;

    ngx_http_perl_set_request(r);

    filename = SvPV_nolen(ST(1));

    if (filename == NULL) {
        croak("sendfile(): NULL filename");
    }

    offset = items < 3 ? -1 : SvIV(ST(2));
    bytes = items < 4 ? 0 : SvIV(ST(3));

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        XSRETURN_EMPTY;
    }

    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
    if (b->file == NULL) {
        XSRETURN_EMPTY;
    }

    path.len = ngx_strlen(filename);

    path.data = ngx_pnalloc(r->pool, path.len + 1);
    if (path.data == NULL) {
        XSRETURN_EMPTY;
    }

    (void) ngx_cpystrn(path.data, (u_char *) filename, path.len + 1);

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    ngx_memzero(&of, sizeof(ngx_open_file_info_t));

    of.read_ahead = clcf->read_ahead;
    of.directio = clcf->directio;
    of.valid = clcf->open_file_cache_valid;
    of.min_uses = clcf->open_file_cache_min_uses;
    of.errors = clcf->open_file_cache_errors;
    of.events = clcf->open_file_cache_events;

    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
        XSRETURN_EMPTY;
    }

    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
        != NGX_OK)
    {
        if (of.err == 0) {
            XSRETURN_EMPTY;
        }

        ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
                      "%s \"%s\" failed", of.failed, filename);
        XSRETURN_EMPTY;
    }

    if (offset == -1) {
        offset = 0;
    }

    if (bytes == 0) {
        bytes = of.size - offset;
    }

    b->in_file = 1;

    b->file_pos = offset;
    b->file_last = offset + bytes;

    b->file->fd = of.fd;
    b->file->log = r->connection->log;
    b->file->directio = of.is_directio;

    (void) ngx_http_perl_output(r, b);


void
flush(r)
    CODE:

    ngx_http_request_t  *r;
    ngx_buf_t           *b;

    ngx_http_perl_set_request(r);

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        XSRETURN_EMPTY;
    }

    b->flush = 1;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "$r->flush");

    (void) ngx_http_perl_output(r, b);

    XSRETURN_EMPTY;


void
internal_redirect(r, uri)
    CODE:

    ngx_http_request_t   *r;
    SV                   *uri;
    ngx_uint_t            i;
    ngx_http_perl_ctx_t  *ctx;

    ngx_http_perl_set_request(r);

    uri = ST(1);

    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);

    if (ngx_http_perl_sv2str(aTHX_ r, &ctx->redirect_uri, uri) != NGX_OK) {
        XSRETURN_EMPTY;
    }

    for (i = 0; i < ctx->redirect_uri.len; i++) {
        if (ctx->redirect_uri.data[i] == '?') {

            ctx->redirect_args.len = ctx->redirect_uri.len - (i + 1);
            ctx->redirect_args.data = &ctx->redirect_uri.data[i + 1];
            ctx->redirect_uri.len = i;

            XSRETURN_EMPTY;
        }
    }


void
allow_ranges(r)
    CODE:

    ngx_http_request_t  *r;

    ngx_http_perl_set_request(r);

    r->allow_ranges = 1;


void
unescape(r, text, type = 0)
    CODE:

    dXSTARG;
    ngx_http_request_t  *r;
    SV                  *text;
    int                  type;
    u_char              *p, *dst, *src;
    STRLEN               len;

    ngx_http_perl_set_request(r);

    text = ST(1);

    src = (u_char *) SvPV(text, len);

    p = ngx_pnalloc(r->pool, len + 1);
    if (p == NULL) {
        XSRETURN_UNDEF;
    }

    dst = p;

    type = items < 3 ? 0 : SvIV(ST(2));

    ngx_unescape_uri(&dst, &src, len, (ngx_uint_t) type);
    *dst = '\0';

    ngx_http_perl_set_targ(p, dst - p);

    ST(0) = TARG;


void
variable(r, name, value = NULL)
    CODE:

    dXSTARG;
    ngx_http_request_t         *r;
    SV                         *name, *value;
    u_char                     *p, *lowcase;
    STRLEN                      len;
    ngx_str_t                   var, val;
    ngx_uint_t                  i, hash;
    ngx_http_perl_var_t        *v;
    ngx_http_perl_ctx_t        *ctx;
    ngx_http_variable_value_t  *vv;

    ngx_http_perl_set_request(r);

    name = ST(1);

    if (SvROK(name) && SvTYPE(SvRV(name)) == SVt_PV) {
        name = SvRV(name);
    }

    if (items == 2) {
        value = NULL;

    } else {
        value = ST(2);

        if (SvROK(value) && SvTYPE(SvRV(value)) == SVt_PV) {
            value = SvRV(value);
        }

        if (ngx_http_perl_sv2str(aTHX_ r, &val, value) != NGX_OK) {
            XSRETURN_UNDEF;
        }
    }

    p = (u_char *) SvPV(name, len);

    lowcase = ngx_pnalloc(r->pool, len);
    if (lowcase == NULL) {
        XSRETURN_UNDEF;
    }

    hash = ngx_hash_strlow(lowcase, p, len);

    var.len = len;
    var.data = lowcase;
#if (NGX_DEBUG)

    if (value) {
        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "perl variable: \"%V\"=\"%V\"", &var, &val);
    } else {
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "perl variable: \"%V\"", &var);
    }
#endif

    vv = ngx_http_get_variable(r, &var, hash);
    if (vv == NULL) {
        XSRETURN_UNDEF;
    }

    if (vv->not_found) {

        ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);

        if (ctx->variables) {

            v = ctx->variables->elts;
            for (i = 0; i < ctx->variables->nelts; i++) {

                if (hash != v[i].hash
                    || len != v[i].name.len
                    || ngx_strncmp(lowcase, v[i].name.data, len) != 0)
                {
                    continue;
                }

                if (value) {
                    v[i].value = val;
                    XSRETURN_UNDEF;
                }

                ngx_http_perl_set_targ(v[i].value.data, v[i].value.len);

                goto done;
            }
        }

        if (value) {
            if (ctx->variables == NULL) {
                ctx->variables = ngx_array_create(r->pool, 1,
                                                  sizeof(ngx_http_perl_var_t));
                if (ctx->variables == NULL) {
                    XSRETURN_UNDEF;
                }
            }

            v = ngx_array_push(ctx->variables);
            if (v == NULL) {
                XSRETURN_UNDEF;
            }

            v->hash = hash;
            v->name.len = len;
            v->name.data = lowcase;
            v->value = val;

            XSRETURN_UNDEF;
        }

        XSRETURN_UNDEF;
    }

    if (value) {
        vv->len = val.len;
        vv->valid = 1;
        vv->no_cacheable = 0;
        vv->not_found = 0;
        vv->data = val.data;

        XSRETURN_UNDEF;
    }

    ngx_http_perl_set_targ(vv->data, vv->len);

    done:

    ST(0) = TARG;


void
sleep(r, sleep, next)
    CODE:

    ngx_http_request_t   *r;
    ngx_msec_t            sleep;
    ngx_http_perl_ctx_t  *ctx;

    ngx_http_perl_set_request(r);

    sleep = (ngx_msec_t) SvIV(ST(1));

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "perl sleep: %M", sleep);

    ctx = ngx_http_get_module_ctx(r, ngx_http_perl_module);

    ctx->next = SvRV(ST(2));

    ngx_add_timer(r->connection->write, sleep);

    r->write_event_handler = ngx_http_perl_sleep_handler;
    r->main->count++;


void
log_error(r, err, msg)
    CODE:

    ngx_http_request_t  *r;
    SV                  *err, *msg;
    u_char              *p;
    STRLEN               len;
    ngx_err_t            e;

    ngx_http_perl_set_request(r);

    err = ST(1);

    if (SvROK(err) && SvTYPE(SvRV(err)) == SVt_PV) {
        err = SvRV(err);
    }

    e = SvIV(err);

    msg = ST(2);

    if (SvROK(msg) && SvTYPE(SvRV(msg)) == SVt_PV) {
        msg = SvRV(msg);
    }

    p = (u_char *) SvPV(msg, len);

    ngx_log_error(NGX_LOG_ERR, r->connection->log, e, "perl: %s", p);