Some compile fixes, not enough
[erwise] / Cl / WWWLibrary / HTFile.c
1 /*                      File Access                             HTFile.c
2 **                      ===========
3 **
4 **      This is unix-specific code in general, with some VMS bits.
5 **      These are routines for file access used by WWW browsers.
6 **
7 ** History:
8 **         Feb 91       Written Tim Berners-Lee CERN/CN
9 **         Apr 91       vms-vms access included using DECnet syntax
10 **
11 ** Bugs:
12 **      Cannot access VMS files from a unix machine. How can we know that the
13 **      target machine runs VMS?
14 */
15
16 #define INFINITY 512            /* file name length @@ FIXME */
17
18
19
20 #ifdef unix                    /* if this is to compile on a UNIX machine */
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/dir.h>
24 #include "HText.h"
25 #define GOT_READ_DIR 1    /* if directory reading functions are available */
26 #endif
27
28 #include "HTUtils.h"
29 #include "HTFile.h"
30
31 #include "WWW.h"
32 #include "HTParse.h"
33 #include "tcp.h"
34 #include "HTTCP.h"
35 #include "HTFTP.h"
36 #include "HTAnchor.h"
37
38 PRIVATE char *HTMountRoot = "/Net/";            /* Where to find mounts */
39 #ifdef vms
40 PRIVATE char *HTCacheRoot = "/WWW$SCRATCH/";   /* Where to cache things */
41 #else
42 PRIVATE char *HTCacheRoot = "/tmp/W3_Cache_";   /* Where to cache things */
43 #endif
44
45 /* PRIVATE char *HTSaveRoot  = "$(HOME)/WWW/";*/    /* Where to save things */
46
47 #ifdef vms
48 /*      Convert unix-style name into VMS name
49 **      -------------------------------------
50 **
51 ** Bug: Returns pointer to static -- non-reentrant
52 */
53 PRIVATE char * vms_name(CONST char * nn, CONST char * fn)
54 {
55
56 /*      We try converting the filename into Files-11 syntax. That is, we assume
57 **      first that the file is, like us, on a VMS node. We try remote
58 **      (or local) DECnet access. Files-11, VMS, VAX and DECnet
59 **      are trademarks of Digital Equipment Corporation. 
60 **      The node is assumed to be local if the hostname WITHOUT DOMAIN
61 **      matches the local one. @@@
62 */
63     static char vmsname[INFINITY];      /* returned */
64     char * filename = (char*)malloc(strlen(fn)+1);
65     char * nodename = (char*)malloc(strlen(nn)+2+1);    /* Copies to hack */
66     char *second;               /* 2nd slash */
67     char *last;                 /* last slash */
68     
69     char * hostname = HTHostName();
70
71     strcpy(filename, fn);
72     strcpy(nodename, "");       /* On same node? Yes if node names match */
73     {
74         char *p, *q;
75         for (p=hostname, q=nn; *p && *p!='.' && *q && *q!='.'; p++, q++){
76             if (TOUPPER(*p)!=TOUPPER(*q)) {
77                 strcpy(nodename, nn);
78                 q = strchr(nodename, '.');      /* Mismatch */
79                 if (q) *q=0;                    /* Chop domain */
80                 strcat(nodename, "::");         /* Try decnet anyway */
81                 break;
82             }
83         }
84     }
85
86     second = strchr(filename+1, '/');           /* 2nd slash */
87     last = strrchr(filename, '/');      /* last slash */
88         
89     if (!second) {                              /* Only one slash */
90         sprintf(vmsname, "%s%s", nodename, filename);
91     } else if(second==last) {           /* Exactly two slashes */
92         *second = 0;            /* Split filename from disk */
93         sprintf(vmsname, "%s%s:%s", nodename, filename+1, second+1);
94         *second = '/';  /* restore */
95     } else {                            /* More than two slashes */
96         char * p;
97         *second = 0;            /* Split disk from directories */
98         *last = 0;              /* Split dir from filename */
99         sprintf(vmsname, "%s%s:[%s]%s",
100                 nodename, filename+1, second+1, last+1);
101         *second = *last = '/';  /* restore filename */
102         for (p=strchr(vmsname, '['); *p!=']'; p++)
103             if (*p=='/') *p='.';        /* Convert dir sep.  to dots */
104     }
105     free(nodename);
106     free(filename);
107     return vmsname;
108 }
109
110
111 #endif /* vms */
112
113 /*      Make the cache file name for a W3 document
114 **      ------------------------------------------
115 **      Make up a suitable name for saving the node in
116 **
117 **      E.g.    /tmp/WWW_Cache_news/1234@cernvax.cern.ch
118 **              /tmp/WWW_Cache_http/crnvmc/FIND/xx.xxx.xx
119 **
120 ** On exit,
121 **      returns a malloc'ed string which must be freed by the caller.
122 */
123 PUBLIC char * HTCacheFileName ARGS1(CONST char *,name)
124 {
125     char * access = HTParse(name, "", PARSE_ACCESS);
126     char * host = HTParse(name, "", PARSE_HOST);
127     char * path = HTParse(name, "", PARSE_PATH+PARSE_PUNCTUATION);
128     
129     char * result;
130     result = (char *)malloc(
131             strlen(HTCacheRoot)+strlen(access)
132             +strlen(host)+strlen(path)+6+1);
133     if (result == NULL) outofmem(__FILE__, "HTCacheFileName");
134     sprintf(result, "%s/WWW/%s/%s%s", HTCacheRoot, access, host, path);
135     free(path);
136     free(access);
137     free(host);
138     return result;
139 }
140
141 /*      Open a file for write, creating the path
142 **      ----------------------------------------
143 */
144 #ifdef NOT_IMPLEMENTED
145 PRIVATE int HTCreatePath ARGS1(CONST char *,path)
146 {
147     return -1;
148 }
149 #endif
150
151 /*      Convert filenames between local and WWW formats
152 **      -----------------------------------------------
153 **      Make up a suitable name for saving the node in
154 **
155 **      E.g.    $(HOME)/WWW/news/1234@cernvax.cern.ch
156 **              $(HOME)/WWW/http/crnvmc/FIND/xx.xxx.xx
157 **
158 ** On exit,
159 **      returns a malloc'ed string which must be freed by the caller.
160 */
161 PUBLIC char * HTLocalName ARGS1(CONST char *,name)
162 {
163     char * access = HTParse(name, "", PARSE_ACCESS);
164     char * host = HTParse(name, "", PARSE_HOST);
165     char * path = HTParse(name, "", PARSE_PATH+PARSE_PUNCTUATION);
166     
167     if (0==strcmp(access, "file")) {
168         free(access);   
169         if ((0==strcmp(host, HTHostName())) || !*host) {
170             free(host);
171             if (TRACE) printf("Node `%s' means path `%s'\n", name, path);
172             return(path);
173         } else {
174             char * result = (char *)malloc(
175                                 strlen("/Net/")+strlen(host)+strlen(path)+1);
176               if (result == NULL) outofmem(__FILE__, "HTLocalName");
177             sprintf(result, "%s%s%s", "/Net/", host, path);
178             free(host);
179             free(path);
180             if (TRACE) printf("Node `%s' means file `%s'\n", name, result);
181             return result;
182         }
183     } else {  /* other access */
184         char * result;
185         CONST char * home =  (CONST char*)getenv("HOME");
186         if (!home) home = "/tmp"; 
187         result = (char *)malloc(
188                 strlen(home)+strlen(access)+strlen(host)+strlen(path)+6+1);
189       if (result == NULL) outofmem(__FILE__, "HTLocalName");
190         sprintf(result, "%s/WWW/%s/%s%s", home, access, host, path);
191         free(path);
192         free(access);
193         free(host);
194         return result;
195     }
196 }
197
198
199 /*      Make a WWW name from a full local path name
200 **
201 ** Bugs:
202 **      At present, only the names of two network root nodes are hand-coded
203 **      in and valid for the NeXT only. This should be configurable in
204 **      the general case.
205 */
206
207 PUBLIC char * WWW_nameOfFile ARGS1 (CONST char *,name)
208 {
209     char * result;
210 #ifdef NeXT
211     if (0==strncmp("/private/Net/", name, 13)) {
212         result = (char *)malloc(7+strlen(name+13)+1);
213         if (result == NULL) outofmem(__FILE__, "WWW_nameOfFile");
214         sprintf(result, "file://%s", name+13);
215     } else
216 #endif
217     if (0==strncmp(HTMountRoot, name, 5)) {
218         result = (char *)malloc(7+strlen(name+5)+1);
219         if (result == NULL) outofmem(__FILE__, "WWW_nameOfFile");
220         sprintf(result, "file://%s", name+5);
221     } else {
222         result = (char *)malloc(7+strlen(HTHostName())+strlen(name)+1);
223         if (result == NULL) outofmem(__FILE__, "WWW_nameOfFile");
224         sprintf(result, "file://%s%s", HTHostName(), name);
225     }
226     if (TRACE) printf("File `%s'\n\tmeans node `%s'\n", name, result);
227     return result;
228 }
229
230
231 /*      Determine file format from file name
232 **      ------------------------------------
233 **
234 ** Note: This function is also in the server file HTRetrieve.c
235 ** If it gets complicated, it should be shared.
236 **
237 */
238
239 PUBLIC HTFormat HTFileFormat ARGS1 (CONST char *,filename)
240 {
241     CONST char * extension;
242     for (extension=filename+strlen(filename);
243         (extension>filename) &&
244                 (*extension != '.') &&
245                 (*extension!='/');
246         extension--) /* search */ ;
247
248     if (*extension == '.') {
249         return    0==strcmp(".html", extension) ? WWW_HTML
250                 : 0==strcmp(".rtf",  extension) ? WWW_RICHTEXT
251                 : 0==strcmp(".txt",  extension) ? WWW_PLAINTEXT
252                 : WWW_PLAINTEXT;        /* Unrecognised, try plain text */
253     } else {
254         return WWW_PLAINTEXT;
255     } 
256 }
257
258
259 /*      Determine write access to a file
260 //      --------------------------------
261 //
262 // On exit,
263 //      return value    YES if file can be accessed and can be written to.
264 //
265 // Bugs:
266 //      1.      No code for non-unix systems.
267 //      2.      Isn't there a quicker way?
268 */
269
270 #ifdef vms
271 #define NO_GROUPS
272 #endif
273 #ifdef NO_UNIX_IO
274 #define NO_GROUPS
275 #endif
276 #ifdef PCNFS
277 #define NO_GROUPS
278 #endif
279
280 PUBLIC BOOL HTEditable ARGS1 (CONST char *,filename)
281 {
282 #ifdef NO_GROUPS
283     return NO;          /* Safe answer till we find the correct algorithm */
284 #else
285     int         groups[NGROUPS];        
286     uid_t       myUid;
287     int         ngroups;                        /* The number of groups  */
288     struct stat fileStatus;
289     int         i;
290         
291     if (stat(filename, &fileStatus))            /* Get details of filename */
292         return NO;                              /* Can't even access file! */
293
294     ngroups = getgroups(NGROUPS, groups);       /* Groups to which I belong  */
295     myUid = geteuid();                          /* Get my user identifier */
296
297     if (TRACE) {
298         int i;
299         printf("File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (",
300             fileStatus.st_mode, fileStatus.st_uid, fileStatus.st_gid,
301             myUid, ngroups);
302         for (i=0; i<ngroups; i++) printf(" %d", groups[i]);
303         printf(")\n");
304     }
305     
306     if (fileStatus.st_mode & 0002)              /* I can write anyway? */
307         return YES;
308         
309     if ((fileStatus.st_mode & 0200)             /* I can write my own file? */
310      && (fileStatus.st_uid == myUid))
311         return YES;
312
313     if (fileStatus.st_mode & 0020)              /* Group I am in can write? */
314     {
315         for (i=0; i<ngroups; i++) {
316             if (groups[i] == fileStatus.st_gid)
317                 return YES;
318         }
319     }
320     if (TRACE) printf("\tFile is not editable.\n");
321     return NO;                                  /* If no excuse, can't do */
322 #endif
323 }
324
325
326 /*      Open a file descriptor for a document
327 **      -------------------------------------
328 **
329 ** On entry,
330 **      addr            must point to the fully qualified hypertext reference.
331 **
332 ** On exit,
333 **      returns         <0      Error has occured.
334 **                      >=0     Value of file descriptor or socket to be used
335 **                               to read data.
336 **      *pFormat        Set to the format of the file, if known.
337 **                      (See WWW.h)
338 **
339 */
340 int HTOpenFile
341 ARGS3
342 (
343  CONST char *,addr,
344  HTFormat *,pFormat,
345  HTParentAnchor *,anchor
346 )
347 {
348     char * filename;
349     int fd = -1;                /* Unix file descriptor number = INVALID */
350     char * nodename = 0;
351     char * newname=0;   /* Simplified name of file */
352
353 /*      Reduce the filename to a basic form (hopefully unique!)
354 */
355     StrAllocCopy(newname, addr);
356     filename=HTParse(newname, "", PARSE_PATH|PARSE_PUNCTUATION);
357     nodename=HTParse(newname, "", PARSE_HOST);
358     free(newname);
359     
360     *pFormat = HTFileFormat(filename);
361
362     
363 #ifdef vms
364 /* Assume that the file is remote vms or ultrix if no domain name @@ */
365     if (!strchr(nodename, '.')) {
366         char * vmsname = vms_name(nodename, filename);
367         fd = open(vmsname, O_RDONLY, 0);
368
369 /*      If the file wasn't VMS syntax, then perhaps it is ultrix
370 */
371         if (fd<0) {
372             char ultrixname[INFINITY];
373             if (TRACE) fprintf(stderr, "HTFile: Can't open as %s\n", vmsname);
374             sprintf(ultrixname, "%s::\"%s\"", nodename, filename);
375             fd = open(ultrixname, O_RDONLY, 0);
376             if (fd<0) {
377                 if (TRACE) fprintf(stderr, 
378                         "HTFile: Can't open as %s\n", ultrixname);
379             }
380         }
381     }
382 #else
383
384 /*      For unix, we try to translate the name into the name of a transparently
385 **      mounted file.
386 */
387 #ifndef NO_UNIX_IO
388     {
389         char * localname = HTLocalName(addr);
390         
391         
392 #ifdef GOT_READ_DIR
393 /*
394 **      Check to see if the 'localname' is in fact a directory.  If it is
395 **      create a new hypertext object containing a list of files and 
396 **      subdirectories contained in the directory.  All of these are links
397 **      to the directories or files listed.
398 **      NB This assumes the existance of a type 'struct direct', which will
399 **      hold the directory entry, and a type 'DIR' which is used to point to
400 **      the current directory being read.
401 */
402         
403         struct stat dir_info;
404         
405         if (stat(localname,&dir_info) == -1) {     /* get file information */
406                                        /* if can't read file information */
407             if (TRACE) fprintf(stderr, "HTFile: can't stat %s\n", localname);
408
409         }  else {               /* Stat was OK */
410                 
411
412             if (((dir_info.st_mode) & S_IFMT) == S_IFDIR) {
413                 /* if localname is a directory */       
414                 HTChildAnchor * child;
415                 HText * HT;
416                 extern HTStyleSheet * styleSheet;
417                 static HTStyle * DirectoryStyle = 0;
418                 static HTStyle * H1Style = 0;
419                 DIR *dp;
420                 struct direct * dirbuf;
421                     
422                 char * tmpfilename = NULL;
423                 char * shortfilename = NULL;
424                 struct stat *file_info = malloc(sizeof(struct stat));
425                 
426                 if (TRACE)
427                     fprintf(stderr,"%s is a directory\n",localname);
428                         
429                 if (!H1Style)
430                     H1Style = HTStyleNamed(styleSheet, "Heading1");
431                 
432                 if (!DirectoryStyle)
433                     DirectoryStyle = HTStyleNamed(styleSheet, "Dir");
434                 
435                 HTAnchor_setTitle(anchor, filename);
436                     
437                 HT = HText_new(anchor);
438                 HText_beginAppend(HT);
439                     
440                 HText_setStyle(HT,H1Style);
441                 shortfilename=strrchr(localname,'/');
442                 /* put the last part of the path in shortfilename */
443                 shortfilename++;               /* get rid of leading '/' */
444                 if (*shortfilename=='\0')
445                     shortfilename--;
446                     
447                 HText_appendText(HT,shortfilename);
448                 HText_setStyle(HT,DirectoryStyle);
449                 
450                 dp = opendir(localname);
451                 if (dp) {                   
452                    /* if the directory file is readable */
453                     while (dirbuf = readdir(dp)) {
454                                 /* while there are directory entries to be read */
455                         if (dirbuf->d_ino == 0)
456                                         /* if the entry is not being used, skip it */
457                             continue;
458                         
459                         if (!strcmp(dirbuf->d_name,"."))
460                             continue;      /* skip the entry for this directory */
461                             
462                         if (strcmp(dirbuf->d_name,".."))
463                                     /* if the current entry is parent directory */
464                             if ((*(dirbuf->d_name)=='.') ||
465                                 (*(dirbuf->d_name)==','))
466                                 continue;    /* skip those files whose name begins
467                                                 with '.' or ',' */
468     
469                         StrAllocCopy(tmpfilename,localname);
470                         if (strcmp(localname,"/")) 
471                                             /* if filename is not root directory */
472                             StrAllocCat(tmpfilename,"/"); 
473                         else
474                             if (!strcmp(dirbuf->d_name,".."))
475                                 continue;
476                 /* if root directory and current entry is parent
477                                         directory, skip the current entry */
478                         
479                         StrAllocCat(tmpfilename,dirbuf->d_name);
480                         /* append the current entry's filename to the path */
481                         HTSimplify(tmpfilename);
482                 
483                         child = HTAnchor_findChildAndLink(
484                                 anchor, 0, tmpfilename, 0);
485                         HText_beginAnchor(HT, child); 
486                         stat(tmpfilename,file_info);
487                         
488                         if (strcmp(dirbuf->d_name,"..")) {
489 /* if the current entry is not the parent directory then use the file name */
490                             HText_appendText(HT,dirbuf->d_name);
491                             if (((file_info->st_mode) & S_IFMT) == S_IFDIR) 
492                                 HText_appendCharacter(HT, '/'); 
493                         }                       
494                         else {
495                         /* use name of parent directory */
496                             char * endbit = strrchr(tmpfilename, '/');
497                             HText_appendText(HT,"Up to ");
498                             HText_appendText(HT, endbit?endbit+1:tmpfilename);
499                         }       
500                         HText_endAnchor(HT);
501                             
502                         HText_appendCharacter(HT, '\t');
503                     } /* end while directory entries left to read */
504                     
505                     closedir(dp);
506                     free(tmpfilename);
507                     
508                 }  else {   /* Directory is not readable */
509                     if (TRACE)
510                         fprintf(stderr,"HTFile.c: directory %s unreadable\n",
511                                 localname);
512                     HText_appendText(HT,
513                            "Sorry, can't read the contents of this directory - probably read protected\n");
514                             
515                 } /* end if directory not readable */    
516                 
517                 HText_endAppend(HT);
518                     
519                 free(file_info);
520                 free(filename);  
521                 free(localname);
522                 return HT_LOADED;       /* fd not valid, but document loaded anyway */
523                 
524             } /* end if localname is directory */
525         
526         } /* end if file stat worked */
527         
528 /* End of directory reading section
529 */
530 #endif
531
532         fd = open(localname, O_RDONLY, 0);
533         if(TRACE) printf ("HTAccess: Opening `%s' gives %d\n",
534                             localname, fd);
535         free(localname);
536     }
537 #endif
538 #endif
539
540 /*      Now, as transparently mounted access has failed, we try FTP.
541 */
542     if (fd<0)
543         if (strcmp(nodename, HTHostName())!=0) {
544             free(filename);
545             return HTFTP_open_file_read(addr, anchor);
546     }
547
548 /*      All attempts have failed if fd<0.
549 */
550     if (fd<0) printf("Can't open `%s', errno=%d\n", filename, errno);
551     free(filename);
552     return fd;
553
554 }