Annotation of libwww/Library/src/HTFile.c, revision 1.58
1.1 timbl 1: /* File Access HTFile.c
2: ** ===========
3: **
4: ** This is unix-specific code in general, with some VMS bits.
1.8 timbl 5: ** These are routines for file access used by browsers.
1.1 timbl 6: **
7: ** History:
8: ** Feb 91 Written Tim Berners-Lee CERN/CN
9: ** Apr 91 vms-vms access included using DECnet syntax
10: ** 26 Jun 92 (JFG) When running over DECnet, suppressed FTP.
11: ** Fixed access bug for relative names on VMS.
1.28 duns 12: ** Sep 93 (MD) Access to VMS files allows sharing.
13: ** 15 Nov 93 (MD) Moved HTVMSname to HTVMSUTILS.C
1.52 duns 14: ** 22 Feb 94 (MD) Excluded two routines if we are not READING directories
1.1 timbl 15: **
16: ** Bugs:
1.2 timbl 17: ** FTP: Cannot access VMS files from a unix machine.
18: ** How can we know that the
1.1 timbl 19: ** target machine runs VMS?
20: */
21:
1.2 timbl 22: #include "HTFile.h" /* Implemented here */
23:
1.1 timbl 24: #define INFINITY 512 /* file name length @@ FIXME */
1.36 luotonen 25:
26: #ifdef VMS
1.52 duns 27: PRIVATE char * suffix_separators = "._";
1.36 luotonen 28: #else
1.49 luotonen 29: PRIVATE char * suffix_separators = ".,_";
1.36 luotonen 30: #endif
31:
1.1 timbl 32:
33: #include "HTUtils.h"
34:
1.28 duns 35: #ifdef VMS
36: #include "HTVMSUtils.h"
37: #endif /* VMS */
38:
1.1 timbl 39: #include "HTParse.h"
40: #include "tcp.h"
41: #include "HTTCP.h"
42: #ifndef DECNET
43: #include "HTFTP.h"
44: #endif
45: #include "HTAnchor.h"
1.2 timbl 46: #include "HTAtom.h"
47: #include "HTWriter.h"
48: #include "HTFWriter.h"
49: #include "HTInit.h"
1.7 secret 50: #include "HTBTree.h"
1.52 duns 51: #include "HTFormat.h"
1.54 luotonen 52: #include "HTMulti.h"
1.37 luotonen 53:
1.2 timbl 54: typedef struct _HTSuffix {
55: char * suffix;
1.36 luotonen 56: HTAtom * rep; /* Content-Type */
57: HTAtom * encoding; /* Content-Encoding */
58: HTAtom * language; /* Content-Language */
1.2 timbl 59: float quality;
60: } HTSuffix;
61:
62:
1.58 ! frystyk 63: #ifdef OLD_CODE
1.12 timbl 64: #ifdef USE_DIRENT /* Set this for Sys V systems */
65: #define STRUCT_DIRENT struct dirent
66: #else
67: #define STRUCT_DIRENT struct direct
68: #endif
1.1 timbl 69:
1.2 timbl 70: #include "HTML.h" /* For directory object building */
71:
1.10 secret 72: #define PUTC(c) (*target->isa->put_character)(target, c)
73: #define PUTS(s) (*target->isa->put_string)(target, s)
74: #define START(e) (*target->isa->start_element)(target, e, 0, 0)
75: #define END(e) (*target->isa->end_element)(target, e)
76: #define FREE_TARGET (*target->isa->free)(target)
1.2 timbl 77: struct _HTStructured {
78: CONST HTStructuredClass * isa;
79: /* ... */
80: };
1.58 ! frystyk 81: #endif /* OLD_CODE */
1.2 timbl 82:
83:
84: /* Controlling globals
85: **
86: */
87:
1.58 ! frystyk 88: #ifdef OLD_CODE
1.2 timbl 89: PUBLIC int HTDirAccess = HT_DIR_OK;
90: PUBLIC int HTDirReadme = HT_DIR_README_TOP;
1.58 ! frystyk 91: #endif /* OLD_CODE */
! 92:
1.33 timbl 93: PUBLIC BOOL HTTakeBackup = YES;
1.47 luotonen 94: PUBLIC BOOL HTSuffixCaseSense = NO; /* Are suffixes case sensitive */
1.1 timbl 95:
96: PRIVATE char *HTMountRoot = "/Net/"; /* Where to find mounts */
1.23 duns 97: #ifdef VMS
1.28 duns 98: PRIVATE char *HTCacheRoot = "/WWW$SCRATCH"; /* Where to cache things */
1.1 timbl 99: #else
100: PRIVATE char *HTCacheRoot = "/tmp/W3_Cache_"; /* Where to cache things */
101: #endif
102:
103: /* PRIVATE char *HTSaveRoot = "$(HOME)/WWW/";*/ /* Where to save things */
104:
1.2 timbl 105:
106: /* Suffix registration
107: */
108:
109: PRIVATE HTList * HTSuffixes = 0;
1.36 luotonen 110: PRIVATE HTSuffix no_suffix = { "*", NULL, NULL, NULL, 1.0 };
111: PRIVATE HTSuffix unknown_suffix = { "*.*", NULL, NULL, NULL, 1.0};
1.2 timbl 112:
1.11 timbl 113:
1.2 timbl 114: /* Define the representation associated with a file suffix
115: ** -------------------------------------------------------
1.11 timbl 116: **
117: ** Calling this with suffix set to "*" will set the default
118: ** representation.
119: ** Calling this with suffix set to "*.*" will set the default
120: ** representation for unknown suffix files which contain a ".".
1.25 luotonen 121: **
122: ** If filename suffix is already defined its previous
123: ** definition is overridden.
1.2 timbl 124: */
1.36 luotonen 125: PUBLIC void HTAddType ARGS4(CONST char *, suffix,
126: CONST char *, representation,
127: CONST char *, encoding,
128: float, value)
129: {
130: HTSetSuffix(suffix, representation, encoding, NULL, value);
131: }
132:
133:
134: PUBLIC void HTAddEncoding ARGS3(CONST char *, suffix,
135: CONST char *, encoding,
136: float, value)
137: {
138: HTSetSuffix(suffix, NULL, encoding, NULL, value);
139: }
140:
141:
142: PUBLIC void HTAddLanguage ARGS3(CONST char *, suffix,
143: CONST char *, language,
144: float, value)
145: {
146: HTSetSuffix(suffix, NULL, NULL, language, value);
147: }
148:
149:
150: PUBLIC void HTSetSuffix ARGS5(CONST char *, suffix,
151: CONST char *, representation,
152: CONST char *, encoding,
153: CONST char *, language,
154: float, value)
1.2 timbl 155: {
1.11 timbl 156: HTSuffix * suff;
1.25 luotonen 157:
1.19 timbl 158: if (strcmp(suffix, "*")==0) suff = &no_suffix;
159: else if (strcmp(suffix, "*.*")==0) suff = &unknown_suffix;
160: else {
1.25 luotonen 161: HTList *cur = HTSuffixes;
162:
163: while (NULL != (suff = (HTSuffix*)HTList_nextObject(cur))) {
164: if (suff->suffix && 0==strcmp(suff->suffix, suffix))
165: break;
166: }
167: if (!suff) { /* Not found -- create a new node */
168: suff = (HTSuffix*) calloc(1, sizeof(HTSuffix));
169: if (suff == NULL) outofmem(__FILE__, "HTSetSuffix");
1.36 luotonen 170:
1.25 luotonen 171: if (!HTSuffixes) HTSuffixes = HTList_new();
172: HTList_addObject(HTSuffixes, suff);
1.36 luotonen 173:
1.25 luotonen 174: StrAllocCopy(suff->suffix, suffix);
175: }
1.11 timbl 176: }
1.19 timbl 177:
1.36 luotonen 178: if (representation)
179: suff->rep = HTAtom_for(representation);
180: if (language)
181: suff->language = HTAtom_for(language);
182: if (encoding) {
1.38 luotonen 183: char * enc = NULL;
1.19 timbl 184: char * p;
185: StrAllocCopy(enc, encoding);
186: for (p=enc; *p; p++) *p = TOLOWER(*p);
1.38 luotonen 187: suff->encoding = HTAtom_for(enc);
188: free(enc); /* Leak fixed AL 6 Feb 1994 */
1.11 timbl 189: }
1.36 luotonen 190:
1.2 timbl 191: suff->quality = value;
192: }
193:
194:
1.36 luotonen 195: PRIVATE BOOL is_separator ARGS1(char, ch)
196: {
197: if (strchr(suffix_separators, ch)) return YES;
198: else return NO;
199: }
200:
201:
1.53 luotonen 202: /* PUBLIC HTSplitFilename()
1.36 luotonen 203: **
204: ** Split the filename to an array of suffixes.
205: ** Return the number of parts placed to the array.
206: ** Array should have MAX_SUFF+1 items.
207: */
1.53 luotonen 208: PUBLIC int HTSplitFilename ARGS2(char *, s_str,
1.36 luotonen 209: char **, s_arr)
210: {
211: char * start = s_str;
212: char * end;
213: char save;
214: int i;
215:
216: if (!s_str || !s_arr) return 0;
217:
218: for (i=0; i < MAX_SUFF && *start; i++) {
219: for(end=start+1; *end && !is_separator(*end); end++);
220: save = *end;
221: *end = 0;
1.38 luotonen 222: StrAllocCopy(s_arr[i], start); /* Frees the previous value */
1.36 luotonen 223: *end = save;
224: start = end;
225: }
226: FREE(s_arr[i]); /* Terminating NULL */
227: return i;
228: }
229:
230:
231:
1.53 luotonen 232: PUBLIC HTContentDescription * HTGetContentDescription ARGS2(char **, actual,
233: int, n)
1.36 luotonen 234: {
235: HTContentDescription * cd;
236: int i;
237:
238: #ifndef NO_INIT
239: if (!HTSuffixes) HTFileInit();
240: #endif
241:
1.47 luotonen 242: if (n < 2) return NULL; /* No suffix */
243:
1.36 luotonen 244: cd = (HTContentDescription*)calloc(1, sizeof(HTContentDescription));
245: if (!cd) outofmem(__FILE__, "HTContentDescription");
246:
247: cd->quality = 1.0;
248:
249: for (i=n-1; i>0; i--) {
250: HTList * cur = HTSuffixes;
251: HTSuffix * suff;
252: BOOL found = NO;
253:
1.47 luotonen 254: CTRACE(stderr, "Searching... for suffix %d: \"%s\"\n", i, actual[i]);
1.36 luotonen 255:
256: while ((suff = (HTSuffix*)HTList_nextObject(cur))) {
1.47 luotonen 257: if ((HTSuffixCaseSense && !strcmp(suff->suffix, actual[i])) ||
258: (!HTSuffixCaseSense && !strcasecomp(suff->suffix, actual[i]))){
1.36 luotonen 259:
260: if (!cd->content_type)
261: cd->content_type = suff->rep;
262: if (!cd->content_encoding)
263: cd->content_encoding = suff->encoding;
264: if (!cd->content_language)
265: cd->content_language = suff->language;
1.47 luotonen 266: if (suff->quality > 0.0000001)
267: cd->quality *= suff->quality;
1.36 luotonen 268:
269: found = YES;
270: break;
271: }
272: }
273: if (!found) {
274: if (i < n-1)
275: break;
276: else {
277: free(cd);
278: return NULL;
279: }
280: }
281: }
282: return cd;
283: }
284:
1.2 timbl 285:
286: /* Send README file
287: **
288: ** If a README file exists, then it is inserted into the document here.
289: */
1.58 ! frystyk 290: #ifdef OLD_CODE
1.12 timbl 291: #ifdef GOT_READ_DIR
1.2 timbl 292: PRIVATE void do_readme ARGS2(HTStructured *, target, CONST char *, localname)
293: {
294: FILE * fp;
295: char * readme_file_name =
296: malloc(strlen(localname)+ 1 + strlen(HT_DIR_README_FILE) + 1);
297: strcpy(readme_file_name, localname);
298: strcat(readme_file_name, "/");
299: strcat(readme_file_name, HT_DIR_README_FILE);
300:
301: fp = fopen(readme_file_name, "r");
302:
303: if (fp) {
304: HTStructuredClass targetClass;
305:
306: targetClass = *target->isa; /* (Can't init agregate in K&R) */
307: START(HTML_PRE);
308: for(;;){
309: char c = fgetc(fp);
310: if (c == (char)EOF) break;
311: switch (c) {
312: case '&':
313: case '<':
314: case '>':
315: PUTC('&');
316: PUTC('#');
317: PUTC((char)(c / 10));
318: PUTC((char) (c % 10));
319: PUTC(';');
320: break;
1.13 secret 321: /* case '\n':
322: PUTC('\r');
323: Bug removed thanks to joe@athena.mit.edu */
1.2 timbl 324: default:
325: PUTC(c);
326: }
327: }
328: END(HTML_PRE);
329: fclose(fp);
330: }
1.38 luotonen 331: free(readme_file_name); /* Leak fixed AL 6 Feb 1994 */
1.2 timbl 332: }
1.58 ! frystyk 333: #endif /* GOT_READ_DIR */
! 334: #endif /* OLD_CODE */
1.2 timbl 335:
1.1 timbl 336: /* Make the cache file name for a W3 document
337: ** ------------------------------------------
338: ** Make up a suitable name for saving the node in
339: **
340: ** E.g. /tmp/WWW_Cache_news/1234@cernvax.cern.ch
341: ** /tmp/WWW_Cache_http/crnvmc/FIND/xx.xxx.xx
342: **
343: ** On exit,
344: ** returns a malloc'ed string which must be freed by the caller.
345: */
346: PUBLIC char * HTCacheFileName ARGS1(CONST char *,name)
347: {
348: char * access = HTParse(name, "", PARSE_ACCESS);
349: char * host = HTParse(name, "", PARSE_HOST);
350: char * path = HTParse(name, "", PARSE_PATH+PARSE_PUNCTUATION);
351:
352: char * result;
353: result = (char *)malloc(
354: strlen(HTCacheRoot)+strlen(access)
355: +strlen(host)+strlen(path)+6+1);
356: if (result == NULL) outofmem(__FILE__, "HTCacheFileName");
357: sprintf(result, "%s/WWW/%s/%s%s", HTCacheRoot, access, host, path);
358: free(path);
359: free(access);
360: free(host);
361: return result;
362: }
363:
1.2 timbl 364:
1.1 timbl 365: /* Open a file for write, creating the path
366: ** ----------------------------------------
367: */
368: #ifdef NOT_IMPLEMENTED
369: PRIVATE int HTCreatePath ARGS1(CONST char *,path)
370: {
371: return -1;
372: }
373: #endif
374:
375: /* Convert filenames between local and WWW formats
376: ** -----------------------------------------------
377: ** Make up a suitable name for saving the node in
378: **
379: ** E.g. $(HOME)/WWW/news/1234@cernvax.cern.ch
380: ** $(HOME)/WWW/http/crnvmc/FIND/xx.xxx.xx
381: **
382: ** On exit,
383: ** returns a malloc'ed string which must be freed by the caller.
384: */
385: PUBLIC char * HTLocalName ARGS1(CONST char *,name)
386: {
387: char * access = HTParse(name, "", PARSE_ACCESS);
388: char * host = HTParse(name, "", PARSE_HOST);
389: char * path = HTParse(name, "", PARSE_PATH+PARSE_PUNCTUATION);
390:
1.6 timbl 391: HTUnEscape(path); /* Interpret % signs */
1.36 luotonen 392:
1.49 luotonen 393: if (HTImServer && !*access) {
1.47 luotonen 394: free(access);
395: free(host);
396: CTRACE(stderr, "Local filename is \"%s\"\n", path);
397: return(path);
398: }
399:
1.27 duns 400: if (0==strcmp(access, "file")) { /* local file */
1.1 timbl 401: free(access);
1.9 timbl 402: if ((0==strcasecomp(host, HTHostName())) ||
403: (0==strcasecomp(host, "localhost")) || !*host) {
1.1 timbl 404: free(host);
405: if (TRACE) fprintf(stderr, "Node `%s' means path `%s'\n", name, path);
406: return(path);
407: } else {
408: char * result = (char *)malloc(
409: strlen("/Net/")+strlen(host)+strlen(path)+1);
410: if (result == NULL) outofmem(__FILE__, "HTLocalName");
411: sprintf(result, "%s%s%s", "/Net/", host, path);
412: free(host);
413: free(path);
414: if (TRACE) fprintf(stderr, "Node `%s' means file `%s'\n", name, result);
415: return result;
416: }
417: } else { /* other access */
418: char * result;
419: CONST char * home = (CONST char*)getenv("HOME");
1.28 duns 420: #ifdef VMS
421: if (!home)
422: home = HTCacheRoot;
423: else
424: home = HTVMS_wwwName(home);
425: #else /* not VMS */
1.1 timbl 426: if (!home) home = "/tmp";
1.28 duns 427: #endif /* not VMS */
1.1 timbl 428: result = (char *)malloc(
429: strlen(home)+strlen(access)+strlen(host)+strlen(path)+6+1);
430: if (result == NULL) outofmem(__FILE__, "HTLocalName");
431: sprintf(result, "%s/WWW/%s/%s%s", home, access, host, path);
432: free(path);
433: free(access);
434: free(host);
435: return result;
436: }
437: }
438:
439:
440: /* Make a WWW name from a full local path name
441: **
442: ** Bugs:
443: ** At present, only the names of two network root nodes are hand-coded
444: ** in and valid for the NeXT only. This should be configurable in
445: ** the general case.
446: */
447:
448: PUBLIC char * WWW_nameOfFile ARGS1 (CONST char *,name)
449: {
450: char * result;
451: #ifdef NeXT
452: if (0==strncmp("/private/Net/", name, 13)) {
453: result = (char *)malloc(7+strlen(name+13)+1);
454: if (result == NULL) outofmem(__FILE__, "WWW_nameOfFile");
455: sprintf(result, "file://%s", name+13);
456: } else
457: #endif
458: if (0==strncmp(HTMountRoot, name, 5)) {
459: result = (char *)malloc(7+strlen(name+5)+1);
460: if (result == NULL) outofmem(__FILE__, "WWW_nameOfFile");
461: sprintf(result, "file://%s", name+5);
462: } else {
463: result = (char *)malloc(7+strlen(HTHostName())+strlen(name)+1);
464: if (result == NULL) outofmem(__FILE__, "WWW_nameOfFile");
465: sprintf(result, "file://%s%s", HTHostName(), name);
466: }
467: if (TRACE) fprintf(stderr, "File `%s'\n\tmeans node `%s'\n", name, result);
468: return result;
469: }
470:
471:
1.2 timbl 472: /* Determine a suitable suffix, given the representation
473: ** -----------------------------------------------------
474: **
475: ** On entry,
476: ** rep is the atomized MIME style representation
477: **
478: ** On exit,
479: ** returns a pointer to a suitable suffix string if one has been
480: ** found, else "".
481: */
482: PUBLIC CONST char * HTFileSuffix ARGS1(HTAtom*, rep)
483: {
484: HTSuffix * suff;
1.35 luotonen 485: HTList * cur;
1.2 timbl 486:
487: #ifndef NO_INIT
488: if (!HTSuffixes) HTFileInit();
489: #endif
1.35 luotonen 490: cur = HTSuffixes;
491: while ((suff = (HTSuffix*)HTList_nextObject(cur))) {
1.2 timbl 492: if (suff->rep == rep) {
493: return suff->suffix; /* OK -- found */
494: }
495: }
496: return ""; /* Dunno */
497: }
498:
499:
1.1 timbl 500: /* Determine file format from file name
501: ** ------------------------------------
502: **
1.19 timbl 503: ** This version will return the representation and also set
504: ** a variable for the encoding.
505: **
506: ** It will handle for example x.txt, x.txt,Z, x.Z
1.2 timbl 507: */
508:
1.47 luotonen 509: PUBLIC HTFormat HTFileFormat ARGS3(CONST char *, filename,
510: HTAtom **, pencoding,
511: HTAtom **, planguage)
512: {
513: char * actual[MAX_SUFF+1];
514: int n;
515: HTContentDescription * cd;
516: HTFormat content_type = NULL;
517:
518: if (!filename) return WWW_BINARY;
519:
520: #ifndef NO_INIT
521: if (!HTSuffixes) HTFileInit();
522: #endif
523:
524: for (n=0; n<MAX_SUFF+1; n++) actual[n] = NULL;
1.53 luotonen 525: n = HTSplitFilename((char*)filename, actual);
526: cd = HTGetContentDescription(actual, n);
1.47 luotonen 527: while(n-- > 0) if (actual[n]) free(actual[n]);
528:
529: if (cd) {
530: if (pencoding) *pencoding = cd->content_encoding;
531: if (planguage) *planguage = cd->content_language;
532: content_type = cd->content_type;
533: free(cd);
534: }
535: else {
536: HTSuffix * suff = strchr(filename,'.') ?
537: (unknown_suffix.rep ? &unknown_suffix : &no_suffix) :
538: &no_suffix;
539:
540: if (pencoding) *pencoding = suff->encoding;
541: if (planguage) *planguage = suff->language;
542: content_type = suff->rep;
543: }
544:
545: if (pencoding && !*pencoding)
546: *pencoding = HTAtom_for("binary");
547: return content_type ? content_type : WWW_BINARY;
548: }
1.2 timbl 549:
1.47 luotonen 550: #ifdef OLD_CODE
1.2 timbl 551: HTSuffix * suff;
1.35 luotonen 552: HTList * cur;
1.2 timbl 553: int lf = strlen(filename);
554:
1.30 timbl 555: if (pencoding) *pencoding = NULL;
1.35 luotonen 556: cur = HTSuffixes;
557: while ((suff = (HTSuffix*)HTList_nextObject(cur))) {
558: int ls = strlen(suff->suffix);
1.2 timbl 559: if ((ls <= lf) && 0==strcmp(suff->suffix, filename + lf - ls)) {
1.30 timbl 560: if (pencoding) *pencoding = suff->encoding;
1.19 timbl 561: if (suff->rep) return suff->rep; /* OK -- found */
1.35 luotonen 562:
563: /* Got encoding, need representation */
564: cur = HTSuffixes;
565: while ((suff = (HTSuffix*)HTList_nextObject(cur))) {
566: int ls2 = strlen(suff->suffix);
567: if ((ls <= lf) &&
568: 0==strncmp(suff->suffix, filename + lf - ls -ls2, ls2)) {
1.19 timbl 569: if (suff->rep) return suff->rep;
570: }
571: }
1.2 timbl 572: }
573: }
1.11 timbl 574:
1.19 timbl 575: /* defaults tree */
1.11 timbl 576:
1.19 timbl 577: suff = strchr(filename, '.') ? /* Unknown suffix */
578: ( unknown_suffix.rep ? &unknown_suffix : &no_suffix)
579: : &no_suffix;
580:
581: /* set default encoding unless found with suffix already */
1.30 timbl 582: if (pencoding && !*pencoding) *pencoding = suff->encoding ? suff->encoding
1.19 timbl 583: : HTAtom_for("binary");
584: return suff->rep ? suff->rep : WWW_BINARY;
1.2 timbl 585: }
1.47 luotonen 586: #endif /* OLD_CODE */
587:
1.2 timbl 588:
589:
590: /* Determine value from file name
591: ** ------------------------------
1.1 timbl 592: **
593: */
594:
1.2 timbl 595: PUBLIC float HTFileValue ARGS1 (CONST char *,filename)
596:
1.1 timbl 597: {
1.2 timbl 598: HTSuffix * suff;
1.35 luotonen 599: HTList * cur;
1.2 timbl 600: int lf = strlen(filename);
601:
602: #ifndef NO_INIT
603: if (!HTSuffixes) HTFileInit();
604: #endif
1.35 luotonen 605: cur = HTSuffixes;
606: while ((suff = (HTSuffix*)HTList_nextObject(cur))) {
607: int ls = strlen(suff->suffix);
1.2 timbl 608: if ((ls <= lf) && 0==strcmp(suff->suffix, filename + lf - ls)) {
609: if (TRACE) fprintf(stderr, "File: Value of %s is %.3f\n",
610: filename, suff->quality);
611: return suff->quality; /* OK -- found */
612: }
613: }
614: return 0.3; /* Dunno! */
1.1 timbl 615: }
616:
617:
618: /* Determine write access to a file
1.2 timbl 619: ** --------------------------------
620: **
621: ** On exit,
622: ** return value YES if file can be accessed and can be written to.
623: **
624: ** Bugs:
625: ** 1. No code for non-unix systems.
626: ** 2. Isn't there a quicker way?
1.1 timbl 627: */
628:
1.23 duns 629: #ifdef VMS
1.1 timbl 630: #define NO_GROUPS
631: #endif
632: #ifdef NO_UNIX_IO
633: #define NO_GROUPS
634: #endif
635: #ifdef PCNFS
636: #define NO_GROUPS
637: #endif
638:
639: PUBLIC BOOL HTEditable ARGS1 (CONST char *,filename)
640: {
641: #ifdef NO_GROUPS
642: return NO; /* Safe answer till we find the correct algorithm */
643: #else
1.44 frystyk 644: #ifdef NeXT
645: int groups[NGROUPS];
646: #else
647: gid_t groups[NGROUPS];
648: #endif
1.1 timbl 649: uid_t myUid;
650: int ngroups; /* The number of groups */
651: struct stat fileStatus;
652: int i;
653:
654: if (stat(filename, &fileStatus)) /* Get details of filename */
655: return NO; /* Can't even access file! */
656:
657: ngroups = getgroups(NGROUPS, groups); /* Groups to which I belong */
658: myUid = geteuid(); /* Get my user identifier */
659:
660: if (TRACE) {
661: int i;
1.19 timbl 662: fprintf(stderr,
663: "File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (",
1.5 timbl 664: (unsigned int) fileStatus.st_mode, fileStatus.st_uid,
665: fileStatus.st_gid,
1.1 timbl 666: myUid, ngroups);
1.19 timbl 667: for (i=0; i<ngroups; i++) fprintf(stderr, " %d", groups[i]);
668: fprintf(stderr, ")\n");
1.1 timbl 669: }
670:
671: if (fileStatus.st_mode & 0002) /* I can write anyway? */
672: return YES;
673:
674: if ((fileStatus.st_mode & 0200) /* I can write my own file? */
675: && (fileStatus.st_uid == myUid))
676: return YES;
677:
678: if (fileStatus.st_mode & 0020) /* Group I am in can write? */
679: {
680: for (i=0; i<ngroups; i++) {
681: if (groups[i] == fileStatus.st_gid)
682: return YES;
683: }
684: }
685: if (TRACE) fprintf(stderr, "\tFile is not editable.\n");
686: return NO; /* If no excuse, can't do */
687: #endif
688: }
689:
690:
1.2 timbl 691: /* Make a save stream
692: ** ------------------
693: **
694: ** The stream must be used for writing back the file.
695: ** @@@ no backup done
696: */
1.29 timbl 697: PUBLIC HTStream * HTFileSaveStream ARGS1(HTRequest *, request)
1.2 timbl 698: {
699:
1.29 timbl 700: CONST char * addr = HTAnchor_address((HTAnchor*)request->anchor);
1.33 timbl 701: char * filename = HTLocalName(addr);
702: FILE* fp;
703:
704: /* @ Introduce CVS handling here one day
705: */
706: /* Take a backup before we do anything silly 931205
707: */
708: if (HTTakeBackup) {
709: char * backup_filename = 0;
710: char * p = backup_filename;
711: backup_filename = malloc(strlen(filename)+2);
712: if (!backup_filename) outofmem(__FILE__, "taking backup");
713: strcpy(backup_filename, filename);
1.34 timbl 714: for(p=backup_filename+strlen(backup_filename);; p--) {
1.33 timbl 715: if ((*p=='/') || (p<backup_filename)) {
716: p[1]=','; /* Insert comma after slash */
717: break;
718: }
719: p[1] = p[0]; /* Move up everything to the right of it */
720: }
721:
1.35 luotonen 722: if ((fp=fopen(filename, "r"))) { /* File exists */
1.33 timbl 723: fclose(fp);
1.56 frystyk 724: if (TRACE) fprintf(stderr, "File `%s' exists\n", filename);
1.33 timbl 725: if (remove(backup_filename)) {
1.56 frystyk 726: if (TRACE) fprintf(stderr, "Backup file `%s' removed\n",
727: backup_filename);
1.33 timbl 728: }
729: if (rename(filename, backup_filename)) { /* != 0 => Failure */
1.56 frystyk 730: if (TRACE) fprintf(stderr, "Rename `%s' to `%s' FAILED!\n",
731: filename, backup_filename);
1.33 timbl 732: } else { /* Success */
1.56 frystyk 733: if (TRACE) fprintf(stderr, "Renamed `%s' to `%s'\n", filename,
734: backup_filename);
1.33 timbl 735: }
736: }
737: free(backup_filename);
738: } /* if take backup */
1.2 timbl 739:
1.33 timbl 740: {
741: fp = fopen(filename, "w");
742: if (!fp) return NULL;
1.2 timbl 743:
1.40 frystyk 744: return HTFWriter_new(fp, NO);
1.33 timbl 745: }
1.2 timbl 746: }
747:
1.10 secret 748: /* Output one directory entry
749: **
750: */
1.58 ! frystyk 751: #if OLD_CODE
1.10 secret 752: PUBLIC void HTDirEntry ARGS3(HTStructured *, target,
753: CONST char * , tail,
754: CONST char *, entry)
755: {
756: char * relative;
757: char * escaped = HTEscape(entry, URL_XPALPHAS);
758:
759: /* If empty tail, gives absolute ref below */
1.37 luotonen 760: relative = (char*) malloc(strlen(tail) + strlen(escaped)+2);
1.10 secret 761: if (relative == NULL) outofmem(__FILE__, "DirRead");
762: sprintf(relative, "%s/%s", tail, escaped);
1.22 timbl 763: HTStartAnchor(target, NULL, relative);
1.10 secret 764: free(escaped);
765: free(relative);
766: PUTS(entry);
767: END(HTML_A);
768: }
1.58 ! frystyk 769: #endif
1.10 secret 770:
771: /* Output parent directory entry
772: **
773: ** This gives the TITLE and H1 header, and also a link
774: ** to the parent directory if appropriate.
775: */
1.58 ! frystyk 776: #ifdef OLD_CODE
1.10 secret 777: PUBLIC void HTDirTitles ARGS2(HTStructured *, target,
778: HTAnchor * , anchor)
779:
780: {
781: char * logical = HTAnchor_address(anchor);
782: char * path = HTParse(logical, "", PARSE_PATH + PARSE_PUNCTUATION);
783: char * current;
784:
785: current = strrchr(path, '/'); /* last part or "" */
786: free(logical);
787:
788: {
789: char * printable = NULL;
790: StrAllocCopy(printable, (current + 1));
791: HTUnEscape(printable);
792: START(HTML_TITLE);
793: PUTS(*printable ? printable : "Welcome ");
794: PUTS(" directory");
795: END(HTML_TITLE);
796:
797: START(HTML_H1);
798: PUTS(*printable ? printable : "Welcome");
799: END(HTML_H1);
800: free(printable);
801: }
802:
803: /* Make link back to parent directory
804: */
805:
806: if (current && current[1]) { /* was a slash AND something else too */
807: char * parent;
808: char * relative;
809: *current++ = 0;
810: parent = strrchr(path, '/'); /* penultimate slash */
811:
812: relative = (char*) malloc(strlen(current) + 4);
813: if (relative == NULL) outofmem(__FILE__, "DirRead");
814: sprintf(relative, "%s/..", current);
1.58 ! frystyk 815: HTSimplify(relative);
1.10 secret 816: HTStartAnchor(target, "", relative);
817: free(relative);
818:
819: PUTS("Up to ");
820: if (parent) {
821: char * printable = NULL;
822: StrAllocCopy(printable, parent + 1);
823: HTUnEscape(printable);
824: PUTS(printable);
825: free(printable);
826: } else {
827: PUTS("/");
828: }
829:
830: END(HTML_A);
831:
832: }
833: free(path);
834: }
1.58 ! frystyk 835: #endif /* OLD_CODE */
1.2 timbl 836:
1.10 secret 837:
1.1 timbl 838: /* Load a document
839: ** ---------------
840: **
841: ** On entry,
842: ** addr must point to the fully qualified hypertext reference.
843: ** This is the physsical address of the file
844: **
845: ** On exit,
1.2 timbl 846: ** returns <0 Error has occured.
847: ** HTLOADED OK
1.1 timbl 848: **
849: */
1.36 luotonen 850: PUBLIC int HTLoadFile ARGS1 (HTRequest *, request)
1.32 timbl 851:
1.1 timbl 852: {
1.46 luotonen 853: CONST char * addr;
1.1 timbl 854: char * filename;
1.50 frystyk 855: char * access;
1.1 timbl 856: HTFormat format;
1.45 luotonen 857: static char * nodename = 0;
1.1 timbl 858: char * newname=0; /* Simplified name of file */
1.47 luotonen 859: HTAtom * encoding;
860: HTAtom * language;
1.45 luotonen 861:
1.46 luotonen 862: if (!request || !request->anchor)
863: return HT_INTERNAL;
864:
1.45 luotonen 865: FREE(nodename); /* From prev call - Leak fixed AL 6 Feb 1994 */
1.19 timbl 866:
1.1 timbl 867: /* Reduce the filename to a basic form (hopefully unique!)
868: */
1.46 luotonen 869: addr = HTAnchor_physical(request->anchor);
1.1 timbl 870: StrAllocCopy(newname, addr);
871: filename=HTParse(newname, "", PARSE_PATH|PARSE_PUNCTUATION);
872: nodename=HTParse(newname, "", PARSE_HOST);
1.50 frystyk 873:
874: /* If access is ftp => go directly to ftp code (henrik 27/02-94) */
875: access = HTParse(newname, "", PARSE_ACCESS);
876: if(!strcmp("ftp", access)) {
877: FREE(newname);
878: FREE(access);
879: FREE(filename); /* Not used anymore */
880: goto try_ftp;
881: } else {
882: FREE(newname);
883: FREE(access);
884: }
885:
1.47 luotonen 886: format = HTFileFormat(filename, &encoding, &language);
1.16 secret 887:
1.23 duns 888: #ifdef VMS
1.1 timbl 889: /* Assume that the file is in Unix-style syntax if it contains a '/'
890: after the leading one @@ */
891: {
1.23 duns 892: FILE * fp;
1.1 timbl 893: char * vmsname = strchr(filename + 1, '/') ?
1.28 duns 894: HTVMS_name(nodename, filename) : filename + 1;
1.23 duns 895: fp = fopen(vmsname, "r", "shr=put", "shr=upd");
1.1 timbl 896:
897: /* If the file wasn't VMS syntax, then perhaps it is ultrix
898: */
1.23 duns 899: if (!fp) {
1.1 timbl 900: char ultrixname[INFINITY];
901: if (TRACE) fprintf(stderr, "HTFile: Can't open as %s\n", vmsname);
902: sprintf(ultrixname, "%s::\"%s\"", nodename, filename);
1.23 duns 903: fp = fopen(ultrixname, "r", "shr=put", "shr=upd");
904: if (!fp) {
1.1 timbl 905: if (TRACE) fprintf(stderr,
906: "HTFile: Can't open as %s\n", ultrixname);
907: }
908: }
1.23 duns 909: if (fp)
910: {
911: if (HTEditable(vmsname)) {
912: HTAtom * put = HTAtom_for("PUT");
1.29 timbl 913: HTList * methods = HTAnchor_methods(request->anchor);
1.23 duns 914: if (HTList_indexOf(methods, put) == (-1)) {
915: HTList_addObject(methods, put);
916: }
917: }
1.52 duns 918: HTParseFile(format, fp, request);
1.23 duns 919: fclose(fp);
920: return HT_LOADED;
921: } /* If successfull open */
1.1 timbl 922: }
923: #else
924:
1.38 luotonen 925: FREE(filename);
926:
1.1 timbl 927: /* For unix, we try to translate the name into the name of a transparently
928: ** mounted file.
929: **
930: ** Not allowed in secure (HTClienntHost) situations TBL 921019
931: */
932: #ifndef NO_UNIX_IO
1.17 timbl 933: /* Need protection here for telnet server but not httpd server */
1.2 timbl 934:
1.17 timbl 935: if (!HTSecure) { /* try local file system */
1.47 luotonen 936: char * localname = HTLocalName(addr);
1.2 timbl 937: struct stat dir_info;
1.36 luotonen 938: char * multi;
939:
1.1 timbl 940: #ifdef GOT_READ_DIR
1.2 timbl 941:
942: /* Multiformat handling
943: **
944: ** If needed, scan directory to find a good file.
945: ** Bug: we don't stat the file to find the length
946: */
1.47 luotonen 947: multi = strrchr(localname, MULTI_SUFFIX[0]);
1.36 luotonen 948: if (multi && !strcmp(multi, MULTI_SUFFIX)) {
1.47 luotonen 949: struct stat stat_info;
950: char * new_path = HTMulti(request, localname, &stat_info);
951: if (new_path) {
952: FREE(localname);
953: localname = new_path;
954: HTAnchor_setPhysical(request->anchor, localname);
955: format = HTFileFormat(localname, &encoding, &language);
1.36 luotonen 956: goto open_file;
1.2 timbl 957: }
1.36 luotonen 958: else { /* If not found suitable file */
1.47 luotonen 959: FREE(localname);
1.36 luotonen 960: return HTLoadError(request, 403, /* List formats? */
1.2 timbl 961: "Could not find suitable representation for transmission.");
962: }
963: } /* if multi suffix */
1.1 timbl 964: /*
965: ** Check to see if the 'localname' is in fact a directory. If it is
966: ** create a new hypertext object containing a list of files and
967: ** subdirectories contained in the directory. All of these are links
968: ** to the directories or files listed.
1.12 timbl 969: ** NB This assumes the existance of a type 'STRUCT_DIRENT', which will
1.1 timbl 970: ** hold the directory entry, and a type 'DIR' which is used to point to
971: ** the current directory being read.
972: */
973:
974:
1.47 luotonen 975: if (stat(localname,&dir_info) == -1) { /* get file information */
1.1 timbl 976: /* if can't read file information */
1.47 luotonen 977: if (TRACE) fprintf(stderr, "HTFile: can't stat %s\n", localname);
1.1 timbl 978:
979: } else { /* Stat was OK */
980: if (((dir_info.st_mode) & S_IFMT) == S_IFDIR) {
1.58 ! frystyk 981: int ret;
! 982: ret = HTBrowseDirectory(request, localname);
! 983: FREE(localname);
! 984: return ret;
! 985:
! 986: #ifdef OLD_CODE
1.47 luotonen 987: /* if localname is a directory */
1.2 timbl 988:
989: HTStructured* target; /* HTML object */
990: HTStructuredClass targetClass;
991:
1.1 timbl 992: DIR *dp;
1.12 timbl 993: STRUCT_DIRENT * dirbuf;
1.2 timbl 994:
1.6 timbl 995: char * logical;
1.55 luotonen 996: static char * tail = NULL;
997:
1.2 timbl 998: BOOL present[HTML_A_ATTRIBUTES];
999:
1.1 timbl 1000: char * tmpfilename = NULL;
1.2 timbl 1001: struct stat file_info;
1.55 luotonen 1002:
1.1 timbl 1003: if (TRACE)
1.47 luotonen 1004: fprintf(stderr,"%s is a directory\n",localname);
1.1 timbl 1005:
1.2 timbl 1006: /* Check directory access.
1007: ** Selective access means only those directories containing a
1008: ** marker file can be browsed
1009: */
1010: if (HTDirAccess == HT_DIR_FORBID) {
1.47 luotonen 1011: FREE(localname);
1.36 luotonen 1012: return HTLoadError(request, 403,
1.2 timbl 1013: "Directory browsing is not allowed.");
1014: }
1015:
1016:
1017: if (HTDirAccess == HT_DIR_SELECTIVE) {
1018: char * enable_file_name =
1.47 luotonen 1019: malloc(strlen(localname)+ 1 +
1.2 timbl 1020: strlen(HT_DIR_ENABLE_FILE) + 1);
1.47 luotonen 1021: strcpy(enable_file_name, localname);
1.2 timbl 1022: strcat(enable_file_name, "/");
1023: strcat(enable_file_name, HT_DIR_ENABLE_FILE);
1024: if (stat(enable_file_name, &file_info) != 0) {
1.47 luotonen 1025: FREE(localname);
1.36 luotonen 1026: return HTLoadError(request, 403,
1.2 timbl 1027: "Selective access is not enabled for this directory");
1028: }
1029: }
1030:
1031:
1.47 luotonen 1032: dp = opendir(localname);
1.2 timbl 1033: if (!dp) {
1.47 luotonen 1034: FREE(localname);
1.36 luotonen 1035: return HTLoadError(request, 403,
1036: "This directory is not readable.");
1.2 timbl 1037: }
1038:
1039:
1040: /* Directory access is allowed and possible
1041: */
1.29 timbl 1042: logical = HTAnchor_address((HTAnchor*)request->anchor);
1.55 luotonen 1043: FREE(tail); /* From previous call */
1.6 timbl 1044: tail = strrchr(logical, '/') +1; /* last part or "" */
1.36 luotonen 1045:
1046: /*
1047: ** Fix AL 26.1.94: make dir.indexing work also if
1048: ** there is a trailing slash:
1049: */
1.57 luotonen 1050: if (!tail || !*tail) tail = ".";
1.55 luotonen 1051: tail = HTEscape(tail, URL_XALPHAS);
1.36 luotonen 1052:
1.30 timbl 1053: target = HTML_new(request, NULL, WWW_HTML,
1054: request->output_format, request->output_stream);
1.2 timbl 1055: targetClass = *target->isa; /* Copy routine entry points */
1056:
1057: { int i;
1058: for(i=0; i<HTML_A_ATTRIBUTES; i++)
1059: present[i] = (i==HTML_A_HREF);
1060: }
1.1 timbl 1061:
1.29 timbl 1062: HTDirTitles(target, (HTAnchor *)request->anchor);
1.10 secret 1063:
1.2 timbl 1064: if (HTDirReadme == HT_DIR_README_TOP)
1.47 luotonen 1065: do_readme(target, localname);
1.7 secret 1066: {
1.9 timbl 1067: HTBTree * bt = HTBTree_new((HTComparer)strcasecomp);
1.2 timbl 1068:
1.21 timbl 1069: while ((dirbuf = readdir(dp))!=0)
1.7 secret 1070: {
1071: HTBTElement * dirname = NULL;
1.2 timbl 1072:
1073: /* while there are directory entries to be read */
1.7 secret 1074: if (dirbuf->d_ino == 0)
1075: /* if the entry is not being used, skip it */
1076: continue;
1.1 timbl 1077:
1.10 secret 1078:
1.2 timbl 1079: /* if the current entry is parent directory */
1.10 secret 1080: if ((*(dirbuf->d_name)=='.') ||
1.7 secret 1081: (*(dirbuf->d_name)==','))
1.2 timbl 1082: continue; /* skip those files whose name begins
1083: with '.' or ',' */
1.10 secret 1084:
1.14 timbl 1085: dirname = (HTBTElement *)malloc(
1086: strlen(dirbuf->d_name) + 2);
1.7 secret 1087: if (dirname == NULL) outofmem(__FILE__,"DirRead");
1.47 luotonen 1088: StrAllocCopy(tmpfilename,localname);
1089: if (strcmp(localname,"/"))
1.7 secret 1090:
1091: /* if filename is not root directory */
1092: StrAllocCat(tmpfilename,"/");
1.10 secret 1093:
1.7 secret 1094:
1095: StrAllocCat(tmpfilename,dirbuf->d_name);
1096: stat(tmpfilename, &file_info);
1097: if (((file_info.st_mode) & S_IFMT) == S_IFDIR)
1098: sprintf((char *)dirname,"D%s",dirbuf->d_name);
1099: else sprintf((char *)dirname,"F%s",dirbuf->d_name);
1100: /* D & F to have first directories, then files */
1101: HTBTree_add(bt,dirname); /* Sort dirname in the tree bt */
1.6 timbl 1102: }
1.7 secret 1103:
1104: /* Run through tree printing out in order
1105: */
1106: {
1107: HTBTElement * next_element = HTBTree_next(bt,NULL);
1108: /* pick up the first element of the list */
1.15 secret 1109: char state;
1110: /* I for initial (.. file),
1111: D for directory file,
1112: F for file */
1113:
1114: state = 'I';
1.7 secret 1115:
1116: while (next_element != NULL)
1117: {
1.47 luotonen 1118: StrAllocCopy(tmpfilename,localname);
1119: if (strcmp(localname,"/"))
1.7 secret 1120:
1.2 timbl 1121: /* if filename is not root directory */
1.7 secret 1122: StrAllocCat(tmpfilename,"/");
1123:
1.15 secret 1124: StrAllocCat(tmpfilename,
1125: (char *)HTBTree_object(next_element)+1);
1.7 secret 1126: /* append the current entry's filename to the path */
1127: HTSimplify(tmpfilename);
1128: /* Output the directory entry */
1.15 secret 1129: if (strcmp((char *)
1130: (HTBTree_object(next_element)),"D.."))
1131: {
1132: if (state != *(char *)(HTBTree_object(next_element)))
1.7 secret 1133: {
1.15 secret 1134: if (state == 'D')
1135: END(HTML_DIR);
1136: state = *(char *)
1137: (HTBTree_object(next_element))=='D'?'D':'F';
1.7 secret 1138: START(HTML_H2);
1.15 secret 1139: PUTS(state == 'D'?"Subdirectories:":"Files");
1.7 secret 1140: END(HTML_H2);
1141: START(HTML_DIR);
1142: }
1.15 secret 1143: START(HTML_LI);
1.7 secret 1144: }
1.10 secret 1145: HTDirEntry(target, tail,
1146: (char*)HTBTree_object(next_element) +1);
1.7 secret 1147:
1148: next_element = HTBTree_next(bt,next_element);
1149: /* pick up the next element of the list;
1150: if none, return NULL*/
1151: }
1.15 secret 1152: if (state == 'I')
1153: {
1154: START(HTML_P);
1155: PUTS("Empty Directory");
1156: }
1157: else
1.7 secret 1158: END(HTML_DIR);
1.2 timbl 1159: }
1.16 secret 1160:
1.7 secret 1161: /* end while directory entries left to read */
1162: closedir(dp);
1163: free(logical);
1164: free(tmpfilename);
1165: HTBTreeAndObject_free(bt);
1166:
1167: if (HTDirReadme == HT_DIR_README_BOTTOM)
1.47 luotonen 1168: do_readme(target, localname);
1.7 secret 1169: FREE_TARGET;
1.47 luotonen 1170: FREE(localname);
1.7 secret 1171: return HT_LOADED; /* document loaded */
1.2 timbl 1172: }
1.58 ! frystyk 1173: #endif /* OLD_CODE */
1.47 luotonen 1174: } /* end if localname is directory */
1.1 timbl 1175: } /* end if file stat worked */
1176:
1177: /* End of directory reading section
1178: */
1.58 ! frystyk 1179: #endif /* GOT_READ_DIR */
1.2 timbl 1180: open_file:
1.16 secret 1181: {
1.47 luotonen 1182: FILE * fp = fopen(localname,"r");
1.24 luotonen 1183:
1.19 timbl 1184: if(TRACE) fprintf (stderr, "HTFile: Opening `%s' gives %p\n",
1.47 luotonen 1185: localname, (void*)fp);
1.16 secret 1186: if (fp) { /* Good! */
1.47 luotonen 1187: if (HTEditable(localname)) {
1.16 secret 1188: HTAtom * put = HTAtom_for("PUT");
1.29 timbl 1189: HTList * methods = HTAnchor_methods(request->anchor);
1.16 secret 1190: if (HTList_indexOf(methods, put) == (-1)) {
1191: HTList_addObject(methods, put);
1192: }
1.2 timbl 1193: }
1.47 luotonen 1194: FREE(localname);
1.30 timbl 1195: HTParseFile(format, fp, request);
1.16 secret 1196: fclose(fp);
1197: return HT_LOADED;
1198: } /* If succesfull open */
1.48 frystyk 1199: FREE(localname); /* If error in fopen */
1.16 secret 1200: } /* scope of fp */
1201: } /* local unix file system */
1.1 timbl 1202: #endif
1203: #endif
1.45 luotonen 1204:
1205: try_ftp:
1.1 timbl 1206:
1207: #ifndef DECNET
1208: /* Now, as transparently mounted access has failed, we try FTP.
1209: */
1.16 secret 1210: {
1.58 ! frystyk 1211: if (nodename && *nodename && strcmp(nodename, HTHostName())!=0) {
1.57 luotonen 1212: #ifdef OLD_CODE
1.31 timbl 1213: return HTFTPLoad(request, NULL, addr,
1214: request->anchor, request->output_format, request->output_stream);
1.57 luotonen 1215: #endif
1.58 ! frystyk 1216: char * newname = NULL;
! 1217: StrAllocCopy(newname, "ftp:");
! 1218: if (!strncmp(addr, "file:", 5))
! 1219: StrAllocCat(newname, addr+5);
! 1220: else
! 1221: StrAllocCat(newname, addr);
! 1222: HTAnchor_setPhysical(request->anchor, newname);
! 1223: free(newname);
! 1224: return HTLoad(request);
! 1225: }
1.1 timbl 1226: }
1227: #endif
1228:
1.16 secret 1229: /* All attempts have failed.
1.1 timbl 1230: */
1.16 secret 1231: {
1.2 timbl 1232: if (TRACE)
1.56 frystyk 1233: fprintf(stderr, "Can't open `%s', errno=%d\n", addr, errno);
1.36 luotonen 1234: return HTLoadError(request, 403, "Can't access requested file.");
1.2 timbl 1235: }
1.1 timbl 1236:
1.16 secret 1237:
1.1 timbl 1238: }
1239:
1240: /* Protocol descriptors
1241: */
1.57 luotonen 1242: GLOBALDEF PUBLIC HTProtocol HTFTP = { "ftp", HTFTPLoad, 0 , 0 };
1.29 timbl 1243: GLOBALDEF PUBLIC HTProtocol HTFile = { "file", HTLoadFile,
1244: HTFileSaveStream, 0 };
Webmaster