Annotation of libwww/Library/src/HTMulti.c, revision 2.35
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.35 ! frystyk 6: ** @(#) $Id: HTMulti.c,v 2.34 1999/02/07 18:24:19 frystyk 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.16 frystyk 336: #ifdef HT_REENTRANT
2.31 frystyk 337: while ((dirbuf = (struct dirent *) readdir_r(dp, &result))) {
2.16 frystyk 338: #else
339: while ((dirbuf = readdir(dp))) {
340: #endif /* HT_REENTRANT */
2.1 luotonen 341: if (!dirbuf->d_ino) continue; /* Not in use */
2.3 luotonen 342: if (!strcmp(dirbuf->d_name,".") ||
343: !strcmp(dirbuf->d_name,"..") ||
2.20 frystyk 344: !strcmp(dirbuf->d_name, DEFAULT_DIR_FILE))
2.3 luotonen 345: continue;
2.7 frystyk 346:
2.13 frystyk 347: /* Use of direct->namlen is only valid in BSD'ish system */
348: /* Thanks to chip@chinacat.unicom.com (Chip Rosenthal) */
349: /* if ((int)(dirbuf->d_namlen) >= baselen) { */
350: if ((int) strlen(dirbuf->d_name) >= baselen) {
2.1 luotonen 351: n = HTSplitFilename(dirbuf->d_name, actual);
352: if (multi_match(required, m, actual, n)) {
353: HTContentDescription * cd;
2.29 frystyk 354: if ((cd = (HTContentDescription *)
355: HT_CALLOC(1, sizeof(HTContentDescription))) == NULL)
356: HT_OUTOFMEM("dir_matches");
357: if (HTBind_getFormat(dirbuf->d_name,
358: &cd->content_type,
359: &cd->content_encoding,
360: &cd->content_transfer,
361: &cd->content_language,
362: &cd->quality)) {
2.1 luotonen 363: if (cd->content_type) {
2.24 frystyk 364: if ((cd->filename = (char *) HT_MALLOC(strlen(dirname) + 2 + strlen(dirbuf->d_name))) == NULL)
365: HT_OUTOFMEM("dir_matches");
2.29 frystyk 366: sprintf(cd->filename, "%s/%s", dirname, dirbuf->d_name);
367: HTArray_addObject(matches, (void *) cd);
368: } else {
369: HT_FREE(cd);
2.1 luotonen 370: }
2.29 frystyk 371: } else {
372: HT_FREE(cd);
2.1 luotonen 373: }
374: }
375: }
376: }
377: closedir(dp);
378:
379: dir_match_failed:
2.24 frystyk 380: HT_FREE(dirname);
2.1 luotonen 381: return matches;
382: }
383:
384:
385: /*
386: ** Get the best match for a given file
387: ** -----------------------------------
388: ** On entry:
389: ** req->conversions accepted content-types
390: ** req->encodings accepted content-transfer-encodings
391: ** req->languages accepted content-languages
392: ** path absolute pathname of the filename for
393: ** which the match is desired.
394: ** On exit:
395: ** returns a newly allocated absolute filepath.
396: */
2.23 frystyk 397: PRIVATE char * HTGetBest (HTRequest * req, char * path)
2.1 luotonen 398: {
2.29 frystyk 399: HTArray * variants = NULL;
400: char * representation = NULL;
2.1 luotonen 401:
2.13 frystyk 402: if (!path || !*path) return NULL;
2.1 luotonen 403:
2.29 frystyk 404: if ((variants = dir_matches(path)) == NULL) {
2.35 ! frystyk 405: HTTRACE(PROT_TRACE, "No matches.. for \"%s\"\n" _ path);
2.13 frystyk 406: return NULL;
2.1 luotonen 407: }
408:
2.35 ! frystyk 409: #ifdef HTDEBUG
2.29 frystyk 410: if (PROT_TRACE) {
411: void ** data;
412: HTContentDescription * cd = HTArray_firstObject(variants, data);
2.35 ! frystyk 413: HTTRACE(PROT_TRACE, "Multi....... Possibilities for \"%s\"\n" _ path);
! 414: HTTRACE(PROT_TRACE, " QUALITY CONTENT-TYPE LANGUAGE ENCODING FILE\n");
2.29 frystyk 415: while (cd) {
2.35 ! frystyk 416: HTTRACE(PROT_TRACE, " %.4f %-20.20s %-8.8s %-10.10s %s\n" _
! 417: cd->quality _
! 418: cd->content_type ?HTAtom_name(cd->content_type) :"-\t" _
! 419: cd->content_language?HTAtom_name(cd->content_language):"-" _
! 420: cd->content_encoding?HTAtom_name(cd->content_encoding):"-" _
2.29 frystyk 421: cd->filename ?cd->filename :"-");
422: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
423: }
2.1 luotonen 424: }
2.35 ! frystyk 425: #endif /* HTDEBUG */
2.1 luotonen 426:
427: /*
2.29 frystyk 428: ** Finally get the best variant which is readable
2.1 luotonen 429: */
2.29 frystyk 430: if (HTRank(req, variants)) {
431: void ** data;
432: HTContentDescription * cd = HTArray_firstObject(variants, data);
433: while (cd) {
434: if (cd->filename) {
435: if (access(cd->filename, R_OK) != -1)
436: StrAllocCopy(representation, cd->filename);
2.35 ! frystyk 437: else HTTRACE(PROT_TRACE, "Multi....... `%s\' is not readable\n" _
2.29 frystyk 438: cd->filename);
2.1 luotonen 439: }
2.29 frystyk 440: HT_FREE(cd->filename);
441: HT_FREE(cd);
442: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
2.1 luotonen 443: }
444: }
2.29 frystyk 445: HTArray_delete(variants);
446: return representation;
2.1 luotonen 447: }
448:
2.3 luotonen 449:
450:
2.23 frystyk 451: PRIVATE int welcome_value (char * name)
2.3 luotonen 452: {
453: HTList * cur = welcome_names;
454: char * welcome;
455: int v = 0;
456:
457: while ((welcome = (char*)HTList_nextObject(cur))) {
458: v++;
459: if (!strcmp(welcome,name)) return v;
460: }
461: return 0;
462: }
463:
464:
465:
2.23 frystyk 466: PRIVATE char * get_best_welcome (char * path)
2.3 luotonen 467: {
468: char * best_welcome = NULL;
469: int best_value = 0;
470: DIR * dp;
2.26 frystyk 471: struct dirent * dirbuf;
2.3 luotonen 472: char * last = strrchr(path, '/');
473:
474: if (!welcome_names) {
475: HTAddWelcome("Welcome.html");
476: HTAddWelcome("welcome.html");
2.5 luotonen 477: #if 0
2.3 luotonen 478: HTAddWelcome("Index.html");
2.5 luotonen 479: #endif
2.3 luotonen 480: HTAddWelcome("index.html");
481: }
482:
2.5 luotonen 483: if (last && last!=path) *last = 0;
2.3 luotonen 484: dp = opendir(path);
2.5 luotonen 485: if (last && last!=path) *last='/';
2.3 luotonen 486: if (!dp) {
2.35 ! frystyk 487: HTTRACE(PROT_TRACE, "Warning..... Can't open directory %s\n" _ path);
2.3 luotonen 488: return NULL;
489: }
490: while ((dirbuf = readdir(dp))) {
2.13 frystyk 491: if (!dirbuf->d_ino ||
2.3 luotonen 492: !strcmp(dirbuf->d_name,".") ||
493: !strcmp(dirbuf->d_name,"..") ||
2.20 frystyk 494: !strcmp(dirbuf->d_name, DEFAULT_DIR_FILE))
2.3 luotonen 495: continue;
496: else {
497: int v = welcome_value(dirbuf->d_name);
498: if (v > best_value) {
499: best_value = v;
500: StrAllocCopy(best_welcome, dirbuf->d_name);
501: }
502: }
503: }
504: closedir(dp);
505:
506: if (best_welcome) {
2.24 frystyk 507: char * welcome;
508: if ((welcome = (char *) HT_MALLOC(strlen(path) + strlen(best_welcome)+2)) == NULL)
509: HT_OUTOFMEM("get_best_welcome");
2.4 luotonen 510: sprintf(welcome, "%s%s%s", path, last ? "" : "/", best_welcome);
2.24 frystyk 511: HT_FREE(best_welcome);
2.35 ! frystyk 512: HTTRACE(PROT_TRACE, "Welcome..... \"%s\"\n" _ welcome);
2.3 luotonen 513: return welcome;
514: }
515: return NULL;
516: }
517:
2.26 frystyk 518: #endif /* HAVE_READDIR */
2.1 luotonen 519:
520:
521: /*
522: ** Do multiformat handling
523: ** -----------------------
524: ** On entry:
525: ** req->conversions accepted content-types
526: ** req->encodings accepted content-transfer-encodings
527: ** req->languages accepted content-languages
528: ** path absolute pathname of the filename for
529: ** which the match is desired.
530: ** stat_info pointer to result space.
531: **
532: ** On exit:
533: ** returns a newly allocated absolute filepath of the best
534: ** match, or NULL if no match.
535: ** stat_info will contain inode information as
536: ** returned by stat().
537: */
2.23 frystyk 538: PUBLIC char * HTMulti (HTRequest * req,
539: char * path,
540: struct stat * stat_info)
2.1 luotonen 541: {
542: char * new_path = NULL;
543: int stat_status = -1;
544:
2.3 luotonen 545: if (!req || !path || !*path || !stat_info)
2.1 luotonen 546: return NULL;
547:
2.26 frystyk 548: #ifdef HAVE_READDIR
2.19 frystyk 549: if (*(path+strlen(path)-1) == '/') { /* Find welcome page */
2.3 luotonen 550: new_path = get_best_welcome(path);
551: if (new_path) path = new_path;
2.29 frystyk 552: } else{
2.3 luotonen 553: char * multi = strrchr(path, MULTI_SUFFIX[0]);
554: if (multi && !strcasecomp(multi, MULTI_SUFFIX)) {
2.35 ! frystyk 555: HTTRACE(PROT_TRACE, "Multi....... by %s suffix\n" _ MULTI_SUFFIX);
2.1 luotonen 556: if (!(new_path = HTGetBest(req, path))) {
2.35 ! frystyk 557: HTTRACE(PROT_TRACE, "Multi....... failed -- giving up\n");
2.1 luotonen 558: return NULL;
559: }
560: path = new_path;
2.29 frystyk 561: } else {
2.18 frystyk 562: stat_status = HT_STAT(path, stat_info);
2.3 luotonen 563: if (stat_status == -1) {
2.35 ! frystyk 564: HTTRACE(PROT_TRACE, "AutoMulti... can't stat \"%s\"(errno %d)\n" _
! 565: path _ errno);
2.3 luotonen 566: if (!(new_path = HTGetBest(req, path))) {
2.35 ! frystyk 567: HTTRACE(PROT_TRACE, "AutoMulti... failed -- giving up\n");
2.3 luotonen 568: return NULL;
569: }
570: path = new_path;
571: }
2.1 luotonen 572: }
573: }
2.26 frystyk 574: #endif /* HAVE_READDIR */
2.1 luotonen 575:
576: if (stat_status == -1)
2.18 frystyk 577: stat_status = HT_STAT(path, stat_info);
2.1 luotonen 578: if (stat_status == -1) {
2.35 ! frystyk 579: HTTRACE(PROT_TRACE, "Stat fails.. on \"%s\" -- giving up (errno %d)\n" _
! 580: path _ errno);
2.1 luotonen 581: return NULL;
2.29 frystyk 582: } else {
2.1 luotonen 583: if (!new_path) {
584: StrAllocCopy(new_path, path);
585: return new_path;
586: }
587: else return path;
588: }
589: }
590:
591:
Webmaster