mod_negotiation.c 109 KB
Newer Older
powelld's avatar
powelld committed
         * ap_update_mtime(r, r->finfo.mtime);
         * ap_set_last_modified(r);
         * ap_set_etag(r);
         */
        ap_set_accept_ranges(r);
        ap_set_content_length(r, best->bytes);

        /* set MIME type and charset as negotiated */
        if (best->mime_type && *best->mime_type) {
            if (best->content_charset && *best->content_charset) {
                ap_set_content_type(r, apr_pstrcat(r->pool,
                                                   best->mime_type,
                                                   "; charset=",
                                                   best->content_charset,
                                                   NULL));
            }
            else {
                ap_set_content_type(r, apr_pstrdup(r->pool, best->mime_type));
            }
        }

        /* set Content-language(s) as negotiated */
        if (best->content_languages && best->content_languages->nelts) {
            r->content_languages = apr_array_copy(r->pool,
                                                  best->content_languages);
        }

        /* set Content-Encoding as negotiated */
        if (best->content_encoding && *best->content_encoding) {
            r->content_encoding = apr_pstrdup(r->pool,
                                              best->content_encoding);
        }

        if ((res = ap_meets_conditions(r)) != OK) {
            return res;
        }

        if ((res = ap_discard_request_body(r)) != OK) {
            return res;
        }
        bb = apr_brigade_create(r->pool, c->bucket_alloc);

        apr_brigade_insert_file(bb, map, best->body, best->bytes, r->pool);

        e = apr_bucket_eos_create(c->bucket_alloc);
        APR_BRIGADE_INSERT_TAIL(bb, e);

        return ap_pass_brigade_fchk(r, bb, NULL);
    }

    if (r->path_info && *r->path_info) {
        /* remove any path_info from the end of the uri before trying
         * to change the filename.  r->path_info from the original
         * request is passed along on the redirect.
         */
        r->uri[ap_find_path_info(r->uri, r->path_info)] = '\0';
    }
    udir = ap_make_dirstr_parent(r->pool, r->uri);
    udir = ap_escape_uri(r->pool, udir);
    if (r->args) {
        if (r->path_info) {
            new_req = apr_pstrcat(r->pool, udir, best->file_name,
                                  r->path_info, "?", r->args, NULL);
        }
        else {
            new_req = apr_pstrcat(r->pool, udir, best->file_name,
                                  "?", r->args, NULL);
        }
    }
    else {
        new_req = apr_pstrcat(r->pool, udir, best->file_name,
                              r->path_info, NULL);
    }
    ap_internal_redirect(new_req, r);
    return OK;
}

static int handle_multi(request_rec *r)
{
    negotiation_state *neg;
    var_rec *best, *avail_recs;
    request_rec *sub_req;
    int res;
    int j;

    if (r->finfo.filetype != APR_NOFILE
        || !(ap_allow_options(r) & OPT_MULTI)) {
        return DECLINED;
    }

    neg = parse_accept_headers(r);

    if ((res = read_types_multi(neg))) {
      return_from_multi:
        /* free all allocated memory from subrequests */
        avail_recs = (var_rec *) neg->avail_vars->elts;
        for (j = 0; j < neg->avail_vars->nelts; ++j) {
            var_rec *variant = &avail_recs[j];
            if (variant->sub_req) {
                ap_destroy_sub_req(variant->sub_req);
            }
        }
        return res;
    }
    if (neg->avail_vars->nelts == 0) {
        return DECLINED;
    }

    res = do_negotiation(r, neg, &best,
                         (r->method_number != M_GET) || r->args ||
                         (r->path_info && *r->path_info));
    if (res != 0)
        goto return_from_multi;

    if (!(sub_req = best->sub_req)) {
        /* We got this out of a map file, so we don't actually have
         * a sub_req structure yet.  Get one now.
         */

        sub_req = ap_sub_req_lookup_file(best->file_name, r, r->output_filters);
        if (sub_req->status != HTTP_OK) {
            res = sub_req->status;
            ap_destroy_sub_req(sub_req);
            goto return_from_multi;
        }
    }
    if (sub_req->args == NULL) {
        sub_req->args = r->args;
    }

    /* now do a "fast redirect" ... promotes the sub_req into the main req */
    ap_internal_fast_redirect(sub_req, r);

    /* give no advise for time on this subrequest.  Perhaps we
     * should tally the last mtime among all variants, and date
     * the most recent, but that could confuse the proxies.
     */
    r->mtime = 0;

    /* clean up all but our favorite variant, since that sub_req
     * is now merged into the main request!
     */
    avail_recs = (var_rec *) neg->avail_vars->elts;
    for (j = 0; j < neg->avail_vars->nelts; ++j) {
        var_rec *variant = &avail_recs[j];
        if (variant != best && variant->sub_req) {
            ap_destroy_sub_req(variant->sub_req);
        }
    }
    return OK;
}

/**********************************************************************
 * There is a problem with content-encoding, as some clients send and
 * expect an x- token (e.g. x-gzip) while others expect the plain token
 * (i.e. gzip). To try and deal with this as best as possible we do
 * the following: if the client sent an Accept-Encoding header and it
 * contains a plain token corresponding to the content encoding of the
 * response, then set content encoding using the plain token. Else if
 * the A-E header contains the x- token use the x- token in the C-E
 * header. Else don't do anything.
 *
 * Note that if no A-E header was sent, or it does not contain a token
 * compatible with the final content encoding, then the token in the
 * C-E header will be whatever was specified in the AddEncoding
 * directive.
 */
static int fix_encoding(request_rec *r)
{
    const char *enc = r->content_encoding;
    char *x_enc = NULL;
    apr_array_header_t *accept_encodings;
    accept_rec *accept_recs;
    int i;

    if (!enc || !*enc) {
        return DECLINED;
    }

    if (enc[0] == 'x' && enc[1] == '-') {
        enc += 2;
    }

    if ((accept_encodings = do_header_line(r->pool,
             apr_table_get(r->headers_in, "Accept-Encoding"))) == NULL) {
        return DECLINED;
    }

    accept_recs = (accept_rec *) accept_encodings->elts;

    for (i = 0; i < accept_encodings->nelts; ++i) {
        char *name = accept_recs[i].name;

        if (!strcmp(name, enc)) {
            r->content_encoding = name;
            return OK;
        }

        if (name[0] == 'x' && name[1] == '-' && !strcmp(name+2, enc)) {
            x_enc = name;
        }
    }

    if (x_enc) {
        r->content_encoding = x_enc;
        return OK;
    }

    return DECLINED;
}

static void register_hooks(apr_pool_t *p)
{
    ap_hook_fixups(fix_encoding,NULL,NULL,APR_HOOK_MIDDLE);
    ap_hook_type_checker(handle_multi,NULL,NULL,APR_HOOK_FIRST);
    ap_hook_handler(handle_map_file,NULL,NULL,APR_HOOK_MIDDLE);
}

AP_DECLARE_MODULE(negotiation) =
{
    STANDARD20_MODULE_STUFF,
    create_neg_dir_config,      /* dir config creator */
    merge_neg_dir_configs,      /* dir merger --- default is to override */
    NULL,                       /* server config */
    NULL,                       /* merge server config */
    negotiation_cmds,           /* command apr_table_t */
    register_hooks              /* register hooks */
};