static apr_status_t do_pattmatch()

in modules/filters/mod_substitute.c [134:411]


static apr_status_t do_pattmatch(ap_filter_t *f, apr_bucket *inb,
                                 apr_bucket_brigade *mybb,
                                 apr_pool_t *pool)
{
    int i;
    int force_quick = 0;
    ap_regmatch_t regm[AP_MAX_REG_MATCH];
    apr_size_t bytes;
    apr_size_t len;
    const char *buff;
    struct ap_varbuf vb;
    apr_bucket *b;
    apr_bucket *tmp_b;
    subst_pattern_t *script;

    subst_dir_conf *cfg =
    (subst_dir_conf *) ap_get_module_config(f->r->per_dir_config,
                                             &substitute_module);
    subst_req_t *rconf = 
    (subst_req_t*) ap_get_module_config(f->r->request_config, 
                                        &substitute_module);

    APR_BRIGADE_INSERT_TAIL(mybb, inb);
    ap_varbuf_init(pool, &vb, 0);

    script = (subst_pattern_t *) cfg->patterns->elts;
    /*
     * Simple optimization. If we only have one pattern, then
     * we can safely avoid the overhead of flattening
     */
    if (cfg->patterns->nelts == 1) {
       force_quick = 1;
    }
    for (i = 0; i < cfg->patterns->nelts; i++) {
        const char *replacement = script->replacement;
        apr_size_t replen = script->replen;
        if (script->expr_replacement) { 
            if (!rconf) { 
                rconf = apr_pcalloc(f->r->pool, sizeof(*rconf));
                rconf->expcache     = apr_pcalloc(f->r->pool, sizeof(const char*) * cfg->patterns->nelts);
                rconf->expcache_len = apr_pcalloc(f->r->pool, sizeof(int) * cfg->patterns->nelts);
                ap_set_module_config(f->r->request_config, &substitute_module, rconf);
            }
        }
        for (b = APR_BRIGADE_FIRST(mybb);
             b != APR_BRIGADE_SENTINEL(mybb);
             b = APR_BUCKET_NEXT(b)) {
            if (APR_BUCKET_IS_METADATA(b)) {
                /*
                 * we should NEVER see this, because we should never
                 * be passed any, but "handle" it just in case.
                 */
                continue;
            }
            if (apr_bucket_read(b, &buff, &bytes, APR_BLOCK_READ)
                    == APR_SUCCESS) {
                int have_match = 0;

                ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                              "Line read (%" APR_SIZE_T_FMT " bytes): %.*s",
                              bytes, CAP2LINEMAX(bytes), buff);
                ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                              "Replacing %s:'%s' by '%s'",
                              script->pattern ? "string" :
                              script->regexp  ? "regex"  :
                                                "unknown",
                              script->from, script->replacement);

                vb.strlen = 0;
                if (script->pattern) {
                    const char *repl;
                    /*
                     * space_left counts how many bytes we have left until the
                     * line length reaches max_line_length.
                     */
                    apr_size_t space_left = cfg->max_line_length;
                    while ((repl = apr_strmatch(script->pattern, buff, bytes)))
                    {
                        
                        if (!have_match && script->expr_replacement) { 
                            if (!rconf->expcache[i]) {
                                const char *err = NULL;
                                rconf->expcache[i] = ap_expr_str_exec(f->r, script->expr_replacement, &err);
                                if (err) { 
                                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "error evaluating expression: %s", err);
                                    return APR_EINVAL;
                                }
                                rconf->expcache_len[i] = strlen(rconf->expcache[i]);
                            }
                            replacement = rconf->expcache[i];
                            replen      = rconf->expcache_len[i];
                        }

                        ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                                      "Matching found, result: '%s'",
                                      replacement);
                        have_match = 1;
                        /* get offset into buff for pattern */
                        len = (apr_size_t) (repl - buff);
                        if (script->flatten && !force_quick) {
                            /*
                             * We are flattening the buckets here, meaning
                             * that we don't do the fast bucket splits.
                             * Instead we copy over what the buckets would
                             * contain and use them. This is slow, since we
                             * are constanting allocing space and copying
                             * strings.
                             */
                            if (vb.strlen + len + replen > cfg->max_line_length)
                                return APR_ENOMEM;
                            ap_varbuf_strmemcat(&vb, buff, len);
                            ap_varbuf_strmemcat(&vb, replacement, replen);
                        }
                        else {
                            /*
                             * The string before the match but after the
                             * previous match (if any) has length 'len'.
                             * Check if we still have space for this string and
                             * the replacement string.
                             */
                            if (space_left < len + replen)
                                return APR_ENOMEM;
                            space_left -= len + replen;
                            /*
                             * We now split off the string before the match
                             * as its own bucket, then isolate the matched
                             * string and delete it.
                             */
                            SEDRMPATBCKT(b, len, tmp_b, script->patlen);
                            /*
                             * Finally, we create a bucket that contains the
                             * replacement...
                             */
                            tmp_b = apr_bucket_transient_create(replacement,
                                      replen,
                                      f->r->connection->bucket_alloc);
                            /* ... and insert it */
                            APR_BUCKET_INSERT_BEFORE(b, tmp_b);
                        }
                        /* now we need to adjust buff for all these changes */
                        len += script->patlen;
                        bytes -= len;
                        buff += len;
                    }
                    if (have_match) {
                        if (script->flatten && !force_quick) {
                            /* XXX: we should check for AP_MAX_BUCKETS here and
                             * XXX: call ap_pass_brigade accordingly
                             */
                            char *copy = ap_varbuf_pdup(pool, &vb, NULL, 0,
                                                        buff, bytes, &len);
                            ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                                          "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
                                          len, CAP2LINEMAX(len), copy);
                            tmp_b = apr_bucket_pool_create(copy, len, pool,
                                                           f->r->connection->bucket_alloc);
                            APR_BUCKET_INSERT_BEFORE(b, tmp_b);
                            apr_bucket_delete(b);
                            b = tmp_b;
                        }
                        else {
                            /*
                             * We want the behaviour to be predictable.
                             * Therefore we try to always error out if the
                             * line length is larger than the limit,
                             * regardless of the content of the line. So,
                             * let's check if the remaining non-matching
                             * string does not exceed the limit.
                             */
                            if (space_left < b->length)
                                return APR_ENOMEM;
                            ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                                          "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
                                          bytes, CAP2LINEMAX(bytes), buff);
                        }
                    }
                }
                else if (script->regexp) {
                    apr_size_t left = bytes;
                    const char *pos = buff;
                    char *repl;
                    apr_size_t space_left = cfg->max_line_length;
                    while (!ap_regexec_len(script->regexp, pos, left,
                                       AP_MAX_REG_MATCH, regm, 0)) {
                        apr_status_t rv;
                        if (!have_match && script->expr_replacement) {
                            if (!rconf->expcache[i]) {
                                const char *err = NULL;
                                rconf->expcache[i] = ap_expr_str_exec(f->r, script->expr_replacement, &err);
                                if (err) {
                                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "error evaluating expression: %s", err);
                                    return APR_EGENERAL;
                                }
                                rconf->expcache_len[i] = strlen(rconf->expcache[i]);
                            }
                            replacement = rconf->expcache[i];
                            replen      = rconf->expcache_len[i];
                        }

                        ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                                      "Matching found");
                        have_match = 1;
                        if (script->flatten && !force_quick) {
                            /* check remaining buffer size */
                            /* Note that the last param in ap_varbuf_regsub below
                             * must stay positive. If it gets 0, it would mean
                             * unlimited space available. */
                            if (vb.strlen + regm[0].rm_so >= cfg->max_line_length)
                                return APR_ENOMEM;
                            /* copy bytes before the match */
                            if (regm[0].rm_so > 0)
                                ap_varbuf_strmemcat(&vb, pos, regm[0].rm_so);
                            /* add replacement string, last argument is unsigned! */
                            rv = ap_varbuf_regsub(&vb, replacement, pos,
                                                  AP_MAX_REG_MATCH, regm,
                                                  cfg->max_line_length - vb.strlen);
                            if (rv != APR_SUCCESS)
                                return rv;
                            ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                                          "Result: '%s'", vb.buf);
                        }
                        else {
                            apr_size_t repl_len;
                            /* account for string before the match */
                            if (space_left <= regm[0].rm_so)
                                return APR_ENOMEM;
                            space_left -= regm[0].rm_so;
                            rv = ap_pregsub_ex(pool, &repl,
                                               replacement, pos,
                                               AP_MAX_REG_MATCH, regm,
                                               space_left);
                            if (rv != APR_SUCCESS)
                                return rv;
                            repl_len = strlen(repl);
                            space_left -= repl_len;
                            len = (apr_size_t) (regm[0].rm_eo - regm[0].rm_so);
                            SEDRMPATBCKT(b, regm[0].rm_so, tmp_b, len);
                            tmp_b = apr_bucket_transient_create(repl, repl_len,
                                                f->r->connection->bucket_alloc);
                            APR_BUCKET_INSERT_BEFORE(b, tmp_b);
                            ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                                          "Result: '%s'", repl);
                        }
                        /*
                         * reset to past what we just did. pos now maps to b
                         * again
                         */
                        pos += regm[0].rm_eo;
                        left -= regm[0].rm_eo;
                    }
                    if (have_match && script->flatten && !force_quick) {
                        char *copy;
                        /* Copy result plus the part after the last match into
                         * a bucket.
                         */
                        copy = ap_varbuf_pdup(pool, &vb, NULL, 0, pos, left,
                                              &len);
                        ap_log_rerror(APLOG_MARK, APLOG_TRACE8, 0, f->r,
                                      "New line (%" APR_SIZE_T_FMT " bytes): %.*s",
                                      len, CAP2LINEMAX(len), copy);
                        tmp_b = apr_bucket_pool_create(copy, len, pool,
                                           f->r->connection->bucket_alloc);
                        APR_BUCKET_INSERT_BEFORE(b, tmp_b);
                        apr_bucket_delete(b);
                        b = tmp_b;
                    }
                }
                else {
                    ap_assert(0);
                    continue;
                }
            }
        }
        script++;
    }
    ap_varbuf_free(&vb);
    return APR_SUCCESS;
}