1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include "tscore/ink_platform.h"
25 #include "HttpCompat.h"
26 #include "HdrUtils.h"
27 
28 //////////////////////////////////////////////////////////////////////////////
29 //
30 //      HttpCompat::parse_tok_list
31 //
32 //      Takes a string containing an HTTP list broken on the separator
33 //      character <sep>, and returns a StrList object containing a
34 //      dynamically allocated list of elements.  This is essentially a
35 //      fancy strtok that runs to completion and hands you back all tokens.
36 //
37 //      The routine either allocates and copies each string token, or
38 //      just maintains the point to the raw text token, depending on the
39 //      mode of the StrList object.
40 //
41 //////////////////////////////////////////////////////////////////////////////
42 
43 void
parse_tok_list(StrList * list,int trim_quotes,const char * string,char sep)44 HttpCompat::parse_tok_list(StrList *list, int trim_quotes, const char *string, char sep)
45 {
46   if (string == nullptr) {
47     return;
48   }
49   HttpCompat::parse_tok_list(list, trim_quotes, string, static_cast<int>(strlen(string)), sep);
50 }
51 
52 void
parse_tok_list(StrList * list,int trim_quotes,const char * string,int len,char sep)53 HttpCompat::parse_tok_list(StrList *list, int trim_quotes, const char *string, int len, char sep)
54 {
55   int in_quote;
56   const char quot = '\"';
57   const char *s, *e, *l, *s_before_skipping_ws;
58   int index, byte_length, hit_sep;
59 
60   if ((string == nullptr) || (list == nullptr) || (sep == NUL)) {
61     return;
62   }
63 
64   s     = string;
65   l     = s + len - 1;
66   index = 0;
67 
68   hit_sep              = 0;
69   s_before_skipping_ws = s;
70 
71   while (s <= l) {
72     //////////////////////////////////////////////////////////
73     // find the start of the first token, skipping over any //
74     // whitespace or empty tokens, to leave <s> pointing at //
75     // a NUL, a character, or a double quote.               //
76     //////////////////////////////////////////////////////////
77 
78     while ((s <= l) && ParseRules::is_ws(*s)) {
79       ++s; // skip whitespace
80     }
81 
82     //////////////////////////////////////////////////////////
83     // if we are pointing at a separator, this was an empty //
84     // token, so add the empty token, and continue parsing. //
85     //////////////////////////////////////////////////////////
86 
87     if ((s <= l) && (*s == sep)) {
88       list->append_string(s_before_skipping_ws, 0);
89       ++index;
90       s_before_skipping_ws = s + 1;
91       s                    = s_before_skipping_ws;
92       hit_sep              = 1;
93       continue;
94     }
95     //////////////////////////////////////////////////////////////////
96     // at this point, <s> points to EOS, a double quote, or another //
97     // character --- if EOS, then break out of the loop, and either //
98     // tack on a final empty token if we had a trailing separator,  //
99     // or just exit.                                                //
100     //////////////////////////////////////////////////////////////////
101 
102     if (s > l) {
103       break;
104     }
105 
106     ///////////////////////////////////////////////////////////////////
107     // we are pointing to the first character of a token now, either //
108     // a character, or a double quote --- the next step is to scan   //
109     // for the next separator or end of string, being careful not to //
110     // include separators inside quotes.                             //
111     ///////////////////////////////////////////////////////////////////
112 
113 #define is_unquoted_separator(c) ((c == sep) && !in_quote)
114 
115     if (*s == quot) {
116       in_quote = 1;
117       e        = s + 1; // start after quote
118       if (trim_quotes) {
119         ++s; // trim starting quote
120       }
121     } else {
122       in_quote = 0;
123       e        = s;
124     }
125 
126     while ((e <= l) && !is_unquoted_separator(*e)) {
127       if (*e == quot) {
128         in_quote = !in_quote;
129       }
130       e++;
131     }
132 
133     ///////////////////////////////////////////////////////////////////////
134     // we point one char past the last character of string, or an        //
135     // unquoted separator --- so back up into any previous whitespace or //
136     // quote, leaving <e> pointed 1 char after the last token character. //
137     ///////////////////////////////////////////////////////////////////////
138 
139     hit_sep = (e <= l); // must have hit a separator if still inside string
140 
141     s_before_skipping_ws = e + 1; // where to start next time
142     while ((e > s) && ParseRules::is_ws(*(e - 1))) {
143       --e; // eat trailing ws
144     }
145     if ((e > s) && (*(e - 1) == quot) && trim_quotes) {
146       --e; // eat trailing quote
147     }
148 
149     /////////////////////////////////////////////////////////////////////
150     // now <e> points to the character AFTER the last character of the //
151     // field, either a separator, a quote, or a NUL (other other char  //
152     // after the last char in the string.                              //
153     /////////////////////////////////////////////////////////////////////
154 
155     byte_length = static_cast<int>(e - s);
156     ink_assert(byte_length >= 0);
157 
158     ///////////////////////////////////////////
159     // add the text to the list, and move on //
160     ///////////////////////////////////////////
161 
162     list->append_string(s, byte_length);
163     s = s_before_skipping_ws; // where to start next time
164     ++index;
165   }
166 
167   ////////////////////////////////////////////////////////////////////////////
168   // fall out of loop when at end of string --- three possibilities:        //
169   //   (1) at end of string after final token ("a,b,c" or "a,b,c   ")       //
170   //   (2) at end of string after final separator ("a,b,c," or "a,b,c,   ") //
171   //   (3) at end of string before any tokens ("" or "   ")                 //
172   // for cases (2) & (3), we want to return an empty token                  //
173   ////////////////////////////////////////////////////////////////////////////
174 
175   if (hit_sep || (index == 0)) {
176     ink_assert(s == l + 1);
177     list->append_string(s_before_skipping_ws, 0);
178     ++index;
179   }
180 }
181 
182 //////////////////////////////////////////////////////////////////////////////
183 //
184 //      bool HttpCompat::lookup_param_in_strlist(
185 //          StrList *param_list, char *param_name,
186 //          char *param_val, int param_val_length)
187 //
188 //      Takes a list of parameter strings, and searches each parameter list
189 //      element for the name <param_name>, and if followed by '=' and a value,
190 //      the value string is stored in <param_val> up to <param_val_length>
191 //      bytes minus 1 character for trailing NUL.
192 //
193 //      This routine can be used to search for charset=XXX, Q=XXX, and other
194 //      kinds of parameters.  The param list can be constructed using the
195 //      parse_comma_list and parse_semicolon_list functions.
196 //
197 //      The routine returns true if there was a match, false otherwise.
198 //
199 //////////////////////////////////////////////////////////////////////////////
200 
201 bool
lookup_param_in_strlist(StrList * param_list,const char * param_name,char * param_val,int param_val_length)202 HttpCompat::lookup_param_in_strlist(StrList *param_list, const char *param_name, char *param_val, int param_val_length)
203 {
204   int cnt;
205   const char *s, *t;
206   Str *param;
207   bool is_match;
208 
209   for (param = param_list->head; param != nullptr; param = param->next) {
210     /////////////////////////////////////////////////////
211     // compare this parameter to the target param_name //
212     /////////////////////////////////////////////////////
213 
214     s = param->str; // source str
215     t = param_name; // target str
216     while (*s && *t && (ParseRules::ink_tolower(*s) == ParseRules::ink_tolower(*t))) {
217       ++s;
218       ++t;
219     }
220 
221     ////////////////////////////////////////////////////////////////
222     // match if target string empty, and if current string empty,  //
223     // or points to space or '=' character.                       //
224     ////////////////////////////////////////////////////////////////
225 
226     is_match = ((!*t) && ((!*s) || ParseRules::is_ws(*s) || (*s == '=')));
227 
228     /////////////////////////////////////////////////////////////
229     // copy text after '=' into param_val, up to length limits //
230     /////////////////////////////////////////////////////////////
231 
232     if (is_match) {
233       param_val[0] = '\0';
234 
235       while (*s && ParseRules::is_ws(*s)) {
236         s++; // skip white
237       }
238       if (*s == '=') {
239         ++s; // skip '='
240         while (*s && ParseRules::is_ws(*s)) {
241           s++; // skip white
242         }
243 
244         for (cnt = 0; *s && (cnt < param_val_length - 1); s++, cnt++) {
245           param_val[cnt] = *s;
246         }
247         if (cnt < param_val_length) {
248           param_val[cnt++] = '\0';
249         }
250       }
251       return (true);
252     }
253   }
254 
255   return (false);
256 }
257 
258 //////////////////////////////////////////////////////////////////////////////
259 //
260 //      bool HttpCompat::lookup_param_in_semicolon_string(
261 //          char *semicolon_string, int semicolon_string_len,
262 //          char *param_name, char *param_val, int param_val_length)
263 //
264 //      Takes a semicolon-separated string of parameters, and searches
265 //      for a parameter named <param_name>, as in lookup_param_in_strlist.
266 //
267 //      The routine returns true if there was a match, false otherwise.
268 //      If multiple parameters will be searched for in the same string,
269 //      use lookup_param_in_strlist(), so the string is not tokenized
270 //      multiple times.
271 //
272 //////////////////////////////////////////////////////////////////////////////
273 
274 bool
lookup_param_in_semicolon_string(const char * semicolon_string,int semicolon_string_len,const char * param_name,char * param_val,int param_val_length)275 HttpCompat::lookup_param_in_semicolon_string(const char *semicolon_string, int semicolon_string_len, const char *param_name,
276                                              char *param_val, int param_val_length)
277 {
278   StrList l;
279   bool result;
280 
281   parse_semicolon_list(&l, semicolon_string, semicolon_string_len);
282   result = lookup_param_in_strlist(&l, param_name, param_val, param_val_length);
283   return (result);
284 }
285 
286 //////////////////////////////////////////////////////////////////////////////
287 //
288 //      void HttpCompat::parse_mime_type(
289 //          char *mime_string, char *type, char *subtype,
290 //          int type_len, int subtype_len)
291 //
292 //      This routine takes a pointer to a MIME type, and decomposes it
293 //      into type and subtype fields, skipping over spaces, and placing
294 //      the decomposed values into <type> and <subtype>.  The length
295 //      fields describe the lengths of the type and subtype buffers,
296 //      including the trailing NUL characters.
297 //
298 //////////////////////////////////////////////////////////////////////////////
299 
300 void
parse_mime_type(const char * mime_string,char * type,char * subtype,int type_len,int subtype_len)301 HttpCompat::parse_mime_type(const char *mime_string, char *type, char *subtype, int type_len, int subtype_len)
302 {
303   const char *s, *e;
304   char *d;
305 
306   *type = *subtype = '\0';
307 
308   /////////////////////
309   // skip whitespace //
310   /////////////////////
311 
312   for (s = mime_string; *s && ParseRules::is_ws(*s); s++) {
313     ;
314   }
315 
316   ///////////////////////////////////////////////////////////////////////
317   // scan type (until NUL, out of room, comma/semicolon, space, slash) //
318   ///////////////////////////////////////////////////////////////////////
319 
320   d = type;
321   e = type + type_len;
322   while (*s && (d < e - 1) && (!ParseRules::is_ws(*s)) && (*s != ';') && (*s != ',') && (*s != '/')) {
323     *d++ = *s++;
324   }
325   *d++ = '\0';
326 
327   //////////////////////////////////////////////////////////////
328   // skip remainder of text and space, then slash, then space //
329   //////////////////////////////////////////////////////////////
330 
331   while (*s && (*s != ';') && (*s != ',') && (*s != '/')) {
332     ++s;
333   }
334   if (*s == '/') {
335     ++s;
336   }
337   while (*s && ParseRules::is_ws(*s)) {
338     ++s;
339   }
340 
341   //////////////////////////////////////////////////////////////////////////
342   // scan subtype (until NUL, out of room, comma/semicolon, space, slash) //
343   //////////////////////////////////////////////////////////////////////////
344 
345   d = subtype;
346   e = subtype + subtype_len;
347   while (*s && (d < e - 1) && (!ParseRules::is_ws(*s)) && (*s != ';') && (*s != ',') && (*s != '/')) {
348     *d++ = *s++;
349   }
350   *d++ = '\0';
351 }
352 
353 void
parse_mime_type_with_len(const char * mime_string,int mime_string_len,char * type,char * subtype,int type_len,int subtype_len)354 HttpCompat::parse_mime_type_with_len(const char *mime_string, int mime_string_len, char *type, char *subtype, int type_len,
355                                      int subtype_len)
356 {
357   const char *s, *s_toofar, *e;
358   char *d;
359 
360   *type = *subtype = '\0';
361   s_toofar         = mime_string + mime_string_len;
362 
363   /////////////////////
364   // skip whitespace //
365   /////////////////////
366 
367   for (s = mime_string; (s < s_toofar) && ParseRules::is_ws(*s); s++) {
368     ;
369   }
370 
371   ///////////////////////////////////////////////////////////////////////
372   // scan type (until NUL, out of room, comma/semicolon, space, slash) //
373   ///////////////////////////////////////////////////////////////////////
374 
375   d = type;
376   e = type + type_len;
377   while ((s < s_toofar) && (d < e - 1) && (!ParseRules::is_ws(*s)) && (*s != ';') && (*s != ',') && (*s != '/')) {
378     *d++ = *s++;
379   }
380   *d++ = '\0';
381 
382   //////////////////////////////////////////////////////////////
383   // skip remainder of text and space, then slash, then space //
384   //////////////////////////////////////////////////////////////
385 
386   while ((s < s_toofar) && (*s != ';') && (*s != ',') && (*s != '/')) {
387     ++s;
388   }
389   if ((s < s_toofar) && (*s == '/')) {
390     ++s;
391   }
392   while ((s < s_toofar) && ParseRules::is_ws(*s)) {
393     ++s;
394   }
395 
396   //////////////////////////////////////////////////////////////////////////
397   // scan subtype (until NUL, out of room, comma/semicolon, space, slash) //
398   //////////////////////////////////////////////////////////////////////////
399 
400   d = subtype;
401   e = subtype + subtype_len;
402   while ((s < s_toofar) && (d < e - 1) && (!ParseRules::is_ws(*s)) && (*s != ';') && (*s != ',') && (*s != '/')) {
403     *d++ = *s++;
404   }
405   *d++ = '\0';
406 }
407 
408 //////////////////////////////////////////////////////////////////////////////
409 //
410 //      bool HttpCompat::do_vary_header_values_match(MIMEField *hv1, MIMEField *hv2)
411 //
412 //      This routine takes two HTTP header fields and determines
413 //      if their values "match", as in section 14.43 of RFC2068:
414 //
415 //        "When the cache receives a subsequent request whose Request-URI
416 //         specifies one or more cache entries including a Vary header, the
417 //         cache MUST NOT use such a cache entry to construct a response to
418 //         the new request unless all of the headers named in the cached
419 //         Vary header are present in the new request, and all of the stored
420 //         selecting request-headers from the previous request match the
421 //         corresponding headers in the new request.
422 //
423 //         The selecting request-headers from two requests are defined to
424 //         match if and only if the selecting request-headers in the first
425 //         request can be transformed to the selecting request-headers in
426 //         the second request by adding or removing linear whitespace (LWS)
427 //         at places where this is allowed by the corresponding BNF, and/or
428 //         combining multiple message-header fields with the same field
429 //         name following the rules about message headers in section 4.2."
430 //
431 //////////////////////////////////////////////////////////////////////////////
432 bool
do_vary_header_values_match(MIMEField * hdr1,MIMEField * hdr2)433 HttpCompat::do_vary_header_values_match(MIMEField *hdr1, MIMEField *hdr2)
434 {
435   // If both headers are missing, the headers match.
436   if (!hdr1 && !hdr2) {
437     return true;
438   }
439 
440   // If one header is missing, the headers do not match.
441   if (!hdr1 || !hdr2) {
442     return false;
443   }
444 
445   // Make sure both headers have the same number of comma-
446   // separated elements.
447   HdrCsvIter iter1, iter2;
448   if (iter1.count_values(hdr1) != iter2.count_values(hdr2)) {
449     return false;
450   }
451 
452   int hdr1_val_len, hdr2_val_len;
453   const char *hdr1_val = iter1.get_first(hdr1, &hdr1_val_len);
454   const char *hdr2_val = iter2.get_first(hdr2, &hdr2_val_len);
455 
456   while (hdr1_val || hdr2_val) {
457     if (!hdr1_val || !hdr2_val || hdr1_val_len != hdr2_val_len ||
458         ParseRules::strncasecmp_eow(hdr1_val, hdr2_val, hdr1_val_len) == false) {
459       return false;
460     }
461 
462     hdr1_val = iter1.get_next(&hdr1_val_len);
463     hdr2_val = iter2.get_next(&hdr2_val_len);
464   }
465 
466   return true;
467 }
468 
469 //////////////////////////////////////////////////////////////////////////////
470 //
471 //      float HttpCompat::find_Q_param_in_strlist(StrList *strlist);
472 //
473 //      Takes a StrList formed from semicolon-parsing a value, and returns
474 //      the value of the Q directive, or 1.0 by default.
475 //
476 //////////////////////////////////////////////////////////////////////////////
477 
478 float
find_Q_param_in_strlist(StrList * strlist)479 HttpCompat::find_Q_param_in_strlist(StrList *strlist)
480 {
481   float f, this_q;
482   char q_string[8];
483 
484   this_q = 1.0;
485   if (HttpCompat::lookup_param_in_strlist(strlist, (char *)"q", q_string, sizeof(q_string))) {
486     // coverity[secure_coding]
487     if (sscanf(q_string, "%f", &f) == 1) { // parse q
488       this_q = (f < 0 ? 0 : (f > 1 ? 1 : f));
489     }
490   }
491 
492   return (this_q);
493 }
494 
495 //////////////////////////////////////////////////////////////////////////////
496 //
497 //      float HttpCompat::match_accept_language
498 //
499 //      This routine returns the resulting Q factor from matching the
500 //      content language tag <lang_str> against the Accept-Language value
501 //      string <acpt_str>.
502 //
503 //      It also returns the index of the particular accept list piece
504 //      that matches, and the length of the accept list piece that matches,
505 //      in case you later want to resolve quality ties by position in the
506 //      list, or by length of match.  In general, you want to sort the
507 //      results of this call first by chosen Q, then by matching_length
508 //      (longer is better), then by matching_index (lower is better).
509 //      The first matching_index value is index 1.
510 //
511 //////////////////////////////////////////////////////////////////////////////
512 
513 static inline bool
does_language_range_match(const char * pattern,int pattern_len,const char * tag,int tag_len)514 does_language_range_match(const char *pattern, int pattern_len, const char *tag, int tag_len)
515 {
516   bool match;
517 
518   while (pattern_len && tag_len && (ParseRules::ink_tolower(*pattern) == ParseRules::ink_tolower(*tag))) {
519     ++pattern;
520     ++tag;
521     --pattern_len;
522     --tag_len;
523   }
524 
525   // matches if range equals tag, or if range is a lang prefix of tag
526   if ((((pattern_len == 0) && (tag_len == 0)) || ((pattern_len == 0) && (*tag == '-')))) {
527     match = true;
528   } else {
529     match = false;
530   }
531 
532   return (match);
533 }
534 
535 float
match_accept_language(const char * lang_str,int lang_len,StrList * acpt_lang_list,int * matching_length,int * matching_index,bool ignore_wildcards)536 HttpCompat::match_accept_language(const char *lang_str, int lang_len, StrList *acpt_lang_list, int *matching_length,
537                                   int *matching_index, bool ignore_wildcards)
538 {
539   float Q, Q_wild;
540   Str *a_value;
541 
542   Q                     = -1; // will never be returned as -1
543   Q_wild                = -1; // will never be returned as -1
544   int match_count       = 0;
545   int wild_match_count  = 0;
546   int longest_match_len = 0;
547 
548   int index        = 0;
549   int Q_index      = 0;
550   int Q_wild_index = 0;
551 
552   *matching_index  = 0;
553   *matching_length = 0;
554 
555   ///////////////////////////////////////////////////////
556   // rip the accept string into comma-separated values //
557   ///////////////////////////////////////////////////////
558   if (acpt_lang_list->count == 0) {
559     return (0.0);
560   }
561 
562   ////////////////////////////////////////
563   // loop over each Accept-Language tag //
564   ////////////////////////////////////////
565   for (a_value = acpt_lang_list->head; a_value; a_value = a_value->next) {
566     ++index;
567 
568     if (a_value->len == 0) {
569       continue; // blank tag
570     }
571 
572     ///////////////////////////////////////////////////////////
573     // now rip the Accept-Language tag into head and Q parts //
574     ///////////////////////////////////////////////////////////
575     StrList a_param_list(false);
576     HttpCompat::parse_semicolon_list(&a_param_list, a_value->str, static_cast<int>(a_value->len));
577     if (!a_param_list.head) {
578       continue;
579     }
580 
581     /////////////////////////////////////////////////////////////////////
582     // This algorithm is a bit weird --- the resulting Q factor is     //
583     // the Q value corresponding to the LONGEST range field that       //
584     // matched, or if none matched, then the Q value of any asterisk.  //
585     // Also, if the lang value is "", meaning that no Content-Language //
586     // was specified, this document matches all accept headers.        //
587     /////////////////////////////////////////////////////////////////////
588     const char *atag_str = a_param_list.head->str;
589     int atag_len         = static_cast<int>(a_param_list.head->len);
590 
591     float tq = HttpCompat::find_Q_param_in_strlist(&a_param_list);
592 
593     if ((atag_len == 1) && (atag_str[0] == '*')) // wildcard
594     {
595       ++wild_match_count;
596       if (tq > Q_wild) {
597         Q_wild       = tq;
598         Q_wild_index = index;
599       }
600     } else if (does_language_range_match(atag_str, atag_len, lang_str, lang_len)) {
601       ++match_count;
602       if (atag_len > longest_match_len) {
603         longest_match_len = atag_len;
604         Q                 = tq;
605         Q_index           = index;
606       } else if (atag_len == longest_match_len) // if tie, pick higher Q
607       {
608         if (tq > Q) {
609           Q       = tq;
610           Q_index = index;
611         }
612       }
613     }
614   }
615 
616   if ((ignore_wildcards == false) && wild_match_count && !match_count) {
617     *matching_index  = Q_wild_index;
618     *matching_length = 1;
619     return (Q_wild);
620   } else if (match_count > 0) // real match
621   {
622     *matching_index  = Q_index;
623     *matching_length = longest_match_len;
624     return (Q);
625   } else // no match
626   {
627     *matching_index  = 0;
628     *matching_length = 0;
629     return (0.0);
630   }
631 }
632 
633 //////////////////////////////////////////////////////////////////////////////
634 //
635 //      float HttpCompat::match_accept_charset
636 //
637 //      This routine returns the resulting Q factor from matching the
638 //      content language tag <lang_str> against the Accept-Language value
639 //      string <acpt_str>.
640 //
641 //      It also returns the index of the particular accept list piece
642 //      that matches, and the length of the accept list piece that matches,
643 //      in case you later want to resolve quality ties by position in the
644 //      list, or by length of match.  In general, you want to sort the
645 //      results of this call first by chosen Q, then by matching_length
646 //      (longer is better), then by matching_index (lower is better).
647 //      The first matching_index value is index 1.
648 //
649 //////////////////////////////////////////////////////////////////////////////
650 
651 // FIX: not implemented!
652 
653 float
match_accept_charset(const char * charset_str,int charset_len,StrList * acpt_charset_list,int * matching_index,bool ignore_wildcards)654 HttpCompat::match_accept_charset(const char *charset_str, int charset_len, StrList *acpt_charset_list, int *matching_index,
655                                  bool ignore_wildcards)
656 {
657   float Q, Q_wild;
658   Str *a_value;
659 
660   Q                    = -1; // will never be returned as -1
661   Q_wild               = -1; // will never be returned as -1
662   int match_count      = 0;
663   int wild_match_count = 0;
664 
665   int index        = 0;
666   int Q_index      = 0;
667   int Q_wild_index = 0;
668 
669   *matching_index = 0;
670 
671   ///////////////////////////////////////////////////////
672   // rip the accept string into comma-separated values //
673   ///////////////////////////////////////////////////////
674   if (acpt_charset_list->count == 0) {
675     return (0.0);
676   }
677 
678   ///////////////////////////////////////
679   // loop over each Accept-Charset tag //
680   ///////////////////////////////////////
681   for (a_value = acpt_charset_list->head; a_value; a_value = a_value->next) {
682     ++index;
683     if (a_value->len == 0) {
684       continue; // blank tag
685     }
686 
687     //////////////////////////////////////////////////////////
688     // now rip the Accept-Charset tag into head and Q parts //
689     //////////////////////////////////////////////////////////
690     StrList a_param_list(false);
691     HttpCompat::parse_semicolon_list(&a_param_list, a_value->str, static_cast<int>(a_value->len));
692     if (!a_param_list.head) {
693       continue;
694     }
695 
696     ///////////////////////////////////////////////////////////////
697     // see if the Accept-Charset tag matches the current charset //
698     ///////////////////////////////////////////////////////////////
699     const char *atag_str = a_param_list.head->str;
700     int atag_len         = static_cast<int>(a_param_list.head->len);
701     float tq             = HttpCompat::find_Q_param_in_strlist(&a_param_list);
702 
703     if ((atag_len == 1) && (atag_str[0] == '*')) // wildcard
704     {
705       ++wild_match_count;
706       if (tq > Q_wild) {
707         Q_wild       = tq;
708         Q_wild_index = index;
709       }
710     } else if ((atag_len == charset_len) && (strncasecmp(atag_str, charset_str, charset_len) == 0)) {
711       ++match_count;
712       if (tq > Q) {
713         Q       = tq;
714         Q_index = index;
715       }
716     }
717   }
718 
719   if ((ignore_wildcards == false) && wild_match_count && !match_count) {
720     *matching_index = Q_wild_index;
721     return (Q_wild);
722   } else if (match_count > 0) // real match
723   {
724     *matching_index = Q_index;
725     return (Q);
726   } else // no match
727   {
728     *matching_index = 0;
729     return (0.0);
730   }
731 }
732