Annotation of libwww/Library/src/HTMulti.c, revision 2.34
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.34 ! frystyk 6: ** @(#) $Id: HTMulti.c,v 2.33 1998/11/24 03:14:45 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) {
154: if (PROT_TRACE) HTTrace("Ranking..... No variants\n");
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());
169: if (PROT_TRACE)
170: HTTrace("Qualities... Content type: %.3f, Content language: %.3f, Content encoding: %.3f\n",
171: HTMAX(ctq_local, ctq_global),
172: HTMAX(clq_local, clq_global),
173: HTMAX(ceq_local, ceq_global));
174: cd->quality *= (HTMAX(ctq_local, ctq_global) *
175: HTMAX(clq_local, clq_global) *
176: HTMAX(ceq_local, ceq_global));
177: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
178: }
179:
180: /* Sort the array of all our accepted preferences */
181: HTArray_sort(variants, VariantSort);
182:
183: /* Write out the result */
184: if (PROT_TRACE) {
185: int cnt = 1;
186: cd = (HTContentDescription *) HTArray_firstObject(variants, data);
187: HTTrace("Ranking.....\n");
188: HTTrace("RANK QUALITY CONTENT-TYPE LANGUAGE ENCODING FILE\n");
189: while (cd) {
190: HTTrace("%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):"-",
196: cd->filename ? cd->filename :"-");
197: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
198: }
199: }
200: return YES;
201: }
2.3 luotonen 202:
2.14 frystyk 203: /* PUBLIC HTSplitFilename()
204: **
205: ** Split the filename to an array of suffixes.
206: ** Return the number of parts placed to the array.
207: ** Array should have MAX_SUFF+1 items.
208: */
2.23 frystyk 209: PRIVATE int HTSplitFilename (char * s_str, char ** s_arr)
2.14 frystyk 210: {
2.26 frystyk 211: const char *delimiters = HTBind_delimiters();
2.14 frystyk 212: char * start = s_str;
213: char * end;
214: char save;
215: int i;
216:
217: if (!s_str || !s_arr) return 0;
218:
219: for (i=0; i < MAX_SUFF && *start; i++) {
220: for(end=start+1; *end && !strchr(delimiters, *end); end++);
221: save = *end;
222: *end = 0;
223: StrAllocCopy(s_arr[i], start); /* Frees the previous value */
224: *end = save;
225: start = end;
226: }
2.24 frystyk 227: HT_FREE(s_arr[i]); /* Terminating NULL */
2.14 frystyk 228: return i;
229: }
230:
231:
2.3 luotonen 232: /*
233: ** Set default file name for welcome page on each directory.
234: */
2.23 frystyk 235: PUBLIC void HTAddWelcome (char * name)
2.3 luotonen 236: {
237: if (name) {
238: char * mycopy = NULL;
239: StrAllocCopy(mycopy,name);
240:
241: if (!welcome_names)
242: welcome_names = HTList_new();
243: HTList_addObject(welcome_names, (void*)mycopy);
244: }
245: }
246:
247:
2.26 frystyk 248: #ifdef HAVE_READDIR
2.1 luotonen 249:
250: /* PRIVATE multi_match()
251: **
252: ** Check if actual filename (split in parts) fulfills
253: ** the requirements.
254: */
2.23 frystyk 255: PRIVATE BOOL multi_match (char ** required, int m, char ** actual, int n)
2.1 luotonen 256: {
257: int c;
258: int i,j;
259:
2.2 duns 260: #ifdef VMS
261: for(c=0; c<m && c<n && !strcasecomp(required[c], actual[c]); c++);
262: #else /* not VMS */
2.1 luotonen 263: for(c=0; c<m && c<n && !strcmp(required[c], actual[c]); c++);
2.2 duns 264: #endif /* not VMS */
2.1 luotonen 265:
266: if (!c) return NO; /* Names differ rigth from start */
267:
268: for(i=c; i<m; i++) {
269: BOOL found = NO;
270: for(j=c; j<n; j++) {
2.2 duns 271: #ifdef VMS
272: if (!strcasecomp(required[i], actual[j])) {
273: #else /* not VMS */
2.1 luotonen 274: if (!strcmp(required[i], actual[j])) {
2.2 duns 275: #endif /* not VMS */
2.1 luotonen 276: found = YES;
277: break;
278: }
279: }
280: if (!found) return NO;
281: }
282: return YES;
283: }
284:
285:
286: /*
287: ** Get multi-match possibilities for a given file
288: ** ----------------------------------------------
289: ** On entry:
290: ** path absolute path to one file in a directory,
291: ** may end in .multi.
292: ** On exit:
293: ** returns a list of ContentDesription structures
294: ** describing the mathing files.
295: **
296: */
2.29 frystyk 297: PRIVATE HTArray * dir_matches (char * path)
2.1 luotonen 298: {
299: static char * required[MAX_SUFF+1];
300: static char * actual[MAX_SUFF+1];
301: int m,n;
302: char * dirname = NULL;
303: char * basename = NULL;
304: int baselen;
305: char * multi = NULL;
306: DIR * dp;
2.26 frystyk 307: struct dirent * dirbuf;
2.29 frystyk 308: HTArray * matches = NULL;
2.16 frystyk 309: #ifdef HT_REENTRANT
2.31 frystyk 310: struct dirent result; /* For readdir_r */
2.16 frystyk 311: #endif
2.1 luotonen 312:
313: if (!path) return NULL;
314:
315: StrAllocCopy(dirname, path);
316: basename = (strrchr(dirname, '/'));
317: if (!basename)
318: goto dir_match_failed;
319: *basename++ = 0;
320:
321: multi = strrchr(basename, MULTI_SUFFIX[0]);
2.2 duns 322: if (multi && !strcasecomp(multi, MULTI_SUFFIX))
2.1 luotonen 323: *multi = 0;
324: baselen = strlen(basename);
325:
326: m = HTSplitFilename(basename, required);
327:
328: dp = opendir(dirname);
329: if (!dp) {
2.13 frystyk 330: if (PROT_TRACE)
2.25 eric 331: HTTrace("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) {
405: if (PROT_TRACE) HTTrace("No matches.. for \"%s\"\n", path);
2.13 frystyk 406: return NULL;
2.1 luotonen 407: }
408:
2.29 frystyk 409: if (PROT_TRACE) {
410: void ** data;
411: HTContentDescription * cd = HTArray_firstObject(variants, data);
2.25 eric 412: HTTrace("Multi....... Possibilities for \"%s\"\n", path);
2.29 frystyk 413: HTTrace(" QUALITY CONTENT-TYPE LANGUAGE ENCODING FILE\n");
414: while (cd) {
415: HTTrace(" %.4f %-20.20s %-8.8s %-10.10s %s\n",
416: cd->quality,
417: cd->content_type ?HTAtom_name(cd->content_type) :"-\t",
418: cd->content_language?HTAtom_name(cd->content_language):"-",
419: cd->content_encoding?HTAtom_name(cd->content_encoding):"-",
420: cd->filename ?cd->filename :"-");
421: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
422: }
2.1 luotonen 423: }
424:
425: /*
2.29 frystyk 426: ** Finally get the best variant which is readable
2.1 luotonen 427: */
2.29 frystyk 428: if (HTRank(req, variants)) {
429: void ** data;
430: HTContentDescription * cd = HTArray_firstObject(variants, data);
431: while (cd) {
432: if (cd->filename) {
433: if (access(cd->filename, R_OK) != -1)
434: StrAllocCopy(representation, cd->filename);
435: else if (PROT_TRACE)
436: HTTrace("Multi....... `%s\' is not readable\n",
437: cd->filename);
2.1 luotonen 438: }
2.29 frystyk 439: HT_FREE(cd->filename);
440: HT_FREE(cd);
441: cd = (HTContentDescription *) HTArray_nextObject(variants, data);
2.1 luotonen 442: }
443: }
2.29 frystyk 444: HTArray_delete(variants);
445: return representation;
2.1 luotonen 446: }
447:
2.3 luotonen 448:
449:
2.23 frystyk 450: PRIVATE int welcome_value (char * name)
2.3 luotonen 451: {
452: HTList * cur = welcome_names;
453: char * welcome;
454: int v = 0;
455:
456: while ((welcome = (char*)HTList_nextObject(cur))) {
457: v++;
458: if (!strcmp(welcome,name)) return v;
459: }
460: return 0;
461: }
462:
463:
464:
2.23 frystyk 465: PRIVATE char * get_best_welcome (char * path)
2.3 luotonen 466: {
467: char * best_welcome = NULL;
468: int best_value = 0;
469: DIR * dp;
2.26 frystyk 470: struct dirent * dirbuf;
2.3 luotonen 471: char * last = strrchr(path, '/');
472:
473: if (!welcome_names) {
474: HTAddWelcome("Welcome.html");
475: HTAddWelcome("welcome.html");
2.5 luotonen 476: #if 0
2.3 luotonen 477: HTAddWelcome("Index.html");
2.5 luotonen 478: #endif
2.3 luotonen 479: HTAddWelcome("index.html");
480: }
481:
2.5 luotonen 482: if (last && last!=path) *last = 0;
2.3 luotonen 483: dp = opendir(path);
2.5 luotonen 484: if (last && last!=path) *last='/';
2.3 luotonen 485: if (!dp) {
2.13 frystyk 486: if (PROT_TRACE)
2.25 eric 487: HTTrace("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.13 frystyk 512: if (PROT_TRACE)
2.25 eric 513: HTTrace("Welcome..... \"%s\"\n",welcome);
2.3 luotonen 514: return welcome;
515: }
516: return NULL;
517: }
518:
2.26 frystyk 519: #endif /* HAVE_READDIR */
2.1 luotonen 520:
521:
522: /*
523: ** Do multiformat handling
524: ** -----------------------
525: ** On entry:
526: ** req->conversions accepted content-types
527: ** req->encodings accepted content-transfer-encodings
528: ** req->languages accepted content-languages
529: ** path absolute pathname of the filename for
530: ** which the match is desired.
531: ** stat_info pointer to result space.
532: **
533: ** On exit:
534: ** returns a newly allocated absolute filepath of the best
535: ** match, or NULL if no match.
536: ** stat_info will contain inode information as
537: ** returned by stat().
538: */
2.23 frystyk 539: PUBLIC char * HTMulti (HTRequest * req,
540: char * path,
541: struct stat * stat_info)
2.1 luotonen 542: {
543: char * new_path = NULL;
544: int stat_status = -1;
545:
2.3 luotonen 546: if (!req || !path || !*path || !stat_info)
2.1 luotonen 547: return NULL;
548:
2.26 frystyk 549: #ifdef HAVE_READDIR
2.19 frystyk 550: if (*(path+strlen(path)-1) == '/') { /* Find welcome page */
2.3 luotonen 551: new_path = get_best_welcome(path);
552: if (new_path) path = new_path;
2.29 frystyk 553: } else{
2.3 luotonen 554: char * multi = strrchr(path, MULTI_SUFFIX[0]);
555: if (multi && !strcasecomp(multi, MULTI_SUFFIX)) {
2.13 frystyk 556: if (PROT_TRACE)
2.25 eric 557: HTTrace("Multi....... by %s suffix\n", MULTI_SUFFIX);
2.1 luotonen 558: if (!(new_path = HTGetBest(req, path))) {
2.13 frystyk 559: if (PROT_TRACE)
2.25 eric 560: HTTrace("Multi....... failed -- giving up\n");
2.1 luotonen 561: return NULL;
562: }
563: path = new_path;
2.29 frystyk 564: } else {
2.18 frystyk 565: stat_status = HT_STAT(path, stat_info);
2.3 luotonen 566: if (stat_status == -1) {
2.13 frystyk 567: if (PROT_TRACE)
2.29 frystyk 568: HTTrace("AutoMulti... can't stat \"%s\"(errno %d)\n",
2.13 frystyk 569: path, errno);
2.3 luotonen 570: if (!(new_path = HTGetBest(req, path))) {
2.13 frystyk 571: if (PROT_TRACE)
2.25 eric 572: HTTrace("AutoMulti... failed -- giving up\n");
2.3 luotonen 573: return NULL;
574: }
575: path = new_path;
576: }
2.1 luotonen 577: }
578: }
2.26 frystyk 579: #endif /* HAVE_READDIR */
2.1 luotonen 580:
581: if (stat_status == -1)
2.18 frystyk 582: stat_status = HT_STAT(path, stat_info);
2.1 luotonen 583: if (stat_status == -1) {
2.13 frystyk 584: if (PROT_TRACE)
2.25 eric 585: HTTrace("Stat fails.. on \"%s\" -- giving up (errno %d)\n",
2.13 frystyk 586: path, errno);
2.1 luotonen 587: return NULL;
2.29 frystyk 588: } else {
2.1 luotonen 589: if (!new_path) {
590: StrAllocCopy(new_path, path);
591: return new_path;
592: }
593: else return path;
594: }
595: }
596:
597:
Webmaster