Annotation of libwww/Library/src/HTMulti.c, revision 2.37
2.29 frystyk 1: /*
2: ** CONTENT NEGOTIATION
2.11 frystyk 3: **
2.15 frystyk 4: ** (c) COPYRIGHT MIT 1995.
2.11 frystyk 5: ** Please first read the full copyright statement in the file COPYRIGH.
2.37 ! kahan 6: ** @(#) $Id: HTMulti.c,v 2.36 2000/01/06 10:48:50 kahan Exp $
2.1 luotonen 7: **
8: ** History:
9: ** March 94 AL Separated from HTFile.c because
10: ** multiformat handling would be a mess in VMS.
11: */
12:
2.13 frystyk 13: /* Library include files */
2.32 frystyk 14: #include "wwwsys.h"
2.28 frystyk 15: #include "WWWUtil.h"
16: #include "WWWCore.h"
2.2 duns 17: #include "HTMulti.h"
2.34 frystyk 18: #include "HTBind.h"
2.20 frystyk 19: #include "HTFile.h"
2.1 luotonen 20:
2.29 frystyk 21: #define MULTI_SUFFIX ".multi"/* Extension for scanning formats */
22: #define MAX_SUFF 15 /* Maximum number of suffixes for a file */
23: #define VARIANTS 4 /* We start with this array size */
24:
25: typedef struct _HTContentDescription {
26: char * filename;
27: HTFormat content_type;
28: HTLanguage content_language;
29: HTEncoding content_encoding;
30: HTEncoding content_transfer;
31: int content_length;
32: double quality;
33: } HTContentDescription;
34:
2.3 luotonen 35: PRIVATE HTList * welcome_names = NULL; /* Welcome.html, index.html etc. */
36:
2.29 frystyk 37: /* ------------------------------------------------------------------------- */
38:
39: /*
40: ** Sort the q values in descending order
41: */
42: PRIVATE int VariantSort (const void * a, const void * b)
43: {
44: HTContentDescription * aa = *(HTContentDescription **) a;
45: HTContentDescription * bb = *(HTContentDescription **) b;
2.33 frystyk 46: if (aa && bb) return (aa->quality > bb->quality) ? -1 : 1;
47: return bb - aa;
2.29 frystyk 48: }
49:
50: /*
51: * Added by takada@seraph.ntt.jp (94/04/08)
52: */
53: PRIVATE BOOL lang_match (HTAtom * tmplate, HTAtom * actual)
54: {
55: const char *t, *a;
56: char *st, *sa;
57: BOOL match = NO;
58:
59: if (tmplate && actual &&
60: (t = HTAtom_name(tmplate)) && (a = HTAtom_name(actual))) {
61: st = strchr(t, '_');
62: sa = strchr(a, '_');
63: if ((st != NULL) && (sa != NULL)) {
64: if (!strcasecomp(t, a))
65: match = YES;
66: else
67: match = NO;
68: }
69: else {
70: if (st != NULL) *st = 0;
71: if (sa != NULL) *sa = 0;
72: if (!strcasecomp(t, a))
73: match = YES;
74: else
75: match = NO;
76: if (st != NULL) *st = '_';
77: if (sa != NULL) *sa = '_';
78: }
79: }
80: return match;
81: }
82:
83: PRIVATE double type_value (HTAtom * content_type, HTList * accepted)
84: {
85: if (!content_type) return (1.0);
86: if (accepted) {
87: HTList * cur = accepted;
88: HTPresentation * pres;
89: HTPresentation * wild = NULL;
90: while ((pres = (HTPresentation *) HTList_nextObject(cur))) {
91: if (pres->rep == content_type)
92: return pres->quality;
2.30 frystyk 93: else if (HTMIMEMatch(pres->rep, content_type))
2.29 frystyk 94: wild = pres;
95: }
96: if (wild) return wild->quality;
97: else return (0.0); /* Nothing matched */
98: }
99: return (1.0); /* We accept all types */
100: }
101:
102: PRIVATE double lang_value (HTAtom * language, HTList * accepted)
103: {
104: if (!language) return (1.0);
105: if (accepted) {
106: HTList * cur = accepted;
107: HTAcceptNode * node;
108: HTAcceptNode * wild = NULL;
109: while ((node = (HTAcceptNode *) HTList_nextObject(cur))) {
110: if (node->atom == language)
111: return node->quality;
112: /*
113: * patch by takada@seraph.ntt.jp (94/04/08)
114: * the original line was
2.30 frystyk 115: * else if (HTMIMEMatch(node->atom, language)) {
2.29 frystyk 116: * and the new line is
117: */
118: else if (lang_match(node->atom, language))
119: wild = node;
120: }
121: if (wild) return wild->quality;
122: else return (0.0); /* Nothing matched */
123: }
124: return (1.0); /* We accept all languages */
125: }
126:
127: PRIVATE double encoding_value (HTAtom * encoding, HTList * accepted)
128: {
129: if (!encoding) return (1.0);
130: if (accepted) {
131: HTList * cur = accepted;
132: HTAcceptNode * node;
133: HTAcceptNode * wild = NULL;
134: const char * e = HTAtom_name(encoding);
135: if (!strcmp(e, "7bit") || !strcmp(e, "8bit") || !strcmp(e, "binary"))
136: return (1.0);
137: while ((node = (HTAcceptNode*)HTList_nextObject(cur))) {
138: if (node->atom == encoding)
139: return node->quality;
2.30 frystyk 140: else if (HTMIMEMatch(node->atom, encoding))
2.29 frystyk 141: wild = node;
142: }
143: if (wild) return wild->quality;
144: else return (0.0); /* Nothing matched */
145: }
146: return (1.0); /* We accept all encodings */
147: }
148:
149: PRIVATE BOOL HTRank (HTRequest * request, HTArray * variants)
150: {
151: HTContentDescription * cd;
152: void ** data;
153: if (!variants) {
2.35 frystyk 154: HTTRACE(PROT_TRACE, "Ranking..... No variants\n");
2.29 frystyk 155: return NO;
156: }
157: /*
158: ** Walk through the list of local and global preferences and find the
159: ** overall q factor for each variant
160: */
161: cd = (HTContentDescription *) HTArray_firstObject(variants, data);
162: while (cd) {
163: double ctq_local = type_value(cd->content_type, HTRequest_conversion(request));
164: double ctq_global = type_value(cd->content_type, HTFormat_conversion());
165: double clq_local = lang_value(cd->content_language, HTRequest_language(request));
166: double clq_global = lang_value(cd->content_language, HTFormat_language());
167: double ceq_local = encoding_value(cd->content_encoding, HTRequest_encoding(request));
168: double ceq_global = encoding_value(cd->content_encoding, HTFormat_contentCoding());
2.35 frystyk 169: HTTRACE(PROT_TRACE, "Qualities... Content type: %.3f, Content language: %.3f, Content encoding: %.3f\n" _
170: HTMAX(ctq_local, ctq_global) _
171: HTMAX(clq_local, clq_global) _
2.29 frystyk 172: HTMAX(ceq_local, ceq_global));
173: cd->quality *= (HTMAX(ctq_local, ctq_global) *
174: HTMAX(clq_local, clq_global) *
175: HTMAX(ceq_local, ceq_global));
176: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
177: }
178:
179: /* Sort the array of all our accepted preferences */
180: HTArray_sort(variants, VariantSort);
181:
182: /* Write out the result */
2.35 frystyk 183: #ifdef HTDEBUG
2.29 frystyk 184: if (PROT_TRACE) {
185: int cnt = 1;
186: cd = (HTContentDescription *) HTArray_firstObject(variants, data);
2.35 frystyk 187: HTTRACE(PROT_TRACE, "Ranking.....\n");
188: HTTRACE(PROT_TRACE, "RANK QUALITY CONTENT-TYPE LANGUAGE ENCODING FILE\n");
2.29 frystyk 189: while (cd) {
2.35 frystyk 190: HTTRACE(PROT_TRACE, "%d. %.4f %-20.20s %-8.8s %-10.10s %s\n" _
191: cnt++ _
192: cd->quality _
193: cd->content_type ? HTAtom_name(cd->content_type) : "-" _
194: cd->content_language?HTAtom_name(cd->content_language):"-" _
195: cd->content_encoding?HTAtom_name(cd->content_encoding):"-" _
2.29 frystyk 196: cd->filename ? cd->filename :"-");
197: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
198: }
199: }
2.35 frystyk 200: #endif /* HTDEBUG */
2.29 frystyk 201: return YES;
202: }
2.3 luotonen 203:
2.14 frystyk 204: /* PUBLIC HTSplitFilename()
205: **
206: ** Split the filename to an array of suffixes.
207: ** Return the number of parts placed to the array.
208: ** Array should have MAX_SUFF+1 items.
209: */
2.23 frystyk 210: PRIVATE int HTSplitFilename (char * s_str, char ** s_arr)
2.14 frystyk 211: {
2.26 frystyk 212: const char *delimiters = HTBind_delimiters();
2.14 frystyk 213: char * start = s_str;
214: char * end;
215: char save;
216: int i;
217:
218: if (!s_str || !s_arr) return 0;
219:
220: for (i=0; i < MAX_SUFF && *start; i++) {
221: for(end=start+1; *end && !strchr(delimiters, *end); end++);
222: save = *end;
223: *end = 0;
224: StrAllocCopy(s_arr[i], start); /* Frees the previous value */
225: *end = save;
226: start = end;
227: }
2.24 frystyk 228: HT_FREE(s_arr[i]); /* Terminating NULL */
2.14 frystyk 229: return i;
230: }
231:
232:
2.3 luotonen 233: /*
234: ** Set default file name for welcome page on each directory.
235: */
2.23 frystyk 236: PUBLIC void HTAddWelcome (char * name)
2.3 luotonen 237: {
238: if (name) {
239: char * mycopy = NULL;
240: StrAllocCopy(mycopy,name);
241:
242: if (!welcome_names)
243: welcome_names = HTList_new();
244: HTList_addObject(welcome_names, (void*)mycopy);
245: }
246: }
247:
248:
2.26 frystyk 249: #ifdef HAVE_READDIR
2.1 luotonen 250:
251: /* PRIVATE multi_match()
252: **
253: ** Check if actual filename (split in parts) fulfills
254: ** the requirements.
255: */
2.23 frystyk 256: PRIVATE BOOL multi_match (char ** required, int m, char ** actual, int n)
2.1 luotonen 257: {
258: int c;
259: int i,j;
260:
2.2 duns 261: #ifdef VMS
262: for(c=0; c<m && c<n && !strcasecomp(required[c], actual[c]); c++);
263: #else /* not VMS */
2.1 luotonen 264: for(c=0; c<m && c<n && !strcmp(required[c], actual[c]); c++);
2.2 duns 265: #endif /* not VMS */
2.1 luotonen 266:
267: if (!c) return NO; /* Names differ rigth from start */
268:
269: for(i=c; i<m; i++) {
270: BOOL found = NO;
271: for(j=c; j<n; j++) {
2.2 duns 272: #ifdef VMS
273: if (!strcasecomp(required[i], actual[j])) {
274: #else /* not VMS */
2.1 luotonen 275: if (!strcmp(required[i], actual[j])) {
2.2 duns 276: #endif /* not VMS */
2.1 luotonen 277: found = YES;
278: break;
279: }
280: }
281: if (!found) return NO;
282: }
283: return YES;
284: }
285:
286:
287: /*
288: ** Get multi-match possibilities for a given file
289: ** ----------------------------------------------
290: ** On entry:
291: ** path absolute path to one file in a directory,
292: ** may end in .multi.
293: ** On exit:
294: ** returns a list of ContentDesription structures
295: ** describing the mathing files.
296: **
297: */
2.29 frystyk 298: PRIVATE HTArray * dir_matches (char * path)
2.1 luotonen 299: {
300: static char * required[MAX_SUFF+1];
301: static char * actual[MAX_SUFF+1];
302: int m,n;
303: char * dirname = NULL;
304: char * basename = NULL;
305: int baselen;
306: char * multi = NULL;
307: DIR * dp;
2.26 frystyk 308: struct dirent * dirbuf;
2.29 frystyk 309: HTArray * matches = NULL;
2.16 frystyk 310: #ifdef HT_REENTRANT
2.31 frystyk 311: struct dirent result; /* For readdir_r */
2.16 frystyk 312: #endif
2.1 luotonen 313:
314: if (!path) return NULL;
315:
316: StrAllocCopy(dirname, path);
317: basename = (strrchr(dirname, '/'));
318: if (!basename)
319: goto dir_match_failed;
320: *basename++ = 0;
321:
322: multi = strrchr(basename, MULTI_SUFFIX[0]);
2.2 duns 323: if (multi && !strcasecomp(multi, MULTI_SUFFIX))
2.1 luotonen 324: *multi = 0;
325: baselen = strlen(basename);
326:
327: m = HTSplitFilename(basename, required);
328:
329: dp = opendir(dirname);
330: if (!dp) {
2.35 frystyk 331: HTTRACE(PROT_TRACE, "Warning..... Can't open directory %s\n" _ dirname);
2.1 luotonen 332: goto dir_match_failed;
333: }
334:
2.29 frystyk 335: matches = HTArray_new(VARIANTS);
2.36 kahan 336: #ifdef HAVE_READDIR_R_2
2.31 frystyk 337: while ((dirbuf = (struct dirent *) readdir_r(dp, &result))) {
2.36 kahan 338: #elif defined(HAVE_READDIR_R_3)
339: while (readdir_r(dp, &result, &dirbuf) == 0) {
2.16 frystyk 340: #else
341: while ((dirbuf = readdir(dp))) {
2.36 kahan 342: #endif /* HAVE_READDIR_R_2 */
2.1 luotonen 343: if (!dirbuf->d_ino) continue; /* Not in use */
2.3 luotonen 344: if (!strcmp(dirbuf->d_name,".") ||
345: !strcmp(dirbuf->d_name,"..") ||
2.20 frystyk 346: !strcmp(dirbuf->d_name, DEFAULT_DIR_FILE))
2.3 luotonen 347: continue;
2.7 frystyk 348:
2.13 frystyk 349: /* Use of direct->namlen is only valid in BSD'ish system */
350: /* Thanks to chip@chinacat.unicom.com (Chip Rosenthal) */
351: /* if ((int)(dirbuf->d_namlen) >= baselen) { */
352: if ((int) strlen(dirbuf->d_name) >= baselen) {
2.1 luotonen 353: n = HTSplitFilename(dirbuf->d_name, actual);
354: if (multi_match(required, m, actual, n)) {
355: HTContentDescription * cd;
2.29 frystyk 356: if ((cd = (HTContentDescription *)
357: HT_CALLOC(1, sizeof(HTContentDescription))) == NULL)
358: HT_OUTOFMEM("dir_matches");
359: if (HTBind_getFormat(dirbuf->d_name,
360: &cd->content_type,
361: &cd->content_encoding,
362: &cd->content_transfer,
363: &cd->content_language,
364: &cd->quality)) {
2.1 luotonen 365: if (cd->content_type) {
2.24 frystyk 366: if ((cd->filename = (char *) HT_MALLOC(strlen(dirname) + 2 + strlen(dirbuf->d_name))) == NULL)
367: HT_OUTOFMEM("dir_matches");
2.29 frystyk 368: sprintf(cd->filename, "%s/%s", dirname, dirbuf->d_name);
369: HTArray_addObject(matches, (void *) cd);
370: } else {
371: HT_FREE(cd);
2.1 luotonen 372: }
2.29 frystyk 373: } else {
374: HT_FREE(cd);
2.1 luotonen 375: }
376: }
377: }
378: }
379: closedir(dp);
380:
381: dir_match_failed:
2.24 frystyk 382: HT_FREE(dirname);
2.1 luotonen 383: return matches;
384: }
385:
386:
387: /*
388: ** Get the best match for a given file
389: ** -----------------------------------
390: ** On entry:
391: ** req->conversions accepted content-types
392: ** req->encodings accepted content-transfer-encodings
393: ** req->languages accepted content-languages
394: ** path absolute pathname of the filename for
395: ** which the match is desired.
396: ** On exit:
397: ** returns a newly allocated absolute filepath.
398: */
2.23 frystyk 399: PRIVATE char * HTGetBest (HTRequest * req, char * path)
2.1 luotonen 400: {
2.29 frystyk 401: HTArray * variants = NULL;
402: char * representation = NULL;
2.1 luotonen 403:
2.13 frystyk 404: if (!path || !*path) return NULL;
2.1 luotonen 405:
2.29 frystyk 406: if ((variants = dir_matches(path)) == NULL) {
2.35 frystyk 407: HTTRACE(PROT_TRACE, "No matches.. for \"%s\"\n" _ path);
2.13 frystyk 408: return NULL;
2.1 luotonen 409: }
410:
2.35 frystyk 411: #ifdef HTDEBUG
2.29 frystyk 412: if (PROT_TRACE) {
413: void ** data;
414: HTContentDescription * cd = HTArray_firstObject(variants, data);
2.35 frystyk 415: HTTRACE(PROT_TRACE, "Multi....... Possibilities for \"%s\"\n" _ path);
416: HTTRACE(PROT_TRACE, " QUALITY CONTENT-TYPE LANGUAGE ENCODING FILE\n");
2.29 frystyk 417: while (cd) {
2.35 frystyk 418: HTTRACE(PROT_TRACE, " %.4f %-20.20s %-8.8s %-10.10s %s\n" _
419: cd->quality _
420: cd->content_type ?HTAtom_name(cd->content_type) :"-\t" _
421: cd->content_language?HTAtom_name(cd->content_language):"-" _
422: cd->content_encoding?HTAtom_name(cd->content_encoding):"-" _
2.29 frystyk 423: cd->filename ?cd->filename :"-");
424: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
425: }
2.1 luotonen 426: }
2.35 frystyk 427: #endif /* HTDEBUG */
2.1 luotonen 428:
429: /*
2.29 frystyk 430: ** Finally get the best variant which is readable
2.1 luotonen 431: */
2.29 frystyk 432: if (HTRank(req, variants)) {
2.37 ! kahan 433: void ** data = NULL;
2.29 frystyk 434: HTContentDescription * cd = HTArray_firstObject(variants, data);
435: while (cd) {
436: if (cd->filename) {
437: if (access(cd->filename, R_OK) != -1)
438: StrAllocCopy(representation, cd->filename);
2.35 frystyk 439: else HTTRACE(PROT_TRACE, "Multi....... `%s\' is not readable\n" _
2.29 frystyk 440: cd->filename);
2.1 luotonen 441: }
2.29 frystyk 442: HT_FREE(cd->filename);
443: HT_FREE(cd);
444: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
2.1 luotonen 445: }
446: }
2.29 frystyk 447: HTArray_delete(variants);
448: return representation;
2.1 luotonen 449: }
450:
2.3 luotonen 451:
452:
2.23 frystyk 453: PRIVATE int welcome_value (char * name)
2.3 luotonen 454: {
455: HTList * cur = welcome_names;
456: char * welcome;
457: int v = 0;
458:
459: while ((welcome = (char*)HTList_nextObject(cur))) {
460: v++;
461: if (!strcmp(welcome,name)) return v;
462: }
463: return 0;
464: }
465:
466:
467:
2.23 frystyk 468: PRIVATE char * get_best_welcome (char * path)
2.3 luotonen 469: {
470: char * best_welcome = NULL;
471: int best_value = 0;
472: DIR * dp;
2.26 frystyk 473: struct dirent * dirbuf;
2.3 luotonen 474: char * last = strrchr(path, '/');
475:
476: if (!welcome_names) {
477: HTAddWelcome("Welcome.html");
478: HTAddWelcome("welcome.html");
2.5 luotonen 479: #if 0
2.3 luotonen 480: HTAddWelcome("Index.html");
2.5 luotonen 481: #endif
2.3 luotonen 482: HTAddWelcome("index.html");
483: }
484:
2.5 luotonen 485: if (last && last!=path) *last = 0;
2.3 luotonen 486: dp = opendir(path);
2.5 luotonen 487: if (last && last!=path) *last='/';
2.3 luotonen 488: if (!dp) {
2.35 frystyk 489: HTTRACE(PROT_TRACE, "Warning..... Can't open directory %s\n" _ path);
2.3 luotonen 490: return NULL;
491: }
492: while ((dirbuf = readdir(dp))) {
2.13 frystyk 493: if (!dirbuf->d_ino ||
2.3 luotonen 494: !strcmp(dirbuf->d_name,".") ||
495: !strcmp(dirbuf->d_name,"..") ||
2.20 frystyk 496: !strcmp(dirbuf->d_name, DEFAULT_DIR_FILE))
2.3 luotonen 497: continue;
498: else {
499: int v = welcome_value(dirbuf->d_name);
500: if (v > best_value) {
501: best_value = v;
502: StrAllocCopy(best_welcome, dirbuf->d_name);
503: }
504: }
505: }
506: closedir(dp);
507:
508: if (best_welcome) {
2.24 frystyk 509: char * welcome;
510: if ((welcome = (char *) HT_MALLOC(strlen(path) + strlen(best_welcome)+2)) == NULL)
511: HT_OUTOFMEM("get_best_welcome");
2.4 luotonen 512: sprintf(welcome, "%s%s%s", path, last ? "" : "/", best_welcome);
2.24 frystyk 513: HT_FREE(best_welcome);
2.35 frystyk 514: HTTRACE(PROT_TRACE, "Welcome..... \"%s\"\n" _ welcome);
2.3 luotonen 515: return welcome;
516: }
517: return NULL;
518: }
519:
2.26 frystyk 520: #endif /* HAVE_READDIR */
2.1 luotonen 521:
522:
523: /*
524: ** Do multiformat handling
525: ** -----------------------
526: ** On entry:
527: ** req->conversions accepted content-types
528: ** req->encodings accepted content-transfer-encodings
529: ** req->languages accepted content-languages
530: ** path absolute pathname of the filename for
531: ** which the match is desired.
532: ** stat_info pointer to result space.
533: **
534: ** On exit:
535: ** returns a newly allocated absolute filepath of the best
536: ** match, or NULL if no match.
537: ** stat_info will contain inode information as
538: ** returned by stat().
539: */
2.23 frystyk 540: PUBLIC char * HTMulti (HTRequest * req,
541: char * path,
542: struct stat * stat_info)
2.1 luotonen 543: {
544: char * new_path = NULL;
545: int stat_status = -1;
546:
2.3 luotonen 547: if (!req || !path || !*path || !stat_info)
2.1 luotonen 548: return NULL;
549:
2.26 frystyk 550: #ifdef HAVE_READDIR
2.19 frystyk 551: if (*(path+strlen(path)-1) == '/') { /* Find welcome page */
2.3 luotonen 552: new_path = get_best_welcome(path);
553: if (new_path) path = new_path;
2.29 frystyk 554: } else{
2.3 luotonen 555: char * multi = strrchr(path, MULTI_SUFFIX[0]);
556: if (multi && !strcasecomp(multi, MULTI_SUFFIX)) {
2.35 frystyk 557: HTTRACE(PROT_TRACE, "Multi....... by %s suffix\n" _ MULTI_SUFFIX);
2.1 luotonen 558: if (!(new_path = HTGetBest(req, path))) {
2.35 frystyk 559: HTTRACE(PROT_TRACE, "Multi....... failed -- giving up\n");
2.1 luotonen 560: return NULL;
561: }
562: path = new_path;
2.29 frystyk 563: } else {
2.18 frystyk 564: stat_status = HT_STAT(path, stat_info);
2.3 luotonen 565: if (stat_status == -1) {
2.35 frystyk 566: HTTRACE(PROT_TRACE, "AutoMulti... can't stat \"%s\"(errno %d)\n" _
567: path _ errno);
2.3 luotonen 568: if (!(new_path = HTGetBest(req, path))) {
2.35 frystyk 569: HTTRACE(PROT_TRACE, "AutoMulti... failed -- giving up\n");
2.3 luotonen 570: return NULL;
571: }
572: path = new_path;
573: }
2.1 luotonen 574: }
575: }
2.26 frystyk 576: #endif /* HAVE_READDIR */
2.1 luotonen 577:
578: if (stat_status == -1)
2.18 frystyk 579: stat_status = HT_STAT(path, stat_info);
2.1 luotonen 580: if (stat_status == -1) {
2.35 frystyk 581: HTTRACE(PROT_TRACE, "Stat fails.. on \"%s\" -- giving up (errno %d)\n" _
582: path _ errno);
2.1 luotonen 583: return NULL;
2.29 frystyk 584: } else {
2.1 luotonen 585: if (!new_path) {
586: StrAllocCopy(new_path, path);
587: return new_path;
588: }
589: else return path;
590: }
591: }
592:
593:
Webmaster