You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by Mark J Cox <mj...@hyperreal.com> on 1996/09/24 14:45:05 UTC

cvs commit: apache/src http_protocol.c httpd.h mod_negotiation.c CHANGES

mjc         96/09/24 05:45:04

  Modified:    src       http_protocol.c httpd.h mod_negotiation.c CHANGES
  Log:
  Submitted by: Paul Sutton <pa...@ukweb.com>
  Reviewed by: Mark Cox, Alexei Kosut, Roy Fielding, Brian Behlendorf
  
  Negotiation updated to implement all aspects of HTTP/1.1, including
  charset and encoding negotiation.   Some code included for transparent
  negotiation (not compiled in by default).
  
  Revision  Changes    Path
  1.47      +20 -4     apache/src/http_protocol.c
  
  Index: http_protocol.c
  ===================================================================
  RCS file: /export/home/cvs/apache/src/http_protocol.c,v
  retrieving revision 1.46
  retrieving revision 1.47
  diff -C3 -r1.46 -r1.47
  *** http_protocol.c	1996/09/17 14:53:54	1.46
  --- http_protocol.c	1996/09/24 12:44:57	1.47
  ***************
  *** 50,56 ****
     *
     */
      
  ! /* $Id: http_protocol.c,v 1.46 1996/09/17 14:53:54 chuck Exp $ */
    
    /*
     * http_protocol.c --- routines which directly communicate with the
  --- 50,56 ----
     *
     */
      
  ! /* $Id: http_protocol.c,v 1.47 1996/09/24 12:44:57 mjc Exp $ */
    
    /*
     * http_protocol.c --- routines which directly communicate with the
  ***************
  *** 793,799 ****
        return OK;
    }
    
  ! #define RESPONSE_CODE_LIST " 200 206 301 302 304 400 401 403 404 405 406 411 412 500 503 501 502 "
    
    /* New Apache routine to map error responses into array indicies 
     *  e.g.  400 -> 0,  500 -> 1,  502 -> 2 ...                     
  --- 793,799 ----
        return OK;
    }
    
  ! #define RESPONSE_CODE_LIST " 200 206 300 301 302 304 400 401 403 404 405 406 411 412 500 503 501 502 506"
    
    /* New Apache routine to map error responses into array indicies 
     *  e.g.  400 -> 0,  500 -> 1,  502 -> 2 ...                     
  ***************
  *** 803,808 ****
  --- 803,809 ----
    char *status_lines[] = {
       "200 OK",
       "206 Partial Content",
  +    "300 Multiple Choices",
       "301 Moved Permanently",
       "302 Found",
       "304 Not Modified",
  ***************
  *** 817,828 ****
       "500 Server error",
       "503 Out of resources",
       "501 Not Implemented",
  !    "502 Bad Gateway"
    }; 
    
    char *response_titles[] = {
       "200 OK",			/* Never actually sent, barring die(200,...) */
       "206 Partial Content",	/* Never sent as an error (we hope) */
       "Document moved",		/* 301 Redirect */
       "Document moved",		/* 302 Redirect */
       "304 Not Modified",		/* Never sent... 304 MUST be header only */
  --- 818,831 ----
       "500 Server error",
       "503 Out of resources",
       "501 Not Implemented",
  !    "502 Bad Gateway",
  !    "506 Variant Also Varies"
    }; 
    
    char *response_titles[] = {
       "200 OK",			/* Never actually sent, barring die(200,...) */
       "206 Partial Content",	/* Never sent as an error (we hope) */
  +    "Multiple Choices",		/* 300 Multiple Choices */
       "Document moved",		/* 301 Redirect */
       "Document moved",		/* 302 Redirect */
       "304 Not Modified",		/* Never sent... 304 MUST be header only */
  ***************
  *** 837,843 ****
       "Server Error",
       "Out of resources",
       "Method not implemented",
  !    "Bad Gateway"
    };
    
    int index_of_response(int err_no) {
  --- 840,847 ----
       "Server Error",
       "Out of resources",
       "Method not implemented",
  !    "Bad Gateway",
  !    "Variant Also Varies"
    };
    
    int index_of_response(int err_no) {
  ***************
  *** 1361,1366 ****
  --- 1365,1377 ----
    	    bvputs(fd, "An appropriate variant to the requested entity ",
    		   escape_html(r->pool, r->uri), " could not be found "
    		   "on this server.<P>\n", NULL);
  + 	    /* fall through */
  + 	case MULTIPLE_CHOICES: 
  + 	    {
  + 		char *list;
  + 		if (list = table_get (r->notes, "variant-list"))
  + 		    bputs(list, fd);
  + 	    }
    	    break;
    	case LENGTH_REQUIRED:
    	    bvputs(fd, "A request of the requested method ", r->method,
  ***************
  *** 1389,1394 ****
  --- 1400,1410 ----
    	    bputs("The proxy server received an invalid\015\012", fd);
    	    bputs("response from an upstream server.<P>\015\012", fd);
    	    break;
  + 	case VARIANT_ALSO_VARIES:
  + 	    bvputs(fd, "A variant for the requested entity  ",
  + 		   escape_html(r->pool, r->uri), " is itself a ",
  + 		   "transparently negotiable resource.<P>\n", NULL);
  +   	    break;
    	}
    
            if (recursive_error) {
  
  
  
  1.50      +4 -2      apache/src/httpd.h
  
  Index: httpd.h
  ===================================================================
  RCS file: /export/home/cvs/apache/src/httpd.h,v
  retrieving revision 1.49
  retrieving revision 1.50
  diff -C3 -r1.49 -r1.50
  *** httpd.h	1996/09/03 00:31:27	1.49
  --- httpd.h	1996/09/24 12:44:58	1.50
  ***************
  *** 50,56 ****
     *
     */
    
  ! /* $Id: httpd.h,v 1.49 1996/09/03 00:31:27 akosut Exp $ */
    
    /*
     * httpd.h: header for simple (ha! not anymore) http daemon
  --- 50,56 ----
     *
     */
    
  ! /* $Id: httpd.h,v 1.50 1996/09/24 12:44:58 mjc Exp $ */
    
    /*
     * httpd.h: header for simple (ha! not anymore) http daemon
  ***************
  *** 255,260 ****
  --- 255,261 ----
    
    #define DOCUMENT_FOLLOWS 200
    #define PARTIAL_CONTENT 206
  + #define MULTIPLE_CHOICES 300
    #define MOVED 301
    #define REDIRECT 302
    #define USE_LOCAL_COPY 304
  ***************
  *** 270,276 ****
    #define NOT_IMPLEMENTED 501
    #define BAD_GATEWAY 502
    #define HTTP_SERVICE_UNAVAILABLE 503
  ! #define RESPONSE_CODES 16
    
    #define METHODS 8
    #define M_GET 0
  --- 271,278 ----
    #define NOT_IMPLEMENTED 501
    #define BAD_GATEWAY 502
    #define HTTP_SERVICE_UNAVAILABLE 503
  ! #define VARIANT_ALSO_VARIES 506
  ! #define RESPONSE_CODES 18
    
    #define METHODS 8
    #define M_GET 0
  
  
  
  1.18      +959 -284  apache/src/mod_negotiation.c
  
  Index: mod_negotiation.c
  ===================================================================
  RCS file: /export/home/cvs/apache/src/mod_negotiation.c,v
  retrieving revision 1.17
  retrieving revision 1.18
  diff -C3 -r1.17 -r1.18
  *** mod_negotiation.c	1996/08/20 11:51:17	1.17
  --- mod_negotiation.c	1996/09/24 12:44:59	1.18
  ***************
  *** 50,56 ****
     *
     */
    
  ! /* $Id: mod_negotiation.c,v 1.17 1996/08/20 11:51:17 paul Exp $ */
    
    /*
     * mod_negotiation.c: keeps track of MIME types the client is willing to
  --- 50,56 ----
     *
     */
    
  ! /* $Id: mod_negotiation.c,v 1.18 1996/09/24 12:44:59 mjc Exp $ */
    
    /*
     * mod_negotiation.c: keeps track of MIME types the client is willing to
  ***************
  *** 65,70 ****
  --- 65,79 ----
    #include "http_core.h"
    #include "http_log.h"
    
  + /* define HOLTMAN to allow for Holtman I-D transparent negotiation.
  +  * This file currently implements the draft-02, except for
  +  * anything to do with features and cache-control (max-age etc)
  +  *
  +  * Since the draft is just that, and we don't yet implement
  +  * everything, regard the transparent negotiation stuff as experimental.
  +  */
  + /*#define HOLTMAN*/
  + 
    /* Commands --- configuring document caching on a per (virtual?)
     * server basis...
     */
  ***************
  *** 136,141 ****
  --- 145,151 ----
        float quality;
        float max_bytes;
        float level;
  +     char *charset;              /* for content-type only */
    } accept_rec;
    
    /* Record of available info on a particular variant
  ***************
  *** 143,155 ****
     * Note that a few of these fields are updated by the actual negotiation
     * code.  These are:
     *
  -  * quality --- initialized to the value of qs, and subsequently jiggered
  -  *             to reflect the client's preferences.  In particular, it
  -  *             gets zeroed out if the variant has an unacceptable content
  -  *             encoding, or if it is in a language which the client
  -  *             doesn't accept and some other variant *is* in a language
  -  *             the client accepts.
  -  *
     * level_matched --- initialized to zero.  Set to the value of level
     *             if the client actually accepts this media type at that
     *             level (and *not* if it got in on a wildcard).  See level_cmp
  --- 153,158 ----
  ***************
  *** 157,178 ****
     */
    
    typedef struct var_rec {
  !     request_rec *sub_req;	/* May be NULL (is, for map files) */
        char *type_name;
        char *file_name;
        char *content_encoding;
        char *content_language;
  !     float level;		/* Auxiliary to content-type... */
  !     float qs;
  !     float bytes;
  !     int lang_index;
  !     int is_pseudo_html;		/* text/html, *or* the INCLUDES_MAGIC_TYPEs */
    
        /* Above are all written-once properties of the variant.  The
  !      * three fields below are changed during negotiation:
         */
        
  -     float quality;	
        float level_matched;
        int mime_stars;
    } var_rec;
  --- 160,198 ----
     */
    
    typedef struct var_rec {
  !     request_rec *sub_req;       /* May be NULL (is, for map files) */
        char *type_name;
        char *file_name;
        char *content_encoding;
        char *content_language;
  !     char *content_charset;
  !     char *description;
  ! 
  !     /* The next five items give the quality values for the dimensions
  !      * of negotiation for this variant. They are obtained from the
  !      * appropriate header lines, except for accept_type_quality, which
  !      * is obtained from the variant itself (the 'qs' parameter value
  !      * from the variant's mime-type). Apart from type_quality,
  !      * these values are set when we find the quality for each variant
  !      * (see best_match()). type_quality is set from the 'qs' parameter
  !      * of the variant description or mime type: see set_mime_fields().
  !      */
  !     float lang_quality;         /* quality of this variant's language */
  !     int   encoding_quality;     /* ditto encoding (1 or 0 only) */
  !     float charset_quality;      /* ditto charset */
  !     float accept_type_quality;  /* ditto media type */
  !     float type_quality;         /* quality of source for this type */
  ! 
  !     /* Now some special values */
  !     float level;                /* Auxiliary to content-type... */
  !     float bytes;		/* content length, if known */
  !     int lang_index;             /* pre HTTP/1.1 language priority stuff */
  !     int is_pseudo_html;         /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
    
        /* Above are all written-once properties of the variant.  The
  !      * two fields below are changed during negotiation:
         */
        
        float level_matched;
        int mime_stars;
    } var_rec;
  ***************
  *** 185,196 ****
        pool *pool;
        request_rec *r;
        char *dir_name;
  !     int accept_q;	/* Do any of the Accept: headers have a q-value ? */
  !     
  !     array_header *accepts;	/* accept_recs */
  !     array_header *accept_encodings;	/* accept_recs */
  !     array_header *accept_langs;	/* accept_recs */
  !     array_header *avail_vars;	/* available variants */
    } negotiation_state;
    
    /* A few functions to manipulate var_recs.
  --- 205,224 ----
        pool *pool;
        request_rec *r;
        char *dir_name;
  !     int accept_q;		/* 1 if an Accept item has a q= param */
  !     float default_lang_quality;	/* fiddle lang q for variants with no lang */
  ! 
  !    
  !     array_header *accepts;      /* accept_recs */
  !     int have_accept_header;	/* 1 if Accept-Header present */
  !     array_header *accept_encodings; /* accept_recs */
  !     array_header *accept_charsets; /* accept_recs */
  !     array_header *accept_langs; /* accept_recs */
  !     array_header *avail_vars;   /* available variants */
  ! 
  !     int ua_can_negotiate;       /* 1 if ua can do transparent negotiate */
  !     int use_transparent_neg;    /* 1 if we are using transparent neg */
  !     int short_accept_headers;   /* 1 if ua does trans neg & sent short accpt */
    } negotiation_state;
    
    /* A few functions to manipulate var_recs.
  ***************
  *** 204,218 ****
        mime_info->file_name = "";
        mime_info->content_encoding = "";
        mime_info->content_language = "";
    
        mime_info->is_pseudo_html = 0;
        mime_info->level = 0.0;
        mime_info->level_matched = 0.0;
  -     mime_info->qs = 0.0;
  -     mime_info->quality = 0.0;
        mime_info->bytes = 0;
        mime_info->lang_index = -1;
        mime_info->mime_stars = 0;
    }
    
    /* Initializing the relevant fields of a variant record from the
  --- 232,252 ----
        mime_info->file_name = "";
        mime_info->content_encoding = "";
        mime_info->content_language = "";
  +     mime_info->content_charset = "";
  +     mime_info->description = "";
    
        mime_info->is_pseudo_html = 0;
        mime_info->level = 0.0;
        mime_info->level_matched = 0.0;
        mime_info->bytes = 0;
        mime_info->lang_index = -1;
        mime_info->mime_stars = 0;
  + 
  +     mime_info->charset_quality = 1.0;
  +     mime_info->type_quality = 1.0;
  +     mime_info->encoding_quality = 1;
  +     mime_info->lang_quality = 1.0;
  +     mime_info->accept_type_quality = 1.0;
    }
    
    /* Initializing the relevant fields of a variant record from the
  ***************
  *** 222,230 ****
    void set_mime_fields (var_rec *var, accept_rec *mime_info)
    {
        var->type_name = mime_info->type_name;
  !     var->qs = mime_info->quality;
  !     var->quality = mime_info->quality; /* Initial quality is just qs */
        var->level = mime_info->level;
    
        var->is_pseudo_html = 
    	(!strcmp (var->type_name, "text/html")
  --- 256,264 ----
    void set_mime_fields (var_rec *var, accept_rec *mime_info)
    {
        var->type_name = mime_info->type_name;
  !     var->type_quality = mime_info->quality;
        var->level = mime_info->level;
  +     var->content_charset = mime_info->charset;
    
        var->is_pseudo_html = 
    	(!strcmp (var->type_name, "text/html")
  ***************
  *** 248,253 ****
  --- 282,288 ----
        result->quality = 1.0;
        result->max_bytes = 0.0;
        result->level = 0.0;
  +     result->charset = "";
        
        /* Note that this handles what I gather is the "old format",
         *
  ***************
  *** 282,287 ****
  --- 317,323 ----
    
    	char *parm;
    	char *cp;
  + 	char *end;
    	    
    	++accept_line;
    	parm = get_token (p, &accept_line, 1);
  ***************
  *** 297,303 ****
    	while (*cp && (isspace(*cp) || *cp == '='))
    	    ++cp;
    
  ! 	if (*cp == '"') ++cp;
    	
    	if (parm[0] == 'q'
    	    && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
  --- 333,352 ----
    	while (*cp && (isspace(*cp) || *cp == '='))
    	    ++cp;
    
  ! 	if (*cp == '"') {
  ! 	    ++cp;
  ! 	    for (end = cp; *end && 
  ! 		     *end != '\n' && *end != '\r' && *end != '\"';
  ! 		 end++)
  ! 		;
  ! 	}
  ! 	else {
  ! 	    for (end = cp; *end && !isspace(*end); end++)
  ! 		;
  ! 	}
  ! 	if (*end)
  ! 	    *end = '\0';	/* strip ending quote or return */
  ! 	str_tolower(cp);
    	
    	if (parm[0] == 'q'
    	    && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
  ***************
  *** 307,323 ****
    	    result->max_bytes = atof(cp);
    	else if (parm[0] == 'l' && !strcmp (&parm[1], "evel"))
    	    result->level = atof(cp);
        }
    
        if (*accept_line == ',') ++accept_line;
    
        return accept_line;
    }
  ! 		 
  ! 
    /*****************************************************************
     *
     * Dealing with header lines ...
     */
    
    array_header *do_header_line (pool *p, char *accept_line)
  --- 356,381 ----
    	    result->max_bytes = atof(cp);
    	else if (parm[0] == 'l' && !strcmp (&parm[1], "evel"))
    	    result->level = atof(cp);
  + 	else if (!strcmp(parm, "charset"))
  + 	    result->charset = cp;
        }
    
        if (*accept_line == ',') ++accept_line;
    
        return accept_line;
    }
  !                  
    /*****************************************************************
     *
     * Dealing with header lines ...
  +  *
  +  * Accept, Accept-Charset, Accept-Language and Accept-Encoding
  +  * are handled by do_header_line() - they all have the same
  +  * basic structure of a list of items of the format
  +  *    name; q=N; charset=TEXT
  +  *
  +  * where q is only valid in Accept, Accept-Charset and Accept-Languages,
  +  * and charset is only valid in Accept.
     */
    
    array_header *do_header_line (pool *p, char *accept_line)
  ***************
  *** 345,374 ****
            (negotiation_state *)pcalloc (r->pool, sizeof (negotiation_state));
        accept_rec *elts;
        table *hdrs = r->headers_in;
  !     
  !     int i;
    
        new->pool = r->pool;
        new->r = r;
        new->dir_name = make_dirstr(r->pool, r->filename, count_dirs(r->filename));
        
        new->accepts = do_header_line (r->pool, table_get (hdrs, "Accept"));
  !     new->accept_encodings =
  !       do_header_line (r->pool, table_get (hdrs, "Accept-encoding"));
        new->accept_langs =
          do_header_line (r->pool, table_get (hdrs, "Accept-language"));
        new->avail_vars = make_array (r->pool, 40, sizeof (var_rec));
    
  !     /* Now we check for q-values. If they're all 1.0, we assume the
  !      * client is "broken", and we are allowed to fiddle with the
  !      * values later. Otherwise, we leave them alone.
  !      */
  !     
  !     elts = (accept_rec *)new->accepts->elts;
  ! 
  !     for (i = 0; i < new->accepts->nelts; ++i)
  ! 	if (elts[i].quality < 1.0) new->accept_q = 1;
    
        return new;
    }
    
  --- 403,462 ----
            (negotiation_state *)pcalloc (r->pool, sizeof (negotiation_state));
        accept_rec *elts;
        table *hdrs = r->headers_in;
  !     int i;   
  !     char *hdr;
    
        new->pool = r->pool;
        new->r = r;
        new->dir_name = make_dirstr(r->pool, r->filename, count_dirs(r->filename));
        
        new->accepts = do_header_line (r->pool, table_get (hdrs, "Accept"));
  ! 
  !     hdr = table_get (hdrs, "Accept-encoding");
  !     if (hdr)
  !       new->have_accept_header = 1;
  !     new->accept_encodings = do_header_line (r->pool, hdr);
  ! 
        new->accept_langs =
          do_header_line (r->pool, table_get (hdrs, "Accept-language"));
  +     new->accept_charsets =
  +       do_header_line (r->pool, table_get (hdrs, "Accept-charset"));
        new->avail_vars = make_array (r->pool, 40, sizeof (var_rec));
    
  ! #ifdef HOLTMAN
  !     if (table_get(r->headers_in, "Negotiate")) {
  !         /* Negotiate: header tells us UA does transparent negotiation
  !          * We have to decide whether we want to ... for now, yes,
  !          * we do */
  ! 
  !         new->ua_can_negotiate = 1;
  !         if (r->method_number == M_GET)
  !             new->use_transparent_neg = 1; /* should be configurable */
  ! 
  !         /* Check for 'Short Accept', ie either no Accept: header,
  !          * or just "Accept: * / *" */
  !         if (new->accepts->nelts == 0 || 
  !             (new->accepts->nelts == 1 &&
  !             (!strcmp(((accept_rec *)new->accepts->elts)[0].type_name, 
  !                                                "*/*")))) {
  !             /* Using short accept header */
  !             new->short_accept_headers = 1;
  !         }
  !     }
  ! #endif
    
  +     if (!new->use_transparent_neg) {
  +         /* Now we check for q-values. If they're all 1.0, we assume the
  +          * client is "broken", and we are allowed to fiddle with the
  +          * values later. Otherwise, we leave them alone.
  +          */
  +     
  +         elts = (accept_rec *)new->accepts->elts;
  +     
  +         for (i = 0; i < new->accepts->nelts; ++i)
  +             if (elts[i].quality < 1.0) new->accept_q = 1;
  +     }
  +     
        return new;
    }
    
  ***************
  *** 449,455 ****
    	     */
    	    
    	    while (c != EOF && c != '\n' && isspace(c))
  ! 	        c = getc(map);
    
    	    ungetc (c, map);
    	    
  --- 537,543 ----
    	     */
    	    
    	    while (c != EOF && c != '\n' && isspace(c))
  ! 		c = getc(map);
    
    	    ungetc (c, map);
    	    
  ***************
  *** 458,464 ****
    	    /* Continuation */
    
    	    while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n')
  ! 	        *cp++ = c;
    
    	    *cp++ = '\n';
    	    *cp = '\0';
  --- 546,552 ----
    	    /* Continuation */
    
    	    while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n')
  ! 		*cp++ = c;
    
    	    *cp++ = '\n';
    	    *cp = '\0';
  ***************
  *** 487,493 ****
    	    ++hdr;
    	}
    	else if (*hdr == '(') {
  ! 	    while (*hdr && *hdr != ')')	*hdr++ = ' ';
    	    
    	    if (*hdr) *hdr++ = ' ';
    	}
  --- 575,581 ----
    	    ++hdr;
    	}
    	else if (*hdr == '(') {
  ! 	    while (*hdr && *hdr != ')') *hdr++ = ' ';
    	    
    	    if (*hdr) *hdr++ = ' ';
    	}
  ***************
  *** 547,553 ****
    	    strip_paren_comments (body);
    	    
    	    if (!strncmp (buffer, "uri:", 4)) {
  ! 	        mime_info.file_name = get_token (neg->pool, &body, 0);
    	    }
    	    else if (!strncmp (buffer, "content-type:", 13)) {
    		struct accept_rec accept_info;
  --- 635,641 ----
    	    strip_paren_comments (body);
    	    
    	    if (!strncmp (buffer, "uri:", 4)) {
  ! 		mime_info.file_name = get_token (neg->pool, &body, 0);
    	    }
    	    else if (!strncmp (buffer, "content-type:", 13)) {
    		struct accept_rec accept_info;
  ***************
  *** 566,576 ****
    		mime_info.content_encoding = get_token (neg->pool, &body, 0);
    		str_tolower (mime_info.content_encoding);
    	    }
  ! 	} else {
  ! 	    if (mime_info.quality > 0) {
  ! 	        void *new_var = push_array (neg->avail_vars);
  ! 		memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
    	    }
    	    
    	    clean_var_rec(&mime_info);
    	}
  --- 654,672 ----
    		mime_info.content_encoding = get_token (neg->pool, &body, 0);
    		str_tolower (mime_info.content_encoding);
    	    }
  ! 	    else if (!strncmp (buffer, "description:", 12)) {
  ! 		mime_info.description = get_token (neg->pool, &body, 0);
    	    }
  + 	} else {
  + #ifdef NOTDEF
  + 	    if (mime_info.quality > 0)
  + #endif
  + 	    if (*mime_info.file_name)
  + 		{
  + 		    void *new_var = push_array (neg->avail_vars);
  + 		    memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
  + 		}
  + 
    	    
    	    clean_var_rec(&mime_info);
    	}
  ***************
  *** 615,621 ****
        }
    
        while ((dir_entry = readdir (dirp))) {
  ! 	
            request_rec *sub_req;
          
    	/* Do we have a match? */
  --- 711,717 ----
        }
    
        while ((dir_entry = readdir (dirp))) {
  !         
            request_rec *sub_req;
          
    	/* Do we have a match? */
  ***************
  *** 623,629 ****
    	if (strncmp (dir_entry->d_name, filp, prefix_len)) continue;
    	if (dir_entry->d_name[prefix_len] != '.') continue;
    	
  ! 	/* Yep.  See if it's something which we have access to, and 
    	 * which has a known type and encoding (as opposed to something
    	 * which we'll be slapping default_type on later).
    	 */
  --- 719,725 ----
    	if (strncmp (dir_entry->d_name, filp, prefix_len)) continue;
    	if (dir_entry->d_name[prefix_len] != '.') continue;
    	
  ! 	/* Yep.	 See if it's something which we have access to, and 
    	 * which has a known type and encoding (as opposed to something
    	 * which we'll be slapping default_type on later).
    	 */
  ***************
  *** 658,666 ****
    	
    	mime_info.sub_req = sub_req;
    	mime_info.file_name = pstrdup(neg->pool, dir_entry->d_name);
  ! 	mime_info.content_encoding = sub_req->content_encoding;
  ! 	mime_info.content_language = sub_req->content_language;
  ! 	
    	get_entry (neg->pool, &accept_info, sub_req->content_type);
    	set_mime_fields (&mime_info, &accept_info);
    	
  --- 754,768 ----
    	
    	mime_info.sub_req = sub_req;
    	mime_info.file_name = pstrdup(neg->pool, dir_entry->d_name);
  ! 	if (sub_req->content_encoding) {
  ! 	    mime_info.content_encoding = sub_req->content_encoding;
  ! 	    str_tolower(mime_info.content_encoding);
  ! 	}
  ! 	if (sub_req->content_language) {
  ! 	    mime_info.content_language = sub_req->content_language;
  ! 	    str_tolower(mime_info.content_language);
  ! 	}
  ! 
    	get_entry (neg->pool, &accept_info, sub_req->content_type);
    	set_mime_fields (&mime_info, &accept_info);
    	
  ***************
  *** 698,713 ****
        char *avail_type = avail->type_name;
        int len = strlen(accept_type);
      
  !     if (accept_type[0] == '*')	{ /* Anything matches star/star */
            if (avail->mime_stars < 1)
  ! 	  avail->mime_stars = 1;
  ! 	return 1; 
        }
        else if ((accept_type[len - 1] == '*') &&
  ! 	     !strncmp (accept_type, avail_type, len - 2)) {
            if (avail->mime_stars < 2)
  ! 	  avail->mime_stars = 2;
  ! 	return 1;
        }
        else if (!strcmp (accept_type, avail_type)
    	     || (!strcmp (accept_type, "text/html")
  --- 800,815 ----
        char *avail_type = avail->type_name;
        int len = strlen(accept_type);
      
  !     if (accept_type[0] == '*')  { /* Anything matches star/star */
            if (avail->mime_stars < 1)
  !           avail->mime_stars = 1;
  !         return 1; 
        }
        else if ((accept_type[len - 1] == '*') &&
  !              !strncmp (accept_type, avail_type, len - 2)) {
            if (avail->mime_stars < 2)
  !           avail->mime_stars = 2;
  !         return 1;
        }
        else if (!strcmp (accept_type, avail_type)
    	     || (!strcmp (accept_type, "text/html")
  ***************
  *** 753,762 ****
        /* Levels are only comparable between matching media types */
    
        if (var1->is_pseudo_html && !var2->is_pseudo_html)
  ! 	return 0;
        
        if (!var1->is_pseudo_html && strcmp (var1->type_name, var2->type_name))
  ! 	return 0;
        
        /* Take highest level that matched, if either did match. */
        
  --- 855,864 ----
        /* Levels are only comparable between matching media types */
    
        if (var1->is_pseudo_html && !var2->is_pseudo_html)
  !         return 0;
        
        if (!var1->is_pseudo_html && strcmp (var1->type_name, var2->type_name))
  !         return 0;
        
        /* Take highest level that matched, if either did match. */
        
  ***************
  *** 773,784 ****
        return 0;
    }
    
  ! /* Finding languages.  Note that we only match the substring specified
  !  * by the Accept: line --- this is to allow "en" to match all subvariants
  !  * of English. We then do it in the other direction, so that all
  !  * subvariants of English match "en".
     *
  !  * Again, strcmp() is legit because we've ditched case already.
     */
    
    int find_lang_index (array_header *accept_langs, char *lang)
  --- 875,897 ----
        return 0;
    }
    
  ! /* Finding languages.  The main entry point is set_language_quality()
  !  * which is called for each variant. It sets two elements in the
  !  * variant record:
  !  *    language_quality  - the 'q' value of the 'best' matching language
  !  *                        from Accept-Language: header (HTTP/1.1)
  !  *    lang_index    -     Pre HTTP/1.1 language priority, using
  !  *                        position of language on the Accept-Language:
  !  *                        header, if present, else LanguagePriority
  !  *                        directive order.
     *
  !  * When we do the variant checking for best variant, we use language
  !  * quality first, and if a tie, language_index next (this only
  !  * applies when _not_ using the network algorithm). If using
  !  * network algorithm, lang_index is never used.
  !  *
  !  * set_language_quality() calls find_lang_index() and find_default_index()
  !  * to set lang_index.
     */
    
    int find_lang_index (array_header *accept_langs, char *lang)
  ***************
  *** 787,804 ****
        int i;
    
        if (!lang)
  ! 	return -1;
    
        accs = (accept_rec *)accept_langs->elts;
    
  !     for (i = 0; i < accept_langs->nelts; ++i) {
  ! 	if (!strncmp (lang, accs[i].type_name, strlen(accs[i].type_name)))
  ! 	    return i;
  ! 	if (!strncmp (lang, accs[i].type_name, strlen(lang)))
  ! 	    return i;
  !     }
  ! 	    
  !     return -1;		
    }
    
    /* This function returns the priority of a given language
  --- 900,914 ----
        int i;
    
        if (!lang)
  !         return -1;
    
        accs = (accept_rec *)accept_langs->elts;
    
  !     for (i = 0; i < accept_langs->nelts; ++i)
  !         if (!strncmp (lang, accs[i].type_name, strlen(accs[i].type_name)))
  !             return i;
  !             
  !     return -1;          
    }
    
    /* This function returns the priority of a given language
  ***************
  *** 814,820 ****
        int i;
    
        if (!lang)
  ! 	return -1;
    
        arr = conf->language_priority;
        nelts = arr->nelts;
  --- 924,930 ----
        int i;
    
        if (!lang)
  !         return -1;
    
        arr = conf->language_priority;
        nelts = arr->nelts;
  ***************
  *** 822,1084 ****
    
        for (i = 0; i < nelts; ++i)
            if (!strcasecmp (elts[i], lang))
  ! 	    return i;
    
        return -1;
    }
    
  ! void find_lang_indexes (negotiation_state *neg)
    {
  !     var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
        int i;
  !     int found_any = 0;
  !     neg_dir_config *conf = NULL;
        int naccept = neg->accept_langs->nelts;
    
        if (naccept == 0)
  ! 	conf = (neg_dir_config *) get_module_config (neg->r->per_dir_config,
  ! 						     &negotiation_module);
    
  !     for (i = 0; i < neg->avail_vars->nelts; ++i)
  ! 	if (var_recs[i].quality > 0) {
  ! 	    int index;
  ! 	    if (naccept == 0)		/* Client doesn't care */
  ! 		index = find_default_index (conf,
  ! 					    var_recs[i].content_language);
  ! 	    else			/* Client has Accept-Language */
  ! 		index = find_lang_index (neg->accept_langs,
  ! 					 var_recs[i].content_language);
    
  ! 	    var_recs[i].lang_index = index;
  ! 	    if (index >= 0) found_any = 1;
  ! 	}
    
  !     /* If we have any variants in a language acceptable to the client,
  !      * blow away everything that isn't.
  !      */
  !     
  !     if (found_any)
  ! 	for (i = 0; i < neg->avail_vars->nelts; ++i) 
  ! 	    if (var_recs[i].lang_index < 0)
  ! 		var_recs[i].quality = 0;
    }
    
  ! /* Finding content encodings.  Note that we assume that the client
  !  * accepts the trivial encodings.  Strcmp() is legit because... aw, hell.
     */
    
  ! int is_identity_encoding (char *enc)
    {
  !     return (!enc || !enc[0] || !strcmp (enc, "7bit") || !strcmp (enc, "8bit")
  ! 	    || !strcmp (enc, "binary"));
    }
    
  ! int find_encoding (array_header *accept_encodings, char *enc)
    {
  -     accept_rec *accs = (accept_rec *)accept_encodings->elts;
        int i;
    
  !     if (is_identity_encoding(enc)) return 1;
    
  !     for (i = 0; i < accept_encodings->nelts; ++i)
  ! 	if (!strcmp (enc, accs[i].type_name))
  ! 	    return 1;
    
  !     return 0;
    }
    
  ! void do_encodings (negotiation_state *neg)
    {
  -     var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
        int i;
    
  !     /* If no Accept-Encoding is present, everything is acceptable */
    
  !     if (!neg->accept_encodings->nelts)
  ! 	return;
    
  !     /* Lose any variant with an unacceptable content encoding */
        
  !     for (i = 0; i < neg->avail_vars->nelts; ++i)
  ! 	if (var_recs[i].quality > 0
  ! 	    && !find_encoding (neg->accept_encodings,
  ! 			       var_recs[i].content_encoding))
  ! 	    
  ! 	    var_recs[i].quality = 0;
    }
    
  ! /* Determining the content length --- if the map didn't tell us,
  !  * we have to do a stat() and remember for next time.
     *
  !  * Grump.  For shambhala, even the first stat here may well be
  !  * redundant (for multiviews) with a stat() done by the sub_req
  !  * machinery.  At some point, that ought to be fixed.
     */
    
  ! int find_content_length(negotiation_state *neg, var_rec *variant)
    {
  !     struct stat statb;
    
  !     if (variant->bytes == 0) {
  !         char *fullname = make_full_path (neg->pool, neg->dir_name,
  ! 					 variant->file_name);
  ! 	
  ! 	if (stat (fullname, &statb) >= 0) variant->bytes = statb.st_size;
        }
    
  !     return variant->bytes;
    }
    
  ! /* The main event. */
  ! 
  ! var_rec *best_match(negotiation_state *neg)
    {
  !     int i, j;
        var_rec *best = NULL;
  !     float best_quality = 0.0;
  !     int levcmp;
        
  -     accept_rec *accept_recs = (accept_rec *)neg->accepts->elts;
        var_rec *avail_recs = (var_rec *)neg->avail_vars->elts;
    
  !     /* Nuke variants which are unsuitable due to a content encoding,
  !      * or possibly a language, which the client doesn't accept.
  !      * (If we haven't *got* a variant in a language the client accepts,
  !      * find_lang_indexes keeps 'em all, so we still wind up serving
  !      * something...).
         */
  -     
  -     do_encodings (neg);
  -     find_lang_indexes (neg);
  -     
  -     for (i = 0; i < neg->accepts->nelts; ++i) {
    
  ! 	accept_rec *type = &accept_recs[i];
  ! 	
  ! 	for (j = 0; j < neg->avail_vars->nelts; ++j) {
  ! 	    
  ! 	    var_rec *variant = &avail_recs[j];
  ! 	    float q, quality = type->quality;
    
  ! 	    /* If we've already rejected this variant, don't waste time */
  ! 	    
  ! 	    if (variant->quality == 0.0) continue;	
  ! 	    
  ! 	    /* If media types don't match, forget it.
  ! 	     * (This includes the level check).
  ! 	     */
  ! 	    
  ! 	    if (!mime_match(type, variant)) continue;
    
  ! 	    /* If we are allowed to mess with the q-values,
  ! 	     * make wildcards very low, so we have a low chance
  ! 	     * of ending up with them if there's something better.
  ! 	     */
  ! 	    
  ! 	    if (!neg->accept_q && variant->mime_stars == 1) quality = 0.01;
  ! 	    if (!neg->accept_q && variant->mime_stars == 2) quality = 0.02;
    
  ! 	    q = quality * variant->quality;
    
  ! 	    /* Check maxbytes */
  ! 		
  ! 	    if (type->max_bytes > 0
  ! 		&& (find_content_length(neg, variant)
  ! 		    > type->max_bytes))
  ! 		continue;
  ! 		
  ! 	    /* If it lasted this far, consider it ---
  ! 	     * If better quality than our current best, take it.
  ! 	     * If equal quality, *maybe* take it.
  ! 	     *
  ! 	     * Note that the current http draft specifies no particular
  ! 	     * behavior for variants which tie in quality; the server
  ! 	     * can, at its option, return a 300 response listing all
  ! 	     * of them (and perhaps the others), or choose one of the
  ! 	     * tied variants by whatever means it likes.  This server
  ! 	     * breaks ties as follows, in order:
  ! 	     *
  ! 	     * By perferring non-wildcard entries to those with
  ! 	     * wildcards. The spec specifically says we should
  ! 	     * do this, and it makes a lot of sense.
  ! 	     *
  ! 	     * By order of languages in Accept-language, to give the
  ! 	     * client a way to specify a language preference.  I'd prefer
  ! 	     * to give this precedence over media type, but the standard
  ! 	     * doesn't allow for that.
  ! 	     *
  ! 	     * By level preference, as defined by level_cmp above.
  ! 	     *
  ! 	     * By order of Accept: header matched, so that the order in
  ! 	     * which media types are named by the client functions as a
  ! 	     * preference order, if the client didn't give us explicit
  ! 	     * quality values.
  ! 	     *
  ! 	     * Finally, by content_length, so that among variants which
  ! 	     * have the same quality, language and content_type (including
  ! 	     * level) we ship the one that saps the least bandwidth.
  ! 	     */
  ! 		
  ! 	    if (q > best_quality
  ! 		|| (q == best_quality
  ! 		    && ((variant->mime_stars > best->mime_stars)
  ! 			|| (variant->lang_index < best->lang_index
  ! 			    || (variant->lang_index == best->lang_index
  ! 				&& ((levcmp = level_cmp (variant, best)) == 1
  ! 				    || (levcmp == 0
  ! 					&& (find_content_length(neg, variant)
  ! 					    <
  ! 				         find_content_length(neg, best)))))))))
  ! 	    {
  ! 		best = variant;
  ! 		best_quality = q;
  ! 	    }
  ! 	}
        }
    
  !     return best;
    }
    
  ! char *set_vary (pool *p, negotiation_state *neg)
    {
  -     var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
        int i;
  !     int accept_type, accept_enc, accept_lang;
  !     char *type, *enc, *lang;
  !     char *last_type, *last_enc, *last_lang;
    
  !     accept_type = accept_enc = accept_lang = 0;
  !     last_type = last_enc = last_lang = NULL;
    
  !     /* Go through each variant and check for a differing
  !      * type, encoding or type.
         */
    
  !     for (i = 0; i < neg->avail_vars->nelts; ++i) {
  ! 	/* Ideally, we wouldn't have to do this, but strcmp(NULL, NULL)
  ! 	 * isn't legal
  ! 	 */
  ! 	type = var_recs[i].type_name ? var_recs[i].type_name : "";
  !         enc = var_recs[i].content_encoding ? var_recs[i].content_encoding : "";
  !        lang = var_recs[i].content_language ? var_recs[i].content_language : "";
  ! 
  ! 	if (!accept_type && last_type && strcmp(last_type, type))
  ! 	    accept_type = 1;
  ! 	else if (!accept_type && !last_type) last_type = type;
  ! 
  ! 	if (!accept_enc && last_enc && strcmp(last_enc, enc))
  ! 	    accept_enc = 1;
  ! 	else if (!accept_enc && !last_enc) last_enc = enc;
  ! 
  ! 	if (!accept_lang && last_lang && strcmp(last_lang, lang))
  ! 	    accept_lang = 1;
  ! 	else if (!accept_lang && !last_lang) last_lang = lang;
  !     }
  ! 
  !     if (!accept_type && !accept_enc && !accept_lang) return NULL;
  !     else return 2 + pstrcat(p, accept_type ? ", Accept" : "",
  ! 			    accept_enc ? ", Accept-Encoding" : "",
  ! 			    accept_lang ? ", Accept-Language" : "", NULL);
    }
    
    /****************************************************************
  --- 932,1709 ----
    
        for (i = 0; i < nelts; ++i)
            if (!strcasecmp (elts[i], lang))
  !             return i;
    
        return -1;
    }
    
  ! /* set_default_lang_quality() sets the quality we apply to variants
  !  * which have no language assigned to them. If none of the variants
  !  * have a language, we are not negotiating on language, so all are
  !  * acceptable, and we set the default q value to 1.0. However if
  !  * some of the variants have languages, we set this default to 0.001.
  !  * The value of this default will be applied to all variants with
  !  * no explicit language -- which will have the effect of making them
  !  * acceptable, but only if no variants with an explicit language
  !  * are acceptable. The default q value set here is assigned to variants
  !  * with no language type in set_language_quality().
  !  */
  ! 
  ! void set_default_lang_quality(negotiation_state *neg)
  ! {
  !     var_rec *avail_recs = (var_rec *)neg->avail_vars->elts;
  !     int j;
  ! 
  !     for (j = 0; j < neg->avail_vars->nelts; ++j) {
  !         var_rec *variant = &avail_recs[j];
  ! 	if (variant->content_language && *variant->content_language) {
  ! 	    neg->default_lang_quality = 0.001;
  ! 	    return;
  ! 	}
  !     }
  ! 	  
  !     neg->default_lang_quality = 1.0;
  ! }
  ! 
  ! /* Set the language_quality value in the variant record. Also
  !  * assigns lang_index for back-compat. 
  !  *
  !  * To find the language_quality value, we look for the 'q' value
  !  * of the 'best' matching language on the Accept-Language:
  !  * header. The'best' match is the language on Accept-Language:
  !  * header which matches the language of this variant either fully,
  !  * or as far as the prefix marker (-). If two or more languages
  !  * match, use the longest string from the Accept-Language: header
  !  * (see HTTP/1.1 [14.4])
  !  *
  !  * If the variant has no language and we have no Accept-Language
  !  * items, leave the quality at 1.0 and return.
  !  *
  !  * If the variant has no language, we use the default as set by
  !  * set_default_lang_quality() (1.0 if we are not negotiating on
  !  * language, 0.001 if we are).
  !  *
  !  * Following the setting of the language quality, we drop through to
  !  * set the old 'lang_index'. This is set based on either the order
  !  * of the languages on the Accept-Language header, or the
  !  * order on the LanguagePriority directive. This is only used
  !  * in the negotiation if the language qualities tie.
  !  */
  ! 
  ! void set_language_quality(negotiation_state *neg, var_rec *variant)
    {
  !     accept_rec *accs, *best = NULL, *star = NULL;
        int i;
  !     char *lang = variant->content_language;
  !     int prefixlen;
  !     char *p;
        int naccept = neg->accept_langs->nelts;
  +     int index;
  +     neg_dir_config *conf;
  +     int longest_lang_range_len = 0;
  +     int len;
    
        if (naccept == 0)
  !         conf = (neg_dir_config *) get_module_config (neg->r->per_dir_config,
  !                                                      &negotiation_module);
    
  !     if (naccept == 0 && (!lang || !*lang))
  !         return;                 /* variant has no assigned language */
    
  !     p = strchr(lang, '-');      /* find prefix part (if any) */
  !     if (p)
  !         prefixlen = p - lang; 
  ! 
  !     if (!lang || !*lang) {
  !         /* This variant has no content-language, so use the default
  ! 	 * quality factor for variants with no content-language
  ! 	 * (previously set by set_default_lang_quality()). */
  !         variant->lang_quality = neg->default_lang_quality;
  !     }
  !     else if (naccept) {
  ! 	float fiddle_q = 0.0;
    
  !         accs = (accept_rec *)neg->accept_langs->elts;
  ! 
  !         for (i = 0; i < neg->accept_langs->nelts; ++i) {
  !             if (!strcmp(accs[i].type_name, "*")) {
  !                 star = &accs[i];
  !                 continue;
  !             }
  !             
  !             /* Find language. We match if either the variant language
  ! 	     * tag exactly matches, or the prefix of the tag up to the
  ! 	     * '-' character matches the whole of the language in the
  ! 	     * Accept-Language header */
  !             if ((!strcmp (lang, accs[i].type_name) ||
  !                  (prefixlen &&
  !                   !strncmp(lang, accs[i].type_name, prefixlen) &&
  ! 		  (accs[i].type_name[prefixlen] == '\0'))) &&
  !                 ((len = strlen(accs[i].type_name)) > 
  !                                      longest_lang_range_len)) {
  !                 longest_lang_range_len = len;
  !                 best = &accs[i];
  !             }
  ! 
  ! 	    if (! best) {
  ! 	        /* The next bit is a fiddle. Some browsers might be
  ! 		 * configured to send more specific language ranges
  ! 		 * than desirable. For example, an Accept-Language of
  ! 		 * en-US should never match variants with languages en
  ! 		 * or en-GB. But US English speakers might pick en-US
  ! 		 * as their language choice.  So this fiddle checks if
  ! 		 * the language range has a prefix, and if so, it
  ! 		 * matches variants which match that prefix with a
  ! 		 * priority of 0.001. So a request for en-US would
  ! 		 * match variants of types en and en-GB, but at much
  ! 		 * lower priority than matches of en-US directly, or
  ! 		 * of any other language listed on the Accept-Language
  ! 		 * header
  ! 		 */
  ! 	        if (p = strchr(accs[i].type_name, '-')) {
  ! 		    int plen = p - accs[i].type_name;
  ! 		    if (!strncmp(lang, accs[i].type_name, plen))
  ! 			fiddle_q = 0.001;
  ! 		}
  ! 	    }
  !             
  !         }
  ! 	
  !         variant->lang_quality = best ? best->quality : 
  ! 	                     (star ? star->quality : fiddle_q);
  !     }
  ! 
  !     /* Now set the old lang_index field */
  !     index = 0;
  !     if (naccept == 0)           /* Client doesn't care */
  !         index = find_default_index (conf,
  !                                     variant->content_language);
  !     else                        /* Client has Accept-Language */
  !         index = find_lang_index (neg->accept_langs,
  !                                  variant->content_language);
  !     variant->lang_index = index;
  ! 
  !     return;             
    }
    
  ! /* Determining the content length --- if the map didn't tell us,
  !  * we have to do a stat() and remember for next time.
  !  *
  !  * Grump.  For shambhala, even the first stat here may well be
  !  * redundant (for multiviews) with a stat() done by the sub_req
  !  * machinery.  At some point, that ought to be fixed.
     */
    
  ! int find_content_length(negotiation_state *neg, var_rec *variant)
    {
  !     struct stat statb;
  ! 
  !     if (variant->bytes == 0) {
  !         char *fullname = make_full_path (neg->pool, neg->dir_name,
  !                                          variant->file_name);
  !         
  !         if (stat (fullname, &statb) >= 0) variant->bytes = statb.st_size;
  !     }
  ! 
  !     return variant->bytes;
    }
    
  ! /* For a given variant, find the best matching Accept: header
  !  * and assign the Accept: header's quality value to the
  !  * accept_type_quality field of the variant, for later use in
  !  * determining the best matching variant.
  !  */
  ! 
  ! void set_accept_quality(negotiation_state *neg, var_rec *variant)
    {
        int i;
  +     accept_rec *accept_recs = (accept_rec *)neg->accepts->elts;
  +     float q = 0.0;
    
  !     /* if no Accept: header, leave quality alone (will
  !      * remain at the default value of 1) */
  !     if (!neg->accepts || neg->accepts->nelts == 0) 
  !         return;
  ! 
  !     /*
  !      * Go through each of the ranges on the Accept: header,
  !      * looking for the 'best' match with this variant's
  !      * content-type. We use the best match's quality
  !      * value (from the Accept: header) for this variant's
  !      * accept_type_quality field.
  !      *
  !      * The best match is determined like this:
  !      *    type/type is better than type/ * is better than * / *
  !      *    if match is type/type, use the level mime param if available
  !      */
  !     for (i = 0; i < neg->accepts->nelts; ++i) {
    
  !         accept_rec *type = &accept_recs[i];
  !         int prev_mime_stars;
  !             
  !         prev_mime_stars = variant->mime_stars;
  ! 
  !         if (!mime_match(type, variant)) 
  !             continue;           /* didn't match the content type at all */
  !         else 
  !             /* did match - see if there were less or more stars than
  !              * in previous match
  !              */
  !             if (prev_mime_stars == variant->mime_stars)
  !                 continue;       /* more stars => not as good a match */
  ! 
  !         /* Check maxbytes -- not in HTTP/1.1 or Holtman */
  !                 
  !         if (type->max_bytes > 0
  !             && (find_content_length(neg, variant)
  !                 > type->max_bytes))
  !             continue;
  !         
  !         /* If we are allowed to mess with the q-values,
  !          * make wildcards very low, so we have a low chance
  !          * of ending up with them if there's something better.
  !          */
  ! 
  !         if (!neg->accept_q && variant->mime_stars == 1) q = 0.01;
  !         else if (!neg->accept_q && variant->mime_stars == 2) q = 0.02;
  !         else q = type->quality;
  !     }
  !     variant->accept_type_quality = q;
    
  !     /* if the _best_ quality we got for this variant was 0.0,
  !      * eliminate it now */
    }
    
  ! /* For a given variant, find the 'q' value of the charset given
  !  * on the Accept-Charset line. If not charsets are listed,
  !  * assume value of '1'.
  !  */
  ! 
  ! void set_charset_quality(negotiation_state *neg, var_rec *variant)
    {
        int i;
  +     accept_rec *accept_recs = (accept_rec *)neg->accept_charsets->elts;
  +     float q = 0.0;
  +     char *charset = variant->content_charset;
  + 
  +     if (!charset)
  +         return;                 /* variant has no charset */
  + 
  +     /* if no Accept-Charset: header, leave quality alone (will
  +      * remain at the default value of 1) */
  +     if (!neg->accept_charsets || neg->accept_charsets->nelts == 0) 
  +         return;
  + 
  +     if (!*charset || !strcmp(charset, "iso-8859-1"))
  +         return;                 /* default charset always ok */
  + 
  + 
  +     /*
  +      * Go through each of the items on the Accept-Charset: header,
  +      * looking for a match with this variant's charset. If none
  +      * match, charset is unacceptable, so set quality to 0.
  +      */
  +     for (i = 0; i < neg->accept_charsets->nelts; ++i) {
  + 
  +         accept_rec *type = &accept_recs[i];
  +             
  +         if (!strcmp(type->type_name, charset)) {
  +             variant->charset_quality = type->quality;
  +             return;
  +         }
  +     }
  +     variant->charset_quality = 0.0;
  + }
  + 
  + /* For a given variant, find the best matching Accept: header
  +  * and assign the Accept: header's quality value to the
  +  * accept_type_quality field of the variant, for later use in
  +  * determining the best matching variant.
  +  */
    
  ! /* is_identity_encoding is included for back-compat, but does anyone
  !  * use 7bit, 8bin or binary in their var files??
  !  */
    
  ! int is_identity_encoding (char *enc)
  ! {
  !     return (!enc || !enc[0] || !strcmp (enc, "7bit") || !strcmp (enc, "8bit")
  !             || !strcmp (enc, "binary"));
  ! }
  ! 
  ! void set_encoding_quality(negotiation_state *neg, var_rec *variant)
  ! {
  !     int i;
  !     accept_rec *accept_recs = (accept_rec *)neg->accept_encodings->elts;
  !     char *enc = variant->content_encoding;
    
  !     if (!enc || is_identity_encoding(enc))
  !         return;
        
  ! 
  !     /* if no Accept: header, leave quality alone (will
  !      * remain at the default value of 1) */
  !     if (neg->accept_encodings->nelts == 0) {
  !         /* If we had an empty Accept-Encoding header, assume that
  ! 	 * no encodings are acceptable, else all encodings are ok */
  !         variant->encoding_quality = neg->have_accept_header ? 0 : 1;
  !         return;
  !     }
  ! 
  !     /* Go through each of the encodings on the Accept-Encoding: header,
  !      * looking for a match with our encoding
  !      */
  !     for (i = 0; i < neg->accept_encodings->nelts; ++i) {
  !         char *name = accept_recs[i].type_name;
  ! 
  !         if (!strcmp(name, enc)) {
  !             variant->encoding_quality = 1;
  !             return;
  !         }
  !     }
  ! 
  !     /* Encoding not found on Accept-Encoding: header, so it is
  !      * _not_ acceptable */
  !     variant->encoding_quality = 0;
    }
    
  ! /* Possible results of the network algorithm */
  ! enum algorithm_results {
  !     na_not_applied = -1,        /* net algorithm not used */
  !     na_choice = 1,              /* choose variant */
  !     na_list                     /* list variants */
  ! };
  ! 
  ! /*
  !  * This is a heavily-rewritten 'best_match' function. For a start, it
  !  * now returns an int, which has one of the three values: na_not_applied,
  !  * na_choice or na_list, which give the result of the network algorithm
  !  * (if it was not applied, the return value is na_not_applied).
  !  * The best variable is returned in *pbest. It also has two possible
  !  * algorithms for determining the best match: the network algorithm,
  !  * and the standard Apache algorithm. These are split out into
  !  * separate functions (is_variant_better_na() and is_variant_better()).
     *
  !  * Previously, best_match iterated first through the content_types
  !  * in the Accept: header, then checked each variant, and eliminated
  !  * those that didn't match the variant's type. We cannot do this because
  !  * we need full information, including language, charset, etc
  !  * quality for _every_ variant, for the Alternates: header,
  !  * and (possibly) the human-readable choice responses or 406 errors.
  !  *
  !  * After the 'best' (if any) is determined, the overall result of
  !  * the negotiation is obtained. If the network algorithm was not
  !  * in use, the result is na_not_applied. Else the result is
  !  * na_list if 'short accept header' is in use, else na_list
  !  * if _no_ best match was found, or na_choice if a best match
  !  * was found.
  !  */
  ! 
  ! /* Firstly, the negotiation 'network algorithm' from Holtman.
  !  */
  ! 
  ! int is_variant_better_na(negotiation_state *neg, var_rec *variant, var_rec *best, float *p_bestq)
  ! {
  !     float bestq = *p_bestq, q;
  ! 
  !     /* Note: Encoding is not negotiated in the Holtman
  !      * transparent neg draft, so we ignored it here. But
  !      * it does mean we could return encodings the UA
  !      * or proxy cannot handle. Eek. */
  !     
  !     q = variant->accept_type_quality *
  !         variant->type_quality *
  !         variant->charset_quality *
  !         variant->lang_quality;
  !     
  ! #ifdef NEG_DEBUG
  !     fprintf(stderr, "Variant: file=%s type=%s lang=%s acceptq=%1.3f langq=%1.3f typeq=%1.3f q=%1.3f\n",
  !             variant->file_name ? variant->file_name : "",
  !             variant->type_name ? variant->type_name : "",
  !             variant->content_language ? variant->content_language : "",
  !             variant->accept_type_quality,
  !             variant->lang_quality,
  !             variant->type_quality,
  !             q
  !             );
  ! #endif
  !     
  !     if (q > bestq) {
  !         *p_bestq = q;
  !         return 1;
  !     }
  !     return 0;
  ! }    
  ! 
  ! /* Negotiation algorithm as used by previous versions of Apache
  !  * (just about). 
     */
    
  ! float is_variant_better(negotiation_state *neg, var_rec *variant, var_rec *best, float *p_bestq)
    {
  !     float bestq = *p_bestq, q;
  !     int levcmp;
    
  !     /*
  !      * For non-transparent negotiation, server can choose how
  !      * to handle the negotiation. We'll use the following in
  !      * order: content-type, language, content-type level, charset,
  !      * content length.
  !      *
  !      * For each check, we have three possible outcomes:
  !      *   This variant is worse than current best: return 0
  !      *   This variant is better than the current best:
  !      *          assign this variant's q to *p_bestq, and return 1
  !      *   This variant is just as desirable as the current best:
  !      *          drop through to the next test.
  !      *
  !      * This code is written in this long-winded way to allow future
  !      * customisation, either by the addition of additional
  !      * checks, or to allow the order of the checks to be determined
  !      * by configuration options (e.g. we might prefer to check
  !      * language quality _before_ content type).
  !      */
  ! 
  !     /* First though, eliminate this variant if it is not
  !      * acceptable by type, charset, encoding or language.
  !      */
  ! 
  !     if (variant->encoding_quality == 0 ||
  !         variant->lang_quality == 0 ||
  !         variant->type_quality == 0 ||
  !         variant->charset_quality == 0 ||
  !         variant->accept_type_quality == 0)
  !         return 0;               /* don't consider unacceptables */
  !     
  !     q = variant->accept_type_quality * variant->type_quality;
  !     if (q == 0.0 || q < bestq) return 0;
  !     if (q > bestq || !best) {
  !         *p_bestq = q;
  !         return 1;
        }
  +     
  +     /* language */
  +     if (variant->lang_quality < best->lang_quality)
  +         return 0;
  +     if (variant->lang_quality > best->lang_quality) {
  +         *p_bestq = q;
  +         return 1;
  +     }
  +     
  +     /* if language qualities were equal, try the LanguagePriority
  +      * stuff */
  +     if (variant->lang_index > best->lang_index)
  +         return 0;
  +     if (variant->lang_index < best->lang_index) {
  +         *p_bestq = q;
  +         return 1;
  +     }
  +     
  +     /* content-type level (text/html only?) */
  +     levcmp = level_cmp (variant, best);
  +     if (levcmp == -1) return 0;
  +     if (levcmp == 1) {
  +         *p_bestq = q;
  +         return 1;
  +     }
  +     
  +     /* encoding -- can only be 1 or 0, and if 0 we eliminated this
  +      * variant at the start of this function. However we 
  +      * prefer variants with no encoding over those with encoding */
  +     if (!*best->content_encoding && *variant->content_encoding)
  + 	return 0;
  +     if (*best->content_encoding && !*variant->content_encoding) {
  + 	*p_bestq = q;
  + 	return 1;
  +     }
  +     
    
  !     /* charset */
  !     if (variant->charset_quality < best->charset_quality)
  !         return 0;
  !     if (variant->charset_quality > best->charset_quality) {
  !         *p_bestq = q;
  !         return 1;
  !     }
  !     
  !     
  !     /* content length if all else equal */
  !     if (find_content_length(neg, variant)
  !          >=
  !          find_content_length(neg, best))
  !         return 0;
  !     
  !     /* ok, to get here means every thing turned out equal, except
  !      * we have a shorter content length, so use this variant */
  !     *p_bestq = q;
  !     return 1;
    }
    
  ! int best_match(negotiation_state *neg, var_rec **pbest)
    {
  !     int j;
        var_rec *best = NULL;
  !     float bestq = 0.0;
  !     enum algorithm_results algorithm_result = na_not_applied;
        
        var_rec *avail_recs = (var_rec *)neg->avail_vars->elts;
    
  !     set_default_lang_quality(neg);
  ! 
  !     /*
  !      * Find the 'best' variant
         */
    
  !     for (j = 0; j < neg->avail_vars->nelts; ++j) {
  !             
  !         var_rec *variant = &avail_recs[j];
  ! 
  !         /* Find all the relevant 'quality' values from the
  !          * Accept... headers, and store in the variant
  !          */
  !         set_accept_quality(neg, variant);
  !         set_language_quality(neg, variant);
  !         set_encoding_quality(neg, variant);
  !         set_charset_quality(neg, variant);
  ! 
  !         /* Now find out if this variant is better than the current
  !          * best, either using the network algorithm, or Apache's
  !          * internal server-driven algorithm. Presumably other
  !          * server-driven algorithms are possible, and could be
  !          * implemented here.
  !          */
  ! 
  !         if (neg->use_transparent_neg) {
  !             if (is_variant_better_na(neg, variant, best, &bestq))
  !                 best = variant;
  !         } 
  !         else {
  !             if (is_variant_better(neg, variant, best, &bestq))
  !                 best = variant;
  !         }
  !     }
    
  !     /* We now either have a best variant, or no best variant 
  !      */
  !     if (neg->use_transparent_neg) {
  !         if (neg->short_accept_headers)
  !             algorithm_result = na_list;
  !         else {
  !             /* From Holtman, result is:
  !              *   If variant & URI are not neigbors, list_ua or list_os
  !              *   Else
  !              *     If UA can do trans neg
  !              *        IF best q > 0, choice_ua 
  !              *        ELSE           list_ua
  !              *     ELSE
  !              *        IF best  > 0, choose_os
  !              *        ELSE          list_os (or forward_os on proxy)
  !              */
  ! 
  !             /* assume variant and URI are neigbors (since URI in
  !              * var map must be in same directory) */
  !             
  !             algorithm_result = bestq ? na_choice : na_list;
  !         }
  !     }   
    
  !     *pbest = best;
  !     return algorithm_result;
  ! }
    
  ! /*
  !  * Sets the Alternates and Vary headers, used if we are going to
  !  * return 406 Not Acceptable status, a 300 Multiple Choice status,
  !  * or a Choice response.
  !  *
  !  * 'type' is the result of the network algorithm, if applied.
  !  * We do different things if the network algorithm was not applied
  !  * (type == na_not_applied): no Alternates header, and Vary:
  !  * does not include 'negotiate'.
  !  *
  !  * We should also add a max-age lifetime for the Alternates header,
  !  * but how long we we give it? Presumably this should be
  !  * configurable in the map file.
  !  */
    
  ! void set_neg_headers(request_rec *r, negotiation_state *neg, int na_result)
  ! {
  !     int j;
  !     var_rec *avail_recs = (var_rec *)neg->avail_vars->elts;
  !     char *sample_type = NULL;
  !     char *sample_language = NULL;
  !     char *sample_encoding = NULL;
  !     char *sample_charset = NULL;
  !     int vary_by_type = 0;
  !     int vary_by_language = 0;
  !     int vary_by_charset = 0;
  !     int vary_by_encoding = 0;
  !     array_header *hdrs;
  ! 
  !     /* Question: do we output Alternates and Vary headers in the
  !      * error document of 406 messages? I assume not here. But we
  !      * definitely need to get these headers output when sending
  !      * a 300 'error', so if the result of the network algorithm
  !      * was a List response, set them in err_headers_out.
  !      */
  !     hdrs = (na_result == na_list) ? r->err_headers_out : r->headers_out;
  ! 
  !     for (j = 0; j < neg->avail_vars->nelts; ++j) {
  !             
  !         var_rec *variant = &avail_recs[j];
  !         char *rec;
  !         char qstr[6];
  !         long len;
  !         char lenstr[20];                /* is this long enough? */
  ! 
  !         sprintf(qstr, "%1.3f", variant->type_quality);
  ! 
  !         /* Strip trailing zeros (saves those valuable network bytes) */
  !         if (qstr[4] == '0') {
  !             qstr[4] = '\0';
  !             if (qstr[3] == '0') {
  !                 qstr[3] = '\0';
  !                 if (qstr[2] == '0') {
  !                     qstr[1] = '\0';
  !                 }
  !             }
  !         }
  !                               
  ! 
  !         rec = pstrcat(r->pool, "{\"", variant->file_name, "\" ", qstr, NULL);
  !         if (variant->type_name) {
  ! 	    if (*variant->type_name)
  ! 	        rec = pstrcat(r->pool, rec, " {type ", 
  ! 			      variant->type_name, "}", NULL);
  ! 	    if (!sample_type) sample_type = variant->type_name;
  ! 	    else if (strcmp(sample_type, variant->type_name))
  ! 	      vary_by_type = 1;
  !         }
  !         if (variant->content_language) {
  !             if (*variant->content_language)
  !                 rec = pstrcat(r->pool, rec, " {language ", 
  !                               variant->content_language, "}", NULL);
  !             if (!sample_language) sample_language = variant->content_language;
  !             else if (strcmp(sample_language, variant->content_language))
  !                 vary_by_language = 1;
  !         }
  !         if (variant->content_encoding) {
  !             if (!sample_encoding) sample_encoding = variant->content_encoding;
  !             else if (strcmp(sample_encoding, variant->content_encoding))
  !                 vary_by_encoding = 1;
  !         }
  !         if (variant->content_charset) {
  !             if (*variant->content_charset)
  !                 rec = pstrcat(r->pool, rec, " {charset ", 
  !                               variant->content_charset, "}", NULL);
  !             if (!sample_charset) sample_charset = variant->content_charset;
  !             else if (strcmp(sample_charset, variant->content_charset))
  !                 vary_by_charset = 1;
  !         }
  !         if ((len = find_content_length(neg, variant)) != 0) {
  !             sprintf(lenstr, "%ld", len);
  !             rec = pstrcat(r->pool, rec, " {length ", lenstr, "}", NULL);
  !         }
  !         
  !         rec = pstrcat(r->pool, rec, "}", NULL);
  !         
  !         if (na_result != na_not_applied)
  !             table_merge(hdrs, "Alternates", rec);
        }
    
  !     if (na_result != na_not_applied)
  !         table_merge(hdrs, "Vary", "negotiate");
  !     if (vary_by_type) 
  !         table_merge(hdrs, "Vary", "accept");
  !     if (vary_by_language) 
  !         table_merge(hdrs, "Vary", "accept-language");
  !     if (vary_by_charset) 
  !         table_merge(hdrs, "Vary", "accept-charset");
  !     if (vary_by_encoding && na_result == na_not_applied) 
  !         table_merge(hdrs, "Vary", "accept-encoding");
    }
    
  ! /**********************************************************************
  !  *
  !  * Return an HTML list of variants. This is output as part of the
  !  * 300 or 406 status body.
  !  */
  ! 
  ! char *make_variant_list (request_rec *r, negotiation_state *neg)
    {
        int i;
  !     char *t;
  ! 
  !     t = pstrdup(r->pool, "Available variants:\n<ul>\n");
  !     for (i = 0; i < neg->avail_vars->nelts; ++i) {
  !         var_rec *variant = &((var_rec *)neg->avail_vars->elts)[i];
  !         char *filename = variant->file_name ? variant->file_name : "";
  !         char *content_type = variant->type_name ? variant->type_name : "";
  !         char *content_language = 
  !             variant->content_language ? variant->content_language : "";
  !         char *description = variant->description ? variant->description : "";
  ! 
  ! 	/* The format isn't very neat, and it would be nice to make
  ! 	 * the tags human readable (eg replace 'language en' with
  ! 	 * 'English'). */
  !         t = pstrcat(r->pool, t, "<li><a href=\"", filename, "\">", 
  !                     filename, "</a> ", description,
  !                     " type ", content_type, 
  ! 		    *content_language ? " language " : "", content_language, 
  ! 		    "\n",
  !                     NULL);
  !     }
  !     t = pstrcat(r->pool, t, "</ul>\n", NULL);
  ! 
  !     return t;
  ! }
  ! 
  ! void store_variant_list (request_rec *r, negotiation_state *neg)
  ! {
  !   table_set (r->notes, "variant-list", make_variant_list (r, neg));
  ! }
  ! 
  ! /* Called if we got a "Choice" response from the network algorithm.
  !  * It checks the result of the chosen variant to see if it
  !  * is itself negotiated (if so, return error VARIANT_ALSO_VARIES).
  !  * Otherwise, add the appropriate headers to the current response.
  !  */
    
  ! int setup_choice_response(request_rec *r, negotiation_state *neg, var_rec *variant)
  ! {
  !     request_rec *sub_req;
  !     char *sub_vary;
  ! 
  !     if (!variant->sub_req) {
  !         sub_req = sub_req_lookup_file(variant->file_name, r);
  !         if (sub_req->status != 200 && sub_req->status != 300)
  !             return sub_req->status;
  !         variant->sub_req = sub_req;
  !     }
  !     else 
  !         sub_req = variant->sub_req;
  !     
    
  !     /* The network algorithm told us to return a "Choice"
  !      * response. This is the normal variant response, with
  !      * some extra headers. First, ensure that the chosen
  !      * variant did not itself return a "List" or "Choice" response.
  !      * If not, set the appropriate headers, and fall through to
  !      * the normal variant handling 
         */
    
  !     if ((sub_req->status == 300) ||
  !         (table_get(sub_req->headers_out, "Alternates")) ||
  !         (table_get(sub_req->headers_out, "Content-Location")))
  !         return VARIANT_ALSO_VARIES;
  !     
  !     if ((sub_vary = table_get(sub_req->headers_out, "Vary")) != NULL)
  !         table_set(r->headers_out, "Variant-Vary", sub_vary);
  !     table_set(r->headers_out, "Content-Location", variant->file_name);
  !     set_neg_headers(r, neg, na_choice); /* add Alternates and Vary */
  !     /* to do: add Expires */
  ! 
  !     return 0;
    }
    
    /****************************************************************
  ***************
  *** 1091,1116 ****
        negotiation_state *neg = parse_accept_headers (r);
        var_rec *best;
        int res;
        
  !     char *vary, *udir;
        
        if ((res = read_type_map (neg, r->filename))) return res;
        
        maybe_add_default_encodings(neg, 0);
        
  !     if (!(best = best_match(neg))) {
          log_reason ("no acceptable variant", r->filename, r);
          return NOT_ACCEPTABLE;
        }
    
        /* Make sure caching works - Vary should handle HTTP/1.1, but for
         * HTTP/1.0, we can't allow caching at all. NB that we merge the
         * header in case some other module negotiates on something else.
         */
        if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001))
            r->no_cache = 1;
  !     if ((vary = set_vary(r->pool, neg)))
  ! 	table_merge(r->err_headers_out, "Vary", vary);
    
        udir = make_dirstr (r->pool, r->uri, count_dirs (r->uri));
        udir = escape_uri(r->pool, udir);
  --- 1716,1769 ----
        negotiation_state *neg = parse_accept_headers (r);
        var_rec *best;
        int res;
  +     int na_result;
        
  !     char *udir;
        
        if ((res = read_type_map (neg, r->filename))) return res;
        
        maybe_add_default_encodings(neg, 0);
        
  !     na_result = best_match(neg, &best);
  ! 
  !     /* na_result is one of
  !      *   na_not_applied: we didn't use the network algorithm
  !      *   na_choice: return a "Choice" response
  !      *   na_list: return a "List" response (no variant chosen)
  !      */
  ! 
  !     if (na_result == na_list) {
  !         set_neg_headers(r, neg, na_list);
  !         store_variant_list (r, neg);
  !         return MULTIPLE_CHOICES;
  !     }
  ! 
  !     if (!best) {
          log_reason ("no acceptable variant", r->filename, r);
  + 
  +       set_neg_headers(r, neg, na_result);
  +       store_variant_list (r, neg);
          return NOT_ACCEPTABLE;
        }
    
  +     if (na_result == na_choice) {
  +         if ((res = setup_choice_response(r, neg, best)) != 0)
  +             return res;
  +         
  +         /* Run the request. Should we check the output status
  +          * for REDIRECT?? */
  +         return run_sub_req(best->sub_req);
  +     }
  + 
        /* Make sure caching works - Vary should handle HTTP/1.1, but for
         * HTTP/1.0, we can't allow caching at all. NB that we merge the
         * header in case some other module negotiates on something else.
         */
        if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001))
            r->no_cache = 1;
  ! 
  !     if (na_result == na_not_applied)
  !         set_neg_headers(r, neg, na_not_applied);
    
        udir = make_dirstr (r->pool, r->uri, count_dirs (r->uri));
        udir = escape_uri(r->pool, udir);
  ***************
  *** 1123,1130 ****
        negotiation_state *neg;
        var_rec *best;
        request_rec *sub_req;
  -     char *vary;
        int res;
        
        if (r->finfo.st_mode != 0 || !(allow_options (r) & OPT_MULTI))
            return DECLINED;
  --- 1776,1783 ----
        negotiation_state *neg;
        var_rec *best;
        request_rec *sub_req;
        int res;
  +     int na_result;              /* result of network algorithm */
        
        if (r->finfo.st_mode != 0 || !(allow_options (r) & OPT_MULTI))
            return DECLINED;
  ***************
  *** 1134,1156 ****
        if ((res = read_types_multi (neg))) return res;
        
        maybe_add_default_encodings(neg,
  ! 				r->method_number != M_GET
  ! 				  || r->args || r->path_info);
        
        if (neg->avail_vars->nelts == 0) return DECLINED;
        
  !     if (!(best = best_match(neg))) {
          log_reason ("no acceptable variant", r->filename, r);
          return NOT_ACCEPTABLE;
        }
    
        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 = sub_req_lookup_file (best->file_name, r);
  ! 	if (sub_req->status != 200) return sub_req->status;
        }
          
        /* BLETCH --- don't multi-resolve non-ordinary files */
  --- 1787,1830 ----
        if ((res = read_types_multi (neg))) return res;
        
        maybe_add_default_encodings(neg,
  !                                 r->method_number != M_GET
  !                                   || r->args || r->path_info);
        
        if (neg->avail_vars->nelts == 0) return DECLINED;
        
  !     na_result = best_match(neg, &best);
  !     if (na_result == na_list) {
  !         /*
  !          * Network algorithm tols us to output a "List" response.
  ! 	 * This is output at a 300 status code, which we will
  ! 	 * return. The list of variants will be stored in r->notes
  ! 	 * under the name "variants-list".
  !          */
  !         set_neg_headers(r, neg, na_list); /* set Alternates: and Vary: */
  ! 
  !         store_variant_list (r, neg);
  !         return MULTIPLE_CHOICES;
  !     }
  ! 
  !     if (!best) {
          log_reason ("no acceptable variant", r->filename, r);
  + 
  +       set_neg_headers (r, neg, na_result);
  +       store_variant_list (r, neg);
          return NOT_ACCEPTABLE;
        }
    
  +     if (na_result == na_choice)
  +         if ((res = setup_choice_response(r, neg, best)) != 0)
  +             return res;
  + 
        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 = sub_req_lookup_file (best->file_name, r);
  !         if (sub_req->status != 200) return sub_req->status;
        }
          
        /* BLETCH --- don't multi-resolve non-ordinary files */
  ***************
  *** 1161,1168 ****
        
        if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001))
            r->no_cache = 1;
  !     if ((vary = set_vary(r->pool, neg)))
  ! 	table_merge(r->err_headers_out, "Vary", vary);
    
        r->filename = sub_req->filename;
        r->handler = sub_req->handler;
  --- 1835,1843 ----
        
        if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001))
            r->no_cache = 1;
  ! 
  !     if (na_result == na_not_applied)
  !         set_neg_headers(r, neg, na_not_applied);
    
        r->filename = sub_req->filename;
        r->handler = sub_req->handler;
  
  
  
  1.65      +14 -1     apache/src/CHANGES
  
  Index: CHANGES
  ===================================================================
  RCS file: /export/home/cvs/apache/src/CHANGES,v
  retrieving revision 1.64
  retrieving revision 1.65
  diff -C3 -r1.64 -r1.65
  *** CHANGES	1996/09/17 14:58:30	1.64
  --- CHANGES	1996/09/24 12:45:00	1.65
  ***************
  *** 1,6 ****
  ! $Id: CHANGES,v 1.64 1996/09/17 14:58:30 chuck Exp $
    
    Changes with Apache 1.2b1:
    
      *) Netscape 2.x keepalive fix, using BrowserMatch/mod_browser.
         [Chuck Murcko]
  --- 1,19 ----
  ! $Id: CHANGES,v 1.65 1996/09/24 12:45:00 mjc Exp $
    
    Changes with Apache 1.2b1:
  + 
  +   *) Negotiation updated to implement all aspects of HTTP/1.1, including
  +       charset and encoding negotiation. Now server-driven negotiation
  +       can return a list of variants (the MULTIPLE_CHOICE response).
  +       Some code included for transparent negotiation (not compiled
  +       in by default). [Paul Sutton]
  + 
  +   *) IP Virtual Hosts now work where the vhost IP address is the main
  +       IP address of the host and the port s not the default port.
  +       [Paul Sutton]
  + 
  +   *) Domain names on 'allow' and 'deny' lines are now case-insensitive.
  +       [Paul Sutton]
    
      *) Netscape 2.x keepalive fix, using BrowserMatch/mod_browser.
         [Chuck Murcko]