PRIVATE int FileEvent (SOCKET soc, void * pVoid, HTEventType type) { file_info *file = pVoid; /* Specific access information */ int status = HT_ERROR; HTNet * net = file->net; HTRequest * request = HTNet_request(net); HTParentAnchor * anchor = HTRequest_anchor(request); /* ** Initiate a new file structure and bind to request structure ** This is actually state FILE_BEGIN, but it can't be in the state ** machine as we need the structure first. */ if (type == HTEvent_CLOSE) { /* Interrupted */ HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED, NULL, 0, "HTLoadFile"); FileCleanup(request, HT_INTERRUPTED); return HT_OK; } /* Now jump into the machine. We know the state from the previous run */ while (1) { switch (file->state) { case FS_BEGIN: /* We only support safe (GET, HEAD, etc) methods for the moment */ if (!HTMethod_isSafe(HTRequest_method(request))) { HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_ALLOWED, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; break; } /* Check whether we have access to local disk at all */ if (HTLib_secure()) { HTTRACE(PROT_TRACE, "LoadFile.... No access to local file system\n"); file->state = FS_TRY_FTP; break; } file->local = HTWWWToLocal(HTAnchor_physical(anchor), "", HTRequest_userProfile(request)); if (!file->local) { file->state = FS_TRY_FTP; break; } /* Create a new host object and link it to the net object */ { HTHost * host = NULL; if ((host = HTHost_new("localhost", 0)) == NULL) return HT_ERROR; HTNet_setHost(net, host); if (HTHost_addNet(host, net) == HT_PENDING) { HTTRACE(PROT_TRACE, "HTLoadFile.. Pending...\n"); /* move to the hack state */ file->state = FS_PENDING; return HT_OK; } } file->state = FS_DO_CN; break; case FS_PENDING: /* ** 2000/08/10 JK : This is a funny state. Because of the ** internal libwww stacks, when doing multiple local ** requests (e.g., while using the Robot), we need to ask ** again for the host object. If we had jumped directly to ** the FS_DO_CN state, libwww would have blocked because ** of socket starvation. ** This state is similar to FS_BEGINNING, but just requests ** the host object. ** YES. THIS IS AN UGLY HACK!! */ { HTHost * host = NULL; if ((host = HTHost_new("localhost", 0)) == NULL) return HT_ERROR; HTNet_setHost(net, host); if (HTHost_addNet(host, net) == HT_PENDING) { HTTRACE(PROT_TRACE, "HTLoadFile.. Pending...\n"); file->state = FS_PENDING; return HT_OK; } } file->state = FS_DO_CN; break; case FS_DO_CN: /* ** If we have to do content negotiation then find the object that ** fits best into either what the client has indicated in the ** accept headers or what the client has registered on its own. ** The object chosen can in fact be a directory! However, content ** negotiation only makes sense if we can read the directory! ** We stat the file in order to find the size and to see it if ** exists. */ if (HTRequest_negotiation(request) && HTMethod_isSafe(HTRequest_method(request))) { char * conneg = HTMulti(request, file->local,&file->stat_info); if (conneg) { HT_FREE(file->local); file->local = conneg; HTAnchor_setPhysical(anchor, conneg); HTTRACE(PROT_TRACE, "Load File... Found `%s\'\n" _ conneg); } else { HTTRACE(PROT_TRACE, "Load File... Not found - even tried content negotiation\n"); HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; break; } } else { if (HT_STAT(file->local, &file->stat_info) == -1) { HTTRACE(PROT_TRACE, "Load File... Not found `%s\'\n" _ file->local); HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_FOUND, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; break; } } /* ** Check to see if the 'localname' is in fact a directory. ** Note that we can't do a HEAD on a directory */ if (((file->stat_info.st_mode) & S_IFMT) == S_IFDIR) { if (HTRequest_method(request) == METHOD_GET) file->state = FS_PARSE_DIR; else { HTRequest_addError(request, ERR_INFO, NO, HTERR_NO_CONTENT, NULL, 0, "HTLoadFile"); file->state = FS_NO_DATA; } break; } /* ** If empty file then only serve it if it is editable. We also get ** the bindings for the file suffixes in lack of better bindings */ { BOOL editable = HTEditable(file->local, &file->stat_info); if (file_suffix_binding) HTBind_getAnchorBindings(anchor); if (editable) HTAnchor_appendAllow(anchor, METHOD_PUT); /* Set the file size */ if (file->stat_info.st_size) HTAnchor_setLength(anchor, file->stat_info.st_size); /* Set the file last modified time stamp */ if (file->stat_info.st_mtime > 0) HTAnchor_setLastModified(anchor, file->stat_info.st_mtime); /* Check to see if we can edit it */ if (!editable && !file->stat_info.st_size) { HTRequest_addError(request, ERR_INFO, NO, HTERR_NO_CONTENT, NULL, 0, "HTLoadFile"); file->state = FS_NO_DATA; } else { file->state = (HTRequest_method(request)==METHOD_GET) ? FS_NEED_OPEN_FILE : FS_GOT_DATA; } } break; case FS_NEED_OPEN_FILE: status = HTFileOpen(net, file->local, HT_FB_RDONLY); if (status == HT_OK) { /* ** Create the stream pipe FROM the channel to the application. ** The target for the input stream pipe is set up using the ** stream stack. */ { HTStream * rstream = HTStreamStack(HTAnchor_format(anchor), HTRequest_outputFormat(request), HTRequest_outputStream(request), request, YES); HTNet_setReadStream(net, rstream); HTRequest_setOutputConnected(request, YES); } /* ** Create the stream pipe TO the channel from the application ** and hook it up to the request object */ { HTOutputStream * output = HTNet_getOutput(net, NULL, 0); HTRequest_setInputStream(request, (HTStream *) output); } /* ** Set up concurrent read/write if this request isn't the ** source for a PUT or POST. As source we don't start reading ** before all destinations are ready. If destination then ** register the input stream and get ready for read */ if (HTRequest_isSource(request) && !HTRequest_destinationsReady(request)) return HT_OK; HTRequest_addError(request, ERR_INFO, NO, HTERR_OK, NULL, 0, "HTLoadFile"); file->state = FS_NEED_BODY; /* If we are _not_ using preemptive mode and we are Unix fd's ** then return here to get the same effect as when we are ** connecting to a socket. That way, HTFile acts just like any ** other protocol module even though we are in fact doing ** blocking connect */ if (HTEvent_isCallbacksRegistered()) { if (!HTRequest_preemptive(request)) { if (!HTNet_preemptive(net)) { HTTRACE(PROT_TRACE, "HTLoadFile.. Returning\n"); HTHost_register(HTNet_host(net), net, HTEvent_READ); } else if (!file->timer) { HTTRACE(PROT_TRACE, "HTLoadFile.. Returning\n"); file->timer = HTTimer_new(NULL, ReturnEvent, file, 1, YES, NO); } return HT_OK; } } } else if (status == HT_WOULD_BLOCK || status == HT_PENDING) return HT_OK; else { HTRequest_addError(request, ERR_INFO, NO, HTERR_INTERNAL, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; /* Error or interrupt */ } break; case FS_NEED_BODY: status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_LOADED || status == HT_CLOSED) { file->state = FS_GOT_DATA; } else { HTRequest_addError(request, ERR_INFO, NO, HTERR_FORBIDDEN, NULL, 0, "HTLoadFile"); file->state = FS_ERROR; } break; case FS_PARSE_DIR: status = HTFile_readDir(request, file); if (status == HT_LOADED) file->state = FS_GOT_DATA; else file->state = FS_ERROR; break; case FS_TRY_FTP: { char *url = HTAnchor_physical(anchor); HTAnchor *anchor; char *newname = NULL; StrAllocCopy(newname, "ftp:"); if (!strncmp(url, "file:", 5)) StrAllocCat(newname, url+5); else StrAllocCat(newname, url); anchor = HTAnchor_findAddress(newname); HTRequest_setAnchor(request, anchor); HT_FREE(newname); FileCleanup(request, HT_IGNORE); return HTLoad(request, YES); } break; case FS_GOT_DATA: FileCleanup(request, HT_LOADED); return HT_OK; break; case FS_NO_DATA: FileCleanup(request, HT_NO_DATA); return HT_OK; break; case FS_RETRY: FileCleanup(request, HT_RETRY); return HT_OK; break; case FS_ERROR: FileCleanup(request, HT_ERROR); return HT_OK; break; } } /* End of while(1) */ }
/* Load a document ** --------------- ** ** On entry, ** addr must point to the fully qualified hypertext reference. ** This is the physsical address of the file ** ** On exit, ** returns <0 Error has occured. ** HTLOADED OK ** */ PUBLIC int HTLoadFile ARGS4 ( WWW_CONST char *, addr, HTParentAnchor *, anchor, HTFormat, format_out, HTStream *, sink ) { char * filename; HTFormat format; int fd = -1; /* Unix file descriptor number = INVALID */ char * nodename = 0; char * newname=0; /* Simplified name of file */ HTAtom * encoding; /* @@ not used yet */ int compressed; extern char *HTgeticonname(HTFormat, char *); /* Reduce the filename to a basic form (hopefully unique!) */ StrAllocCopy(newname, addr); filename=HTParse(newname, "", PARSE_PATH|PARSE_PUNCTUATION); nodename=HTParse(newname, "", PARSE_HOST); free(newname); format = HTFileFormat(filename, &encoding, WWW_PLAINTEXT, &compressed); #ifdef vms /* Assume that the file is in Unix-style syntax if it contains a '/' after the leading one @@ */ { char * vmsname = strchr(filename + 1, '/') ? vms_name(nodename, filename) : filename + 1; fd = open(vmsname, O_RDONLY, 0); /* If the file wasn't VMS syntax, then perhaps it is ultrix */ if (fd<0) { char ultrixname[INFINITY]; #ifndef DISABLE_TRACE if (www2Trace) fprintf(stderr, "HTFile: Can't open as %s\n", vmsname); #endif sprintf(ultrixname, "%s::\"%s\"", nodename, filename); fd = open(ultrixname, O_RDONLY, 0); if (fd<0) { #ifndef DISABLE_TRACE if (www2Trace) fprintf(stderr, "HTFile: Can't open as %s\n", ultrixname); #endif } } } #else free(filename); /* For unix, we try to translate the name into the name of a transparently ** mounted file. ** ** Not allowed in secure (HTClienntHost) situations TBL 921019 */ #ifndef NO_UNIX_IO /* Need protection here for telnet server but not httpd server */ { /* try local file system */ char * localname = HTLocalName(addr); struct stat dir_info; if (!localname) goto suicide; #ifdef GOT_READ_DIR /* Multiformat handling ** ** If needed, scan directory to find a good file. ** Bug: we don't stat the file to find the length */ if ( (strlen(localname) > strlen(MULTI_SUFFIX)) && (0==strcmp(localname + strlen(localname) - strlen(MULTI_SUFFIX), MULTI_SUFFIX))) { DIR *dp; STRUCT_DIRENT * dirbuf; float best = NO_VALUE_FOUND; /* So far best is bad */ HTFormat best_rep = NULL; /* Set when rep found */ STRUCT_DIRENT best_dirbuf; /* Best dir entry so far */ char * base = strrchr(localname, '/'); int baselen; if (!base || base == localname) goto forget_multi; *base++ = 0; /* Just got directory name */ baselen = strlen(base)- strlen(MULTI_SUFFIX); base[baselen] = 0; /* Chop off suffix */ dp = opendir(localname); if (!dp) { forget_multi: free(localname); return HTLoadError(sink, 500, "Multiformat: directory scan failed."); } while (dirbuf = readdir(dp)) { /* while there are directory entries to be read */ if (dirbuf->d_ino == 0) continue; /* if the entry is not being used, skip it */ if (!strncmp(dirbuf->d_name, base, baselen)) { HTFormat rep = HTFileFormat(dirbuf->d_name, &encoding, WWW_PLAINTEXT, &compressed); float value = HTStackValue(rep, format_out, HTFileValue(dirbuf->d_name), 0.0 /* @@@@@@ */); if (value != NO_VALUE_FOUND) { #ifndef DISABLE_TRACE if (www2Trace) fprintf(stderr, "HTFile: value of presenting %s is %f\n", HTAtom_name(rep), value); #endif if (value > best) { best_rep = rep; best = value; best_dirbuf = *dirbuf; } } /* if best so far */ } /* if match */ } /* end while directory entries left to read */ closedir(dp); if (best_rep) { format = best_rep; base[-1] = '/'; /* Restore directory name */ base[0] = 0; StrAllocCat(localname, best_dirbuf.d_name); goto open_file; } else { /* If not found suitable file */ free(localname); return HTLoadError(sink, 403, /* List formats? */ "Could not find suitable representation for transmission."); } /*NOTREACHED*/ } /* if multi suffix */ /* ** Check to see if the 'localname' is in fact a directory. If it is ** create a new hypertext object containing a list of files and ** subdirectories contained in the directory. All of these are links ** to the directories or files listed. ** NB This assumes the existance of a type 'STRUCT_DIRENT', which will ** hold the directory entry, and a type 'DIR' which is used to point to ** the current directory being read. */ if (stat(localname,&dir_info) == -1) { /* get file information */ /* if can't read file information */ #ifndef DISABLE_TRACE if (www2Trace) fprintf(stderr, "HTFile: can't stat %s\n", localname); #endif } else { /* Stat was OK */ if (((dir_info.st_mode) & S_IFMT) == S_IFDIR) { /* if localname is a directory */ /* ** ** Read the localdirectory and present a nicely formatted list to the user ** Re-wrote most of the read directory code here, excepting for the checking ** access. ** ** Author: Charles Henrich ([email protected]) 10-09-93 ** ** This is still pretty messy, need to go through and clean it up at some point ** */ /* Define some parameters that everyone should already have */ #ifndef MAXPATHLEN #define MAXPATHLEN 1024 #endif #ifndef BUFSIZ #define BUFSIZ 4096 #endif char filepath[MAXPATHLEN]; char buffer[4096]; char *ptr; char *dataptr; HText * HT; HTFormat format; HTAtom *pencoding; struct stat statbuf; STRUCT_DIRENT * dp; DIR *dfp; int cmpr; int count; #ifndef DISABLE_TRACE if (www2Trace) fprintf(stderr,"%s is a directory\n",localname); #endif /* Check directory access. ** Selective access means only those directories containing a ** marker file can be browsed */ if (HTDirAccess == HT_DIR_FORBID) { free(localname); return HTLoadError(sink, 403, "Directory browsing is not allowed."); } if (HTDirAccess == HT_DIR_SELECTIVE) { char * enable_file_name = malloc(strlen(localname)+ 1 + strlen(HT_DIR_ENABLE_FILE) + 1); strcpy(enable_file_name, localname); strcat(enable_file_name, "/"); strcat(enable_file_name, HT_DIR_ENABLE_FILE); if (stat(enable_file_name, &statbuf) != 0) { free(localname); return HTLoadError(sink, 403, "Selective access is not enabled for this directory."); } } dfp = opendir(localname); if (!dfp) { free(localname); return HTLoadError(sink, 403, "This directory is not readable."); } /* Suck the directory up into a list to be sorted */ HTSortInit(); for(dp=readdir(dfp);dp != NULL;dp=readdir(dfp)) { ptr = malloc(strlen(dp->d_name)+1); if(ptr == NULL) { return HTLoadError(sink, 403, "Ran out of memory in directory read!"); } strcpy(ptr,dp->d_name); HTSortAdd(ptr); } closedir(dfp); /* Sort the dir list */ HTSortSort(); /* Start a new HTML page */ HT = HText_new(); HText_beginAppend(HT); HText_appendText(HT, "<H1>Local Directory "); HText_appendText(HT, localname); HText_appendText(HT, "</H1>\n"); HText_appendText(HT,"<DL>\n"); /* Sort the list and then spit it out in a nice form */ /* How this for a disgusting loop :) */ for(count=0,dataptr=HTSortFetch(count); dataptr != NULL; free(dataptr), count++, dataptr=HTSortFetch(count)) { /* We dont want to see . */ if(strcmp(dataptr,".") == 0) continue; /* If its .. *and* the current directory is / dont show anything, otherwise /* print out a nice Parent Directory entry. /* */ if(strcmp(dataptr,"..") == 0) { if(strcmp(localname,"/") != 0) { strcpy(buffer,localname); ptr = strrchr(buffer, '/'); if(ptr != NULL) *ptr='\0'; if(buffer[0] == '\0') strcpy(buffer,"/"); HText_appendText(HT,"<DD><A HREF=\""); HText_appendText(HT, buffer); HText_appendText(HT,"\"><IMG SRC=\""); HText_appendText(HT, HTgeticonname(NULL, "directory")); HText_appendText(HT,"\"> Parent Directory</a>"); continue; } else { continue; } } /* Get the filesize information from a stat, if we cant stat it, we probably */ /* cant read it either, so ignore it. */ sprintf(filepath,"%s/%s",localname, dataptr); if(stat(filepath, &statbuf) == -1) continue; HText_appendText(HT,"<DD><A HREF=\""); HText_appendText (HT, localname); if(localname[strlen(localname)-1] != '/') { HText_appendText (HT, "/"); } HText_appendText (HT, dataptr); HText_appendText (HT, "\">"); /* If its a directory, dump out a dir icon, dont bother with anything else */ /* if it is a file try and figure out what type of file it is, and grab */ /* the appropriate icon. If we cant figure it out, call it text. If its */ /* a compressed file, call it binary no matter what */ if(statbuf.st_mode & S_IFDIR) { sprintf(buffer,"%s",dataptr); HText_appendText(HT, "<IMG SRC=\""); HText_appendText(HT, HTgeticonname(NULL, "directory")); HText_appendText(HT, "\"> "); } else { sprintf(buffer,"%s (%d bytes)", dataptr, (int)statbuf.st_size); format = HTFileFormat(dataptr, &pencoding, WWW_SOURCE, &cmpr); /* If its executable then call it application, else it might as well be text */ if(cmpr == 0) { HText_appendText(HT, "<IMG SRC=\""); if((statbuf.st_mode & S_IXUSR) || (statbuf.st_mode & S_IXGRP) || (statbuf.st_mode & S_IXOTH)) { HText_appendText(HT, HTgeticonname(format, "application")); } else { HText_appendText(HT, HTgeticonname(format, "text")); } HText_appendText(HT, "\"> "); } else { HText_appendText(HT, "<IMG SRC=\""); HText_appendText(HT, HTgeticonname(NULL, "application")); HText_appendText(HT, "\"> "); } } /* Spit out the anchor */ HText_appendText (HT, buffer); HText_appendText (HT, "</A>\n"); } /* End of list, clean up and we are done */ HText_appendText (HT, "</DL>\n"); HText_endAppend (HT); free(localname); return HT_LOADED; } /* end if localname is directory */ } /* end if file stat worked */ /* End of directory reading section */ #endif open_file: { FILE * fp = fopen(localname,"r"); #ifndef DISABLE_TRACE if(www2Trace) fprintf (stderr, "HTFile: Opening `%s' gives %p\n", localname, (void*)fp); #endif if (fp) { /* Good! */ if (HTEditable(localname)) { HTAtom * put = HTAtom_for("PUT"); HTList * methods = HTAnchor_methods(anchor); if (HTList_indexOf(methods, put) == (-1)) { HTList_addObject(methods, put); } } free(localname); HTParseFile(format, format_out, anchor, fp, sink, compressed); /* This is closed elsewhere...SWP fclose(fp); */ return HT_LOADED; } /* If succesfull open */ } /* scope of fp */ } /* local unix file system */ #endif #endif /* Now, as transparently mounted access has failed, we try FTP. */ suicide: /* return HTFTPLoad(addr, anchor, format_out, sink); */ /* Sorry Charlie...if we are given a file:// URL and it fails, then it fails! Do NOT FTP!! */ return HT_NOT_LOADED; }