home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 8 / CDACTUAL8.iso / share / os2 / varios / apache / mod_nego.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-07-08  |  31.9 KB  |  1,124 lines

  1.  
  2. /* ====================================================================
  3.  * Copyright (c) 1995 The Apache Group.  All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  * 1. Redistributions of source code must retain the above copyright
  10.  *    notice, this list of conditions and the following disclaimer. 
  11.  *
  12.  * 2. Redistributions in binary form must reproduce the above copyright
  13.  *    notice, this list of conditions and the following disclaimer in
  14.  *    the documentation and/or other materials provided with the
  15.  *    distribution.
  16.  *
  17.  * 3. All advertising materials mentioning features or use of this
  18.  *    software must display the following acknowledgment:
  19.  *    "This product includes software developed by the Apache Group
  20.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  21.  *
  22.  * 4. The names "Apache Server" and "Apache Group" must not be used to
  23.  *    endorse or promote products derived from this software without
  24.  *    prior written permission.
  25.  *
  26.  * 5. Redistributions of any form whatsoever must retain the following
  27.  *    acknowledgment:
  28.  *    "This product includes software developed by the Apache Group
  29.  *    for use in the Apache HTTP server project (http://www.apache.org/)."
  30.  *
  31.  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
  32.  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  33.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  34.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
  35.  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  39.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  42.  * OF THE POSSIBILITY OF SUCH DAMAGE.
  43.  * ====================================================================
  44.  *
  45.  * This software consists of voluntary contributions made by many
  46.  * individuals on behalf of the Apache Group and was originally based
  47.  * on public domain software written at the National Center for
  48.  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
  49.  * For more information on the Apache Group and the Apache HTTP server
  50.  * project, please see <http://www.apache.org/>.
  51.  *
  52.  */
  53.  
  54.  
  55. /*
  56.  * mod_negotiation.c: keeps track of MIME types the client is willing to
  57.  * accept, and contains code to handle type arbitration.
  58.  *
  59.  * rst
  60.  */
  61.  
  62. #include "httpd.h"
  63. #include "http_config.h"
  64. #include "http_request.h"
  65. #include "http_core.h"
  66. #include "http_log.h"
  67.  
  68. /* Commands --- configuring document caching on a per (virtual?)
  69.  * server basis...
  70.  */
  71.  
  72. typedef struct {
  73.     array_header *language_priority;
  74. } neg_dir_config;
  75.  
  76. module negotiation_module;
  77.  
  78. void *create_neg_dir_config (pool *p, char *dummy)
  79. {
  80.     neg_dir_config *new =
  81.       (neg_dir_config *) palloc (p, sizeof (neg_dir_config));
  82.  
  83.     new->language_priority = make_array (p, 4, sizeof (char *));
  84.     return new;
  85. }
  86.  
  87. void *merge_neg_dir_configs (pool *p, void *basev, void *addv)
  88. {
  89.     neg_dir_config *base = (neg_dir_config *)basev;
  90.     neg_dir_config *add = (neg_dir_config *)addv;
  91.     neg_dir_config *new =
  92.       (neg_dir_config *) palloc (p, sizeof (neg_dir_config));
  93.  
  94.     /* give priority to the config in the subdirectory */
  95.     new->language_priority = append_arrays (p, add->language_priority,
  96.                         base->language_priority);
  97.     return new;
  98. }
  99.  
  100. char *set_language_priority (cmd_parms *cmd, void *n, char *lang)
  101. {
  102.     array_header *arr = ((neg_dir_config *) n)->language_priority;
  103.     char **langp = (char **) push_array (arr);
  104.  
  105.     *langp = pstrdup (arr->pool, lang);
  106.     return NULL;
  107. }
  108.  
  109. char *cache_negotiated_docs (cmd_parms *cmd, void *dummy, char *dummy2)
  110. {
  111.     void *server_conf = cmd->server->module_config;
  112.     
  113.     set_module_config (server_conf, &negotiation_module, "Cache");
  114.     return NULL;
  115. }
  116.  
  117. int do_cache_negotiated_docs (server_rec *s)
  118. {
  119.     return (get_module_config (s->module_config, &negotiation_module) != NULL);
  120. }
  121.  
  122. command_rec negotiation_cmds[] = {
  123. { "CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF, RAW_ARGS,
  124.     NULL },
  125. { "LanguagePriority", set_language_priority, NULL, OR_FILEINFO, ITERATE,
  126.     NULL },
  127. { NULL }
  128. };
  129.  
  130. /*
  131.  * TO DO --- error code 406.  Unfortunately, the specification for
  132.  *           a 406 reply in the current draft standard is unworkable;
  133.  *           we return 404 for these pending a workable spec. 
  134.  */
  135.  
  136. /* Record of available info on a media type specified by the client
  137.  * (we also use 'em for encodings and languages)
  138.  */
  139.  
  140. typedef struct accept_rec {
  141.     char *type_name;
  142.     float quality;
  143.     float max_bytes;
  144.     float level;
  145. } accept_rec;
  146.  
  147. /* Record of available info on a particular variant
  148.  *
  149.  * Note that a few of these fields are updated by the actual negotiation
  150.  * code.  These are:
  151.  *
  152.  * quality --- initialized to the value of qs, and subsequently jiggered
  153.  *             to reflect the client's preferences.  In particular, it
  154.  *             gets zeroed out if the variant has an unacceptable content
  155.  *             encoding, or if it is in a language which the client
  156.  *             doesn't accept and some other variant *is* in a language
  157.  *             the client accepts.
  158.  *
  159.  * level_matched --- initialized to zero.  Set to the value of level
  160.  *             if the client actually accepts this media type at that
  161.  *             level (and *not* if it got in on a wildcard).  See level_cmp
  162.  *             below.
  163.  */
  164.  
  165. typedef struct var_rec {
  166.     request_rec *sub_req;    /* May be NULL (is, for map files) */
  167.     char *type_name;
  168.     char *file_name;
  169.     char *content_encoding;
  170.     char *content_language;
  171.     float level;        /* Auxiliary to content-type... */
  172.     float qs;
  173.     float bytes;
  174.     int lang_index;
  175.     int is_pseudo_html;        /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
  176.  
  177.     /* Above are all written-once properties of the variant.  The
  178.      * three fields below are changed during negotiation:
  179.      */
  180.     
  181.     float quality;    
  182.     float level_matched;
  183.     int mime_stars;
  184. } var_rec;
  185.  
  186. /* Something to carry around the state of negotiation (and to keep
  187.  * all of this thread-safe)...
  188.  */
  189.  
  190. typedef struct {
  191.     pool *pool;
  192.     request_rec *r;
  193.     char *dir_name;
  194.     
  195.     array_header *accepts;    /* accept_recs */
  196.     array_header *accept_encodings;    /* accept_recs */
  197.     array_header *accept_langs;    /* accept_recs */
  198.     array_header *avail_vars;    /* available variants */
  199. } negotiation_state;
  200.  
  201. /* A few functions to manipulate var_recs.
  202.  * Cleaning out the fields...
  203.  */
  204.  
  205. void clean_var_rec (var_rec *mime_info)
  206. {
  207.     mime_info->sub_req = NULL;
  208.     mime_info->type_name = "";
  209.     mime_info->file_name = "";
  210.     mime_info->content_encoding = "";
  211.     mime_info->content_language = "";
  212.  
  213.     mime_info->is_pseudo_html = 0;
  214.     mime_info->level = 0.0;
  215.     mime_info->level_matched = 0.0;
  216.     mime_info->qs = 0.0;
  217.     mime_info->quality = 0.0;
  218.     mime_info->bytes = 0;
  219.     mime_info->lang_index = -1;
  220.     mime_info->mime_stars = 0;
  221. }
  222.  
  223. /* Initializing the relevant fields of a variant record from the
  224.  * accept_info read out of its content-type, one way or another.
  225.  */
  226.  
  227. void set_mime_fields (var_rec *var, accept_rec *mime_info)
  228. {
  229.     var->type_name = mime_info->type_name;
  230.     var->qs = mime_info->quality;
  231.     var->quality = mime_info->quality; /* Initial quality is just qs */
  232.     var->level = mime_info->level;
  233.  
  234.     var->is_pseudo_html = 
  235.     (!strcmp (var->type_name, "text/html")
  236.      || !strcmp (var->type_name, INCLUDES_MAGIC_TYPE)
  237.      || !strcmp (var->type_name, INCLUDES_MAGIC_TYPE3));
  238. }
  239.  
  240. /*****************************************************************
  241.  *
  242.  * Parsing (lists of) media types and their parameters, as seen in
  243.  * HTTPD header lines and elsewhere.
  244.  */
  245.  
  246. /*
  247.  * Get a single mime type entry --- one media type and parameters;
  248.  * enter the values we recognize into the argument accept_rec
  249.  */
  250.  
  251. char *get_entry (pool *p, accept_rec *result, char *accept_line)
  252. {
  253.     result->quality = 1.0;
  254.     result->max_bytes = 0.0;
  255.     result->level = 0.0;
  256.     
  257.     /* Note that this handles what I gather is the "old format",
  258.      *
  259.      *    Accept: text/html text/plain moo/zot
  260.      *
  261.      * without any compatibility kludges --- if the token after the
  262.      * MIME type begins with a semicolon, we know we're looking at parms,
  263.      * otherwise, we know we aren't.  (So why all the pissing and moaning
  264.      * in the CERN server code?  I must be missing something).
  265.      */
  266.     
  267.     result->type_name = get_token (p, &accept_line, 0);
  268.     str_tolower (result->type_name); /* You want case-insensitive,
  269.                       * you'll *get* case-insensitive.
  270.                       */
  271.     
  272.  
  273.     /* KLUDGE!!! Default HTML to level 2.0 unless the browser
  274.      * *explicitly* says something else.
  275.      */
  276.     
  277.     if (!strcmp (result->type_name, "text/html")
  278.     && result->level == 0.0)
  279.     result->level = 2.0;
  280.     else if (!strcmp (result->type_name, INCLUDES_MAGIC_TYPE))
  281.     result->level = 2.0;
  282.     else if (!strcmp (result->type_name, INCLUDES_MAGIC_TYPE3))
  283.     result->level = 3.0;
  284.  
  285.     while (*accept_line == ';') {
  286.     /* Parameters ... */
  287.  
  288.     char *parm;
  289.     char *cp;
  290.         
  291.     ++accept_line;
  292.     parm = get_token (p, &accept_line, 1);
  293.  
  294.     /* Look for 'var = value' --- and make sure the var is in lcase. */
  295.     
  296.     for (cp = parm; *cp && !isspace(*cp) && *cp != '='; ++cp)
  297.         *cp = tolower(*cp);
  298.  
  299.     if (!*cp) continue;    /* No '='; just ignore it. */
  300.         
  301.     *cp++ = '\0';        /* Delimit var */
  302.     while (*cp && (isspace(*cp) || *cp == '='))
  303.         ++cp;
  304.  
  305.     if (*cp == '"') ++cp;
  306.     
  307.     if (parm[0] == 'q'
  308.         && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
  309.         result->quality = atof(cp);
  310.     else if (parm[0] == 'm' && parm[1] == 'x' &&
  311.          parm[2] == 'b' && parm[3] == '\0')
  312.         result->max_bytes = atof(cp);
  313.     else if (parm[0] == 'l' && !strcmp (&parm[1], "evel"))
  314.         result->level = atof(cp);
  315.     }
  316.  
  317.     if (*accept_line == ',') ++accept_line;
  318.  
  319.     return accept_line;
  320. }
  321.          
  322.  
  323. /*****************************************************************
  324.  *
  325.  * Dealing with header lines ...
  326.  */
  327.  
  328. array_header *do_header_line (pool *p, char *accept_line)
  329. {
  330.     array_header *accept_recs = make_array (p, 40, sizeof (accept_rec));
  331.   
  332.     if (!accept_line) return accept_recs;
  333.     
  334.     while (*accept_line) {
  335.         accept_rec *new = (accept_rec *)push_array (accept_recs);
  336.     accept_line = get_entry (p, new, accept_line);
  337.     }
  338.  
  339.     return accept_recs;
  340. }
  341.  
  342. /*****************************************************************
  343.  *
  344.  * Handling header lines from clients...
  345.  */
  346.  
  347. negotiation_state *parse_accept_headers (request_rec *r)
  348. {
  349.     negotiation_state *new =
  350.         (negotiation_state *)palloc (r->pool, sizeof (negotiation_state));
  351.     table *hdrs = r->headers_in;
  352.  
  353.     new->pool = r->pool;
  354.     new->r = r;
  355.     new->dir_name = make_dirstr(r->pool, r->filename, count_dirs(r->filename));
  356.     
  357.     new->accepts = do_header_line (r->pool, table_get (hdrs, "Accept"));
  358.     new->accept_encodings =
  359.       do_header_line (r->pool, table_get (hdrs, "Accept-encoding"));
  360.     new->accept_langs =
  361.       do_header_line (r->pool, table_get (hdrs, "Accept-language"));
  362.     new->avail_vars = make_array (r->pool, 40, sizeof (var_rec));
  363.  
  364.     return new;
  365. }
  366.  
  367. /* Sometimes clients will give us no Accept info at all; this routine sets
  368.  * up the standard default for that case, and also arranges for us to be
  369.  * willing to run a CGI script if we find one.  (In fact, we set up to
  370.  * dramatically prefer CGI scripts in cases where that's appropriate,
  371.  * e.g., POST).
  372.  */
  373.  
  374. void maybe_add_default_encodings(negotiation_state *neg, int prefer_scripts)
  375. {
  376.     accept_rec *new_accept = (accept_rec *)push_array (neg->accepts); 
  377.   
  378.     new_accept->type_name = CGI_MAGIC_TYPE;
  379.     new_accept->quality = prefer_scripts ? 1e-20 : 1e20;
  380.     new_accept->level = 0.0;
  381.     new_accept->max_bytes = 0.0;
  382.  
  383.     if (neg->accepts->nelts > 1) return;
  384.     
  385.     new_accept = (accept_rec *)push_array (neg->accepts); 
  386.     
  387.     new_accept->type_name = "*/*";
  388.     new_accept->quality = 1.0;
  389.     new_accept->level = 0.0;
  390.     new_accept->max_bytes = 0.0;
  391. }
  392.  
  393. /*****************************************************************
  394.  *
  395.  * Parsing type-map files, in Roy's meta/http format augmented with
  396.  * #-comments.
  397.  */
  398.  
  399. /* Reading RFC822-style header lines, ignoring #-comments and
  400.  * handling continuations.
  401.  */
  402.  
  403. enum header_state { header_eof, header_seen, header_sep };
  404.  
  405. enum header_state get_header_line (char *buffer, int len, FILE *map)
  406. {
  407.     char *buf_end = buffer + len;
  408.     char *cp;
  409.     int c;
  410.     
  411.     /* Get a noncommented line */
  412.     
  413.     do {
  414.     if (fgets(buffer, MAX_STRING_LEN, map) == NULL)
  415.         return header_eof;
  416.     } while (buffer[0] == '#');
  417.     
  418.     /* If blank, just return it --- this ends information on this variant */
  419.     
  420.     for (cp = buffer; *cp && isspace (*cp); ++cp)
  421.       continue;
  422.  
  423.     if (*cp == '\0') return header_sep;
  424.  
  425.     /* If non-blank, go looking for header lines, but note that we still
  426.      * have to treat comments specially...
  427.      */
  428.  
  429.     cp += strlen(cp);
  430.     
  431.     while ((c = getc(map)) != EOF)
  432.     {
  433.     if (c == '#') {
  434.         /* Comment line */
  435.         while ((c = getc(map)) != EOF && c != '\n')
  436.            continue;
  437.     } else if (isspace(c)) {
  438.         /* Leading whitespace.  POSSIBLE continuation line
  439.          * Also, possibly blank --- if so, we ungetc() the final newline
  440.          * so that we will pick up the blank line the next time 'round.
  441.          */
  442.         
  443.         while (c != EOF && c != '\n' && isspace(c))
  444.             c = getc(map);
  445.  
  446.         ungetc (c, map);
  447.         
  448.         if (c == '\n') return header_seen; /* Blank line */
  449.  
  450.         /* Continuation */
  451.  
  452.         while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n')
  453.             *cp++ = c;
  454.  
  455.         *cp++ = '\n';
  456.         *cp = '\0';
  457.     } else {
  458.  
  459.         /* Line beginning with something other than whitespace */
  460.         
  461.         ungetc (c, map);
  462.         return header_seen;
  463.     }
  464.     }
  465.  
  466.     return header_seen;
  467. }
  468.  
  469. /* Stripping out RFC822 comments */
  470.  
  471. void strip_paren_comments (char *hdr)
  472. {
  473.     /* Hmmm... is this correct?  In Roy's latest draft, (comments) can nest! */
  474.   
  475.     while (*hdr) {
  476.     if (*hdr == '"') {
  477.         while (*++hdr && *hdr != '"')
  478.         continue;
  479.         ++hdr;
  480.     }
  481.     else if (*hdr == '(') {
  482.         while (*hdr && *hdr != ')')    *hdr++ = ' ';
  483.         
  484.         if (*hdr) *hdr++ = ' ';
  485.     }
  486.     else ++hdr;
  487.     }
  488. }
  489.  
  490. /* Getting to a header body from the header */
  491.  
  492. char *lcase_header_name_return_body (char *header, request_rec *r)
  493. {
  494.     char *cp = header;
  495.     
  496.     while (*cp && *cp != ':')
  497.         *cp++ = tolower(*cp);
  498.     
  499.     if (!*cp) {
  500.     log_reason ("Syntax error in type map --- no ':'", r->filename, r);
  501.     return NULL;
  502.     }
  503.  
  504.     do ++cp; while (*cp && isspace (*cp));
  505.  
  506.     if (!*cp) {
  507.     log_reason ("Syntax error in type map --- no header body",
  508.             r->filename, r);
  509.     return NULL;
  510.     }
  511.  
  512.     return cp;
  513. }
  514.  
  515. int read_type_map (negotiation_state *neg, char *map_name)
  516. {
  517.     request_rec *r = neg->r;
  518.     FILE *map = pfopen (neg->pool, map_name, "r");
  519.  
  520.     char buffer[MAX_STRING_LEN];
  521.     enum header_state hstate;
  522.     struct var_rec mime_info;
  523.     
  524.     if (map == NULL) {
  525.         log_reason("cannot access type map file", map_name, r);
  526.     return FORBIDDEN;
  527.     }
  528.  
  529.     clean_var_rec (&mime_info);
  530.     
  531.     do {
  532.     hstate = get_header_line (buffer, MAX_STRING_LEN, map);
  533.     
  534.     if (hstate == header_seen) {
  535.         char *body = lcase_header_name_return_body (buffer, neg->r);
  536.         
  537.         if (body == NULL) return SERVER_ERROR;
  538.         
  539.         strip_paren_comments (body);
  540.         
  541.         if (!strncmp (buffer, "uri:", 4)) {
  542.             mime_info.file_name = get_token (neg->pool, &body, 0);
  543.         }
  544.         else if (!strncmp (buffer, "content-type:", 13)) {
  545.         struct accept_rec accept_info;
  546.         
  547.         get_entry (neg->pool, &accept_info, body);
  548.         set_mime_fields (&mime_info, &accept_info);
  549.         }
  550.         else if (!strncmp (buffer, "content-length:", 15)) {
  551.         mime_info.bytes = atoi(body);
  552.         }
  553.         else if (!strncmp (buffer, "content-language:", 17)) {
  554.         mime_info.content_language = get_token (neg->pool, &body, 0);
  555.         str_tolower (mime_info.content_language);
  556.         }
  557.         else if (!strncmp (buffer, "content-encoding:", 17)) {
  558.         mime_info.content_encoding = get_token (neg->pool, &body, 0);
  559.         str_tolower (mime_info.content_encoding);
  560.         }
  561.     } else {
  562.         if (mime_info.quality > 0) {
  563.             void *new_var = push_array (neg->avail_vars);
  564.         memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
  565.         }
  566.         
  567.         clean_var_rec(&mime_info);
  568.     }
  569.     } while (hstate != header_eof);
  570.     
  571.     pfclose (neg->pool, map);
  572.     return OK;
  573. }
  574.  
  575. /*****************************************************************
  576.  *
  577.  * Same, except we use a filtered directory listing as the map...
  578.  */
  579.  
  580. int read_types_multi (negotiation_state *neg)
  581. {
  582.     request_rec *r = neg->r;
  583.     
  584.     char *filp;
  585.     int prefix_len;
  586.     DIR *dirp;
  587.     struct DIR_TYPE *dir_entry;
  588.     struct var_rec mime_info;
  589.     struct accept_rec accept_info;
  590.     void *new_var;
  591.  
  592.     clean_var_rec (&mime_info);
  593.  
  594.     if (!(filp = strrchr (r->filename, '/'))) return DECLINED; /* Weird... */
  595.  
  596.     ++filp;
  597.     prefix_len = strlen (filp);
  598.  
  599.     dirp = opendir (neg->dir_name); /* Not pool protected; sigh... */
  600.  
  601.     if (dirp == NULL) {
  602.         log_reason("cannot read directory for multi", neg->dir_name, r);
  603.     return FORBIDDEN;
  604.     }
  605.  
  606.     while ((dir_entry = readdir (dirp))) {
  607.     
  608.         request_rec *sub_req;
  609.       
  610.     /* Do we have a match? */
  611.     
  612.     if (strncmp (dir_entry->d_name, filp, prefix_len)) continue;
  613.     if (dir_entry->d_name[prefix_len] != '.') continue;
  614.     
  615.     /* Yep.  See if it's something which we have access to, and 
  616.      * which has a known type and encoding (as opposed to something
  617.      * which we'll be slapping default_type on later).
  618.      */
  619.     
  620.     sub_req = sub_req_lookup_file (dir_entry->d_name, r);
  621.  
  622.     /* If it has a handler, we'll pretend it's a CGI script,
  623.      * since that's a good indication of the sort of thing it
  624.      * might be doing.
  625.      */
  626.     if (sub_req->handler && !sub_req->content_type)
  627.       sub_req->content_type = CGI_MAGIC_TYPE;
  628.  
  629.     if (sub_req->status != 200 || !sub_req->content_type) continue;
  630.     
  631.     /* If it's a map file, we use that instead of the map
  632.      * we're building...
  633.      */
  634.  
  635.     if (((sub_req->content_type) &&
  636.          !strcmp (sub_req->content_type, MAP_FILE_MAGIC_TYPE)) || 
  637.         ((sub_req->handler) && 
  638.         !strcmp (sub_req->handler, "type-map"))) {
  639.         closedir(dirp);
  640.         
  641.         neg->avail_vars->nelts = 0;
  642.         return read_type_map (neg, sub_req->filename);
  643.     }
  644.     
  645.     /* Have reasonable variant --- gather notes.
  646.      */
  647.     
  648.     mime_info.sub_req = sub_req;
  649.     mime_info.file_name = pstrdup(neg->pool, dir_entry->d_name);
  650.     mime_info.content_encoding = sub_req->content_encoding;
  651.     mime_info.content_language = sub_req->content_language;
  652.     
  653.     get_entry (neg->pool, &accept_info, sub_req->content_type);
  654.     set_mime_fields (&mime_info, &accept_info);
  655.     
  656.     new_var = push_array (neg->avail_vars);
  657.     memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
  658.         
  659.     clean_var_rec(&mime_info);
  660.     }
  661.  
  662.     closedir(dirp);
  663.     return OK;
  664. }
  665.  
  666.  
  667. /*****************************************************************
  668.  * And now for the code you've been waiting for... actually
  669.  * finding a match to the client's requirements.
  670.  */
  671.  
  672. /* Matching MIME types ... the star/star and foo/star commenting conventions
  673.  * are implemented here.  (You know what I mean by star/star, but just
  674.  * try mentioning those three characters in a C comment).  Using strcmp()
  675.  * is legit, because everything has already been smashed to lowercase.
  676.  *
  677.  * Note also that if we get an exact match on the media type, we update
  678.  * level_matched for use in level_cmp below...
  679.  * 
  680.  * We also give a value for mime_stars, which is used later. It should
  681.  * be 1 for star/star, 2 for type/star and 3 for type/subtype.
  682.  */
  683.  
  684. int mime_match (accept_rec *accept, var_rec *avail)
  685. {
  686.     char *accept_type = accept->type_name;
  687.     char *avail_type = avail->type_name;
  688.     int len = strlen(accept_type);
  689.   
  690.     if (accept_type[0] == '*')    { /* Anything matches star/star */
  691.         if (avail->mime_stars < 1)
  692.       avail->mime_stars = 1;
  693.     return 1; 
  694.     }
  695.     else if ((accept_type[len - 1] == '*') &&
  696.          !strncmp (accept_type, avail_type, len - 2)) {
  697.         if (avail->mime_stars < 2)
  698.       avail->mime_stars = 2;
  699.     return 1;
  700.     }
  701.     else if (!strcmp (accept_type, avail_type)
  702.          || (!strcmp (accept_type, "text/html")
  703.          && (!strcmp(avail_type, INCLUDES_MAGIC_TYPE)
  704.              || !strcmp(avail_type, INCLUDES_MAGIC_TYPE3)))) {
  705.     if (accept->level >= avail->level) {
  706.         avail->level_matched = avail->level;
  707.         avail->mime_stars = 3;
  708.         return 1;
  709.     }
  710.     }
  711.  
  712.     return OK;
  713. }
  714.  
  715. /* This code implements a piece of the tie-breaking algorithm between
  716.  * variants of equal quality.  This piece is the treatment of variants
  717.  * of the same base media type, but different levels.  What we want to
  718.  * return is the variant at the highest level that the client explicitly
  719.  * claimed to accept.
  720.  *
  721.  * If all the variants available are at a higher level than that, or if
  722.  * the client didn't say anything specific about this media type at all
  723.  * and these variants just got in on a wildcard, we prefer the lowest
  724.  * level, on grounds that that's the one that the client is least likely
  725.  * to choke on.
  726.  *
  727.  * (This is all motivated by treatment of levels in HTML --- we only
  728.  * want to give level 3 to browsers that explicitly ask for it; browsers
  729.  * that don't, including HTTP/0.9 browsers that only get the implicit
  730.  * "Accept: * / *" [space added to avoid confusing cpp --- no, that
  731.  * syntax doesn't really work] should get HTML2 if available).
  732.  *
  733.  * (Note that this code only comes into play when we are choosing among
  734.  * variants of equal quality, where the draft standard gives us a fair
  735.  * bit of leeway about what to do.  It ain't specified by the standard;
  736.  * rather, it is a choice made by this server about what to do in cases
  737.  * where the standard does not specify a unique course of action).
  738.  */
  739.  
  740. int level_cmp (var_rec *var1, var_rec *var2)
  741. {
  742.     /* Levels are only comparable between matching media types */
  743.  
  744.     if (var1->is_pseudo_html && !var2->is_pseudo_html)
  745.     return 0;
  746.     
  747.     if (!var1->is_pseudo_html && strcmp (var1->type_name, var2->type_name))
  748.     return 0;
  749.     
  750.     /* Take highest level that matched, if either did match. */
  751.     
  752.     if (var1->level_matched > var2->level_matched) return 1;
  753.     if (var1->level_matched < var2->level_matched) return -1;
  754.  
  755.     /* Neither matched.  Take lowest level, if there's a difference. */
  756.  
  757.     if (var1->level < var2->level) return 1;
  758.     if (var1->level > var2->level) return -1;
  759.  
  760.     /* Tied */
  761.  
  762.     return 0;
  763. }
  764.  
  765. /* Finding languages.  Note that we only match the substring specified
  766.  * by the Accept: line --- this is to allow "en" to match all subvariants
  767.  * of English.
  768.  *
  769.  * Again, strcmp() is legit because we've ditched case already.
  770.  */
  771.  
  772. int find_lang_index (array_header *accept_langs, char *lang)
  773. {
  774.     accept_rec *accs;
  775.     int i;
  776.  
  777.     if (!lang)
  778.     return -1;
  779.  
  780.     accs = (accept_rec *)accept_langs->elts;
  781.  
  782.     for (i = 0; i < accept_langs->nelts; ++i)
  783.     if (!strncmp (lang, accs[i].type_name, strlen(accs[i].type_name)))
  784.         return i;
  785.         
  786.     return -1;        
  787. }
  788.  
  789. /* This function returns the priority of a given language
  790.  * according to LanguagePriority.  It is used in case of a tie
  791.  * between several languages.
  792.  */
  793.  
  794. int find_default_index (neg_dir_config *conf, char *lang)
  795. {
  796.     array_header *arr;
  797.     int nelts;
  798.     char **elts;
  799.     int i;
  800.  
  801.     if (!lang)
  802.     return -1;
  803.  
  804.     arr = conf->language_priority;
  805.     nelts = arr->nelts;
  806.     elts = (char **) arr->elts;
  807.  
  808.     for (i = 0; i < nelts; ++i)
  809.         if (!strcasecmp (elts[i], lang))
  810.         return i;
  811.  
  812.     return -1;
  813. }
  814.  
  815. void find_lang_indexes (negotiation_state *neg)
  816. {
  817.     var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
  818.     int i;
  819.     int found_any = 0;
  820.     neg_dir_config *conf = NULL;
  821.     int naccept = neg->accept_langs->nelts;
  822.  
  823.     if (naccept == 0)
  824.     conf = (neg_dir_config *) get_module_config (neg->r->per_dir_config,
  825.                              &negotiation_module);
  826.  
  827.     for (i = 0; i < neg->avail_vars->nelts; ++i)
  828.     if (var_recs[i].quality > 0) {
  829.         int index;
  830.         if (naccept == 0)        /* Client doesn't care */
  831.         index = find_default_index (conf,
  832.                         var_recs[i].content_language);
  833.         else            /* Client has Accept-Language */
  834.         index = find_lang_index (neg->accept_langs,
  835.                      var_recs[i].content_language);
  836.  
  837.         var_recs[i].lang_index = index;
  838.         if (index >= 0) found_any = 1;
  839.     }
  840.  
  841.     /* If we have any variants in a language acceptable to the client,
  842.      * blow away everything that isn't.
  843.      */
  844.     
  845.     if (found_any)
  846.     for (i = 0; i < neg->avail_vars->nelts; ++i) 
  847.         if (var_recs[i].lang_index < 0)
  848.         var_recs[i].quality = 0;
  849. }
  850.  
  851. /* Finding content encodings.  Note that we assume that the client
  852.  * accepts the trivial encodings.  Strcmp() is legit because... aw, hell.
  853.  */
  854.  
  855. int is_identity_encoding (char *enc)
  856. {
  857.     return (!enc || !enc[0] || !strcmp (enc, "7bit") || !strcmp (enc, "8bit")
  858.         || !strcmp (enc, "binary"));
  859. }
  860.  
  861. int find_encoding (array_header *accept_encodings, char *enc)
  862. {
  863.     accept_rec *accs = (accept_rec *)accept_encodings->elts;
  864.     int i;
  865.  
  866.     if (is_identity_encoding(enc)) return 1;
  867.  
  868.     for (i = 0; i < accept_encodings->nelts; ++i)
  869.     if (!strcmp (enc, accs[i].type_name))
  870.         return 1;
  871.  
  872.     return 0;
  873. }
  874.  
  875. void do_encodings (negotiation_state *neg)
  876. {
  877.     var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
  878.     int i;
  879.  
  880.     /* If no Accept-Encoding is present, everything is acceptable */
  881.  
  882.     if (!neg->accept_encodings->nelts)
  883.     return;
  884.  
  885.     /* Lose any variant with an unacceptable content encoding */
  886.     
  887.     for (i = 0; i < neg->avail_vars->nelts; ++i)
  888.     if (var_recs[i].quality > 0
  889.         && !find_encoding (neg->accept_encodings,
  890.                    var_recs[i].content_encoding))
  891.         
  892.         var_recs[i].quality = 0;
  893. }
  894.  
  895. /* Determining the content length --- if the map didn't tell us,
  896.  * we have to do a stat() and remember for next time.
  897.  *
  898.  * Grump.  For shambhala, even the first stat here may well be
  899.  * redundant (for multiviews) with a stat() done by the sub_req
  900.  * machinery.  At some point, that ought to be fixed.
  901.  */
  902.  
  903. int find_content_length(negotiation_state *neg, var_rec *variant)
  904. {
  905.     struct stat statb;
  906.  
  907.     if (variant->bytes == 0) {
  908.         char *fullname = make_full_path (neg->pool, neg->dir_name,
  909.                      variant->file_name);
  910.     
  911.     if (stat (fullname, &statb) >= 0) variant->bytes = statb.st_size;
  912.     }
  913.  
  914.     return variant->bytes;
  915. }
  916.  
  917. /* The main event. */
  918.  
  919. var_rec *best_match(negotiation_state *neg)
  920. {
  921.     int i, j;
  922.     var_rec *best = NULL;
  923.     float best_quality = 0.0;
  924.     int levcmp;
  925.     
  926.     accept_rec *accept_recs = (accept_rec *)neg->accepts->elts;
  927.     var_rec *avail_recs = (var_rec *)neg->avail_vars->elts;
  928.  
  929.     /* Nuke variants which are unsuitable due to a content encoding,
  930.      * or possibly a language, which the client doesn't accept.
  931.      * (If we haven't *got* a variant in a language the client accepts,
  932.      * find_lang_indexes keeps 'em all, so we still wind up serving
  933.      * something...).
  934.      */
  935.     
  936.     do_encodings (neg);
  937.     find_lang_indexes (neg);
  938.     
  939.     for (i = 0; i < neg->accepts->nelts; ++i) {
  940.  
  941.     accept_rec *type = &accept_recs[i];
  942.     
  943.     for (j = 0; j < neg->avail_vars->nelts; ++j) {
  944.         
  945.         var_rec *variant = &avail_recs[j];
  946.         float q = type->quality * variant->quality;
  947.         
  948.         /* If we've already rejected this variant, don't waste time */
  949.         
  950.         if (q == 0.0) continue;    
  951.         
  952.         /* If media types don't match, forget it.
  953.          * (This includes the level check).
  954.          */
  955.         
  956.         if (!mime_match(type, variant)) continue;
  957.  
  958.         /* Check maxbytes */
  959.         
  960.         if (type->max_bytes > 0
  961.         && (find_content_length(neg, variant)
  962.             > type->max_bytes))
  963.         continue;
  964.         
  965.         /* If it lasted this far, consider it ---
  966.          * If better quality than our current best, take it.
  967.          * If equal quality, *maybe* take it.
  968.          *
  969.          * Note that the current http draft specifies no particular
  970.          * behavior for variants which tie in quality; the server
  971.          * can, at its option, return a 300 response listing all
  972.          * of them (and perhaps the others), or choose one of the
  973.          * tied variants by whatever means it likes.  This server
  974.          * breaks ties as follows, in order:
  975.          *
  976.          * By perferring non-wildcard entries to those with
  977.          * wildcards. The spec specifically says we should
  978.          * do this, and it makes a lot of sense.
  979.          *
  980.          * By order of languages in Accept-language, to give the
  981.          * client a way to specify a language preference.  I'd prefer
  982.          * to give this precedence over media type, but the standard
  983.          * doesn't allow for that.
  984.          *
  985.          * By level preference, as defined by level_cmp above.
  986.          *
  987.          * By order of Accept: header matched, so that the order in
  988.          * which media types are named by the client functions as a
  989.          * preference order, if the client didn't give us explicit
  990.          * quality values.
  991.          *
  992.          * Finally, by content_length, so that among variants which
  993.          * have the same quality, language and content_type (including
  994.          * level) we ship the one that saps the least bandwidth.
  995.          */
  996.         
  997.         if (q > best_quality
  998.         || (q == best_quality
  999.             && ((variant->mime_stars > best->mime_stars)
  1000.             || (variant->lang_index < best->lang_index
  1001.                 || (variant->lang_index == best->lang_index
  1002.                 && ((levcmp = level_cmp (variant, best)) == 1
  1003.                     || (levcmp == 0
  1004.                     && !strcmp (variant->type_name,
  1005.                             best->type_name)
  1006.                     && (find_content_length(neg, variant)
  1007.                         <
  1008.                 find_content_length(neg, best)))))))))
  1009.         {
  1010.         best = variant;
  1011.         best_quality = q;
  1012.         }
  1013.     }
  1014.     }
  1015.  
  1016.     return best;
  1017. }
  1018.  
  1019. /****************************************************************
  1020.  *
  1021.  * Executive...
  1022.  */
  1023.  
  1024. int handle_map_file (request_rec *r)
  1025. {
  1026.     negotiation_state *neg = parse_accept_headers (r);
  1027.     var_rec *best;
  1028.     int res;
  1029.     
  1030.     char *udir;
  1031.     
  1032.     if ((res = read_type_map (neg, r->filename))) return res;
  1033.     
  1034.     maybe_add_default_encodings(neg, 0);
  1035.     
  1036.     if (!(best = best_match(neg))) {
  1037.       /* Should be a 406 */
  1038.       log_reason ("no acceptable variant", r->filename, r);
  1039.       return NOT_FOUND;
  1040.     }
  1041.  
  1042.     if (!do_cache_negotiated_docs(r->server)) r->no_cache = 1;
  1043.     udir = make_dirstr (r->pool, r->uri, count_dirs (r->uri));
  1044.     udir = escape_uri(r->pool, udir);
  1045.     internal_redirect (make_full_path (r->pool, udir, best->file_name), r);
  1046.     return OK;
  1047. }
  1048.  
  1049. int handle_multi (request_rec *r)
  1050. {
  1051.     negotiation_state *neg;
  1052.     var_rec *best;
  1053.     request_rec *sub_req;
  1054.     int res;
  1055.     
  1056.     if (r->finfo.st_mode != 0 || !(allow_options (r) & OPT_MULTI))
  1057.         return DECLINED;
  1058.     
  1059.     neg = parse_accept_headers (r);
  1060.     
  1061.     if ((res = read_types_multi (neg))) return res;
  1062.     
  1063.     maybe_add_default_encodings(neg,
  1064.                 r->method_number != M_GET
  1065.                   || r->args || r->path_info);
  1066.     
  1067.     if (neg->avail_vars->nelts == 0) return DECLINED;
  1068.     
  1069.     if (!(best = best_match(neg))) {
  1070.       /* Should be a 406 */
  1071.       log_reason ("no acceptable variant", r->filename, r);
  1072.       return NOT_FOUND;
  1073.     }
  1074.  
  1075.     if (! (sub_req = best->sub_req)) {
  1076.         /* We got this out of a map file, so we don't actually have
  1077.      * a sub_req structure yet.  Get one now.
  1078.      */
  1079.       
  1080.         sub_req = sub_req_lookup_file (best->file_name, r);
  1081.     if (sub_req->status != 200) return sub_req->status;
  1082.     }
  1083.       
  1084.     /* BLETCH --- don't multi-resolve non-ordinary files */
  1085.  
  1086.     if (!S_ISREG(sub_req->finfo.st_mode)) return NOT_FOUND;
  1087.     
  1088.     /* Otherwise, use it. */
  1089.     
  1090.     if (!do_cache_negotiated_docs(r->server)) r->no_cache = 1;
  1091.     r->filename = sub_req->filename;
  1092.     r->handler = sub_req->handler;
  1093.     r->content_type = sub_req->content_type;
  1094.     r->content_encoding = sub_req->content_encoding;
  1095.     r->content_language = sub_req->content_language;
  1096.     r->finfo = sub_req->finfo;
  1097.     
  1098.     return OK;
  1099. }
  1100.  
  1101. handler_rec negotiation_handlers[] = {
  1102. { MAP_FILE_MAGIC_TYPE, handle_map_file },
  1103. { "type-map", handle_map_file },
  1104. { NULL }
  1105. };
  1106.  
  1107. module negotiation_module = {
  1108.    STANDARD_MODULE_STUFF,
  1109.    NULL,            /* initializer */
  1110.    create_neg_dir_config,    /* dir config creater */
  1111.    merge_neg_dir_configs,    /* dir merger --- default is to override */
  1112.    NULL,            /* server config */
  1113.    NULL,            /* merge server config */
  1114.    negotiation_cmds,        /* command table */
  1115.    negotiation_handlers,    /* handlers */
  1116.    NULL,            /* filename translation */
  1117.    NULL,            /* check_user_id */
  1118.    NULL,            /* check auth */
  1119.    NULL,            /* check access */
  1120.    handle_multi,        /* type_checker */
  1121.    NULL,            /* fixups */
  1122.    NULL                /* logger */
  1123. };
  1124.