/* ** Do multiformat handling ** ----------------------- ** On entry: ** req->conversions accepted content-types ** req->encodings accepted content-transfer-encodings ** req->languages accepted content-languages ** path absolute pathname of the filename for ** which the match is desired. ** stat_info pointer to result space. ** ** On exit: ** returns a newly allocated absolute filepath of the best ** match, or NULL if no match. ** stat_info will contain inode information as ** returned by stat(). */ PUBLIC char * HTMulti (HTRequest * req, char * path, struct stat * stat_info) { char * new_path = NULL; int stat_status = -1; if (!req || !path || !*path || !stat_info) return NULL; #ifdef HAVE_READDIR if (*(path+strlen(path)-1) == '/') { /* Find welcome page */ new_path = get_best_welcome(path); if (new_path) path = new_path; } else{ char * multi = strrchr(path, MULTI_SUFFIX[0]); if (multi && !strcasecomp(multi, MULTI_SUFFIX)) { HTTRACE(PROT_TRACE, "Multi....... by %s suffix\n" _ MULTI_SUFFIX); if (!(new_path = HTGetBest(req, path))) { HTTRACE(PROT_TRACE, "Multi....... failed -- giving up\n"); return NULL; } path = new_path; } else { stat_status = HT_STAT(path, stat_info); if (stat_status == -1) { HTTRACE(PROT_TRACE, "AutoMulti... can't stat \"%s\"(errno %d)\n" _ path _ errno); if (!(new_path = HTGetBest(req, path))) { HTTRACE(PROT_TRACE, "AutoMulti... failed -- giving up\n"); return NULL; } path = new_path; } } } #endif /* HAVE_READDIR */ if (stat_status == -1) stat_status = HT_STAT(path, stat_info); if (stat_status == -1) { HTTRACE(PROT_TRACE, "Stat fails.. on \"%s\" -- giving up (errno %d)\n" _ path _ errno); return NULL; } else { if (!new_path) { StrAllocCopy(new_path, path); return new_path; } else return path; } }
/* Determine write access to a file ** -------------------------------- ** If stat_info is NULL then the function calls stat() on it's own, ** otherwise it uses the information found in stat_info ** On exit, ** return value YES if file can be accessed and can be written to. ** ** Bugs: ** 1. No code for non-unix systems. ** 2. Isn't there a quicker way? */ PRIVATE BOOL HTEditable (const char * filename, struct stat * stat_info) { #ifdef GETGROUPS_T int i; uid_t myUid; int ngroups; /* The number of groups */ struct stat fileStatus; struct stat *fileptr = stat_info ? stat_info : &fileStatus; GETGROUPS_T groups[NGROUPS]; if (!stat_info) { if (HT_STAT(filename, &fileStatus)) return NO; /* Can't even access file! */ } ngroups = getgroups(NGROUPS, groups); /* Groups to which I belong */ myUid = geteuid(); /* Get my user identifier */ #ifdef HTDEBUG if (PROT_TRACE) { int i; HTTRACE(PROT_TRACE, "File mode is 0%o, uid=%d, gid=%d. My uid=%d, %d groups (" _ (unsigned int) fileptr->st_mode _ (int) fileptr->st_uid _ (int) fileptr->st_gid _ (int) myUid _ ngroups); for (i=0; i<ngroups; i++) HTTRACE(PROT_TRACE, " %d" _ (int) groups[i]); HTTRACE(PROT_TRACE, ")\n"); } #endif /* HTDEBUG */ if (fileptr->st_mode & 0002) /* I can write anyway? */ return YES; if ((fileptr->st_mode & 0200) /* I can write my own file? */ && (fileptr->st_uid == myUid)) return YES; if (fileptr->st_mode & 0020) /* Group I am in can write? */ { for (i=0; i<ngroups; i++) { if (groups[i] == fileptr->st_gid) return YES; } } HTTRACE(PROT_TRACE, "\tFile is not editable.\n"); return NO; /* If no excuse, can't do */ #else /* ** We don't know and can't find out. Can we be sure that when opening ** a file in mode "a" that the file is not modified? */ return NO; #endif /* GETGROUPS_T */ }
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) */ }
/* HTFile_readDir ** -------------- ** Reads the directory "path" ** Returns: ** HT_ERROR Error ** HT_FORBIDDEN Directory reading not allowed ** HT_LOADED Successfully read the directory */ PRIVATE int HTFile_readDir (HTRequest * request, file_info *file) { #ifdef HAVE_READDIR DIR * dp; struct stat file_info; HTParentAnchor * anchor = HTRequest_anchor(request); char *url = HTAnchor_physical(anchor); char fullname[HT_MAX_PATH+1]; char *name; HTTRACE(PROT_TRACE, "Reading..... directory\n"); if (dir_access == HT_DIR_FORBID) { HTRequest_addError(request, ERR_FATAL, NO, HTERR_FORBIDDEN, NULL, 0, "HTFile_readDir"); return HT_FORBIDDEN; } /* Initialize path name for stat() */ if (*(name = (url+strlen(url)-1)) != '/') { char *newurl = NULL; StrAllocCopy(newurl, url); StrAllocCat(newurl, "/"); HT_FREE(file->local); file->local = HTWWWToLocal(newurl, "", HTRequest_userProfile(request)); HT_FREE(newurl); } strcpy(fullname, file->local); name = fullname+strlen(fullname); /* Point to end of fullname */ /* Check if access is enabled */ if (dir_access == HT_DIR_SELECTIVE) { strcpy(name, DEFAULT_DIR_FILE); if (HT_STAT(fullname, &file_info)) { HTTRACE(PROT_TRACE, "Read dir.... `%s\' not found\n" _ DEFAULT_DIR_FILE); HTRequest_addError(request, ERR_FATAL, NO, HTERR_FORBIDDEN, NULL, 0, "HTFile_readDir"); return HT_FORBIDDEN; } } if ((dp = opendir(file->local))) { struct dirent * dirbuf; HTDir *dir = HTDir_new(request, dir_show, dir_key); char datestr[20]; char sizestr[10]; HTFileMode mode; #ifdef HT_REENTRANT struct dirent result; /* For readdir_r */ #endif #ifdef HAVE_READDIR_R_2 while ((dirbuf = (struct dirent *) readdir_r(dp, &result))) #elif defined(HAVE_READDIR_R_3) while (readdir_r(dp, &result, &dirbuf) == 0) #else while ((dirbuf = readdir(dp))) #endif /* HAVE_READDIR_R_2 */ { /* Current and parent directories are never shown in list */ #ifdef HAVE_DIRENT_INO if (!dirbuf->d_ino || !strcmp(dirbuf->d_name, ".") || !strcmp(dirbuf->d_name, "..")) #else if (!strcmp(dirbuf->d_name, ".") || !strcmp(dirbuf->d_name, "..")) #endif continue; /* Make a lstat on the file */ strcpy(name, dirbuf->d_name); if (HT_LSTAT(fullname, &file_info)) { HTTRACE(PROT_TRACE, "Read dir.... lstat failed: %s\n" _ fullname); continue; } /* Convert stat info to fit our setup */ if (((mode_t) file_info.st_mode & S_IFMT) == S_IFDIR) { #ifdef VMS char *dot = strstr(name, ".DIR"); /* strip .DIR part... */ if (dot) *dot = '\0'; #endif /* VMS */ mode = HT_IS_DIR; if (dir_show & HT_DS_SIZE) strcpy(sizestr, "-"); } else { mode = HT_IS_FILE; if (dir_show & HT_DS_SIZE) HTNumToStr(file_info.st_size, sizestr, 10); } if (dir_show & HT_DS_DATE) HTDateDirStr(&file_info.st_mtime, datestr, 20); /* Add to the list */ if (HTDir_addElement(dir, name, datestr, sizestr, mode) != YES) break; } closedir(dp); HTDir_free(dir); return HT_LOADED; } else { HTRequest_addSystemError(request, ERR_FATAL, errno, NO, "opendir"); return HT_ERROR; } #else return HT_ERROR; /* needed for WWW_MSWINDOWS */ #endif /* HAVE_READDIR */ }