/* FILE WRITER HTFWrite.h ** =========== ** ** This version of the stream object just writes to a C file. ** The file is assumed open and left open. ** ** Bugs: ** strings written must be less than buffer size. */ #define CACHE_LIMIT 100 /* files */ #include "HTFWriter.h" #include "HTFormat.h" #include "HTAlert.h" #include "HTFile.h" #include "HTList.h" #ifdef Mips #define L_tmpnam 64 #endif /* Stream Object ** ------------ */ struct _HTStream { CONST HTStreamClass * isa; FILE * fp; BOOL leave_open; /* Close file? HFN 08/02-94 */ char * end_command; BOOL remove_on_close; BOOL announce; char * filename; HTRequest * request; /* saved for callback */ BOOL (*callback) PARAMS((struct _HTRequest * req, void * filename)); HTCacheItem * cache; }; /* ------------------------------------------------------------------------- */ /* BASIC STREAM CLASSES */ /* ------------------------------------------------------------------------- */ /* ** ** B L A C K H O L E C L A S S ** ** There is only one black hole instance shared by anyone ** who wants a black hole. These black holes don't radiate, ** they just absorb data. */ PRIVATE void HTBlackHole_put_character ARGS2(HTStream *, me, char, c) {} PRIVATE void HTBlackHole_put_string ARGS2(HTStream *, me, CONST char*, s) {} PRIVATE void HTBlackHole_write ARGS3(HTStream *, me, CONST char*, s, int, l) {} PRIVATE void HTBlackHole_free ARGS1(HTStream *, me) {} PRIVATE void HTBlackHole_abort ARGS2(HTStream *, me, HTError, e) {} /* Black Hole stream ** ----------------- */ PRIVATE CONST HTStreamClass HTBlackHoleClass = { "BlackHole", HTBlackHole_free, HTBlackHole_abort, HTBlackHole_put_character, HTBlackHole_put_string, HTBlackHole_write }; PRIVATE HTStream HTBlackHoleInstance = { &HTBlackHoleClass, NULL, NO, /* HENRIK 08/02-94 */ NULL, NO, NO, NULL, NULL, NULL, NULL, }; /* Black hole creation */ PUBLIC HTStream * HTBlackHole NOARGS { return &HTBlackHoleInstance; } /* HTThroughLine ** ------------- ** ** This function is a dummy function that returns the same output stream ** as given as a parameter. Henrik 01/03-94 */ PUBLIC HTStream* HTThroughLine ARGS5( HTRequest *, request, void *, param, HTFormat, input_format, HTFormat, output_format, HTStream *, output_stream) /* Only one used */ { return output_stream; } /*_________________________________________________________________________ ** ** F I L E A C T I O N R O U T I N E S ** Bug: ** All errors are ignored. */ /* ------------------------------------------------------------------------- This function tries really hard to find a non-existent filename relative to the path given. path: path of directory in which to put temp file url: used for a similar name or generating a hash within 'limit' suffix: if != 0, this suffix is used, else no suffix limit: if 0 => use last part of url if >0 generate hash. Max value of limit is max(unsigned int). Returns NULL if no filename could be found. Henrik 10/03-94 ------------------------------------------------------------------------- */ PUBLIC char *HTFWriter_filename ARGS4(char *, path, char *, url, CONST char *, suffix, unsigned int, limit) { #define HASHTRY 5 /* Keep this less than 10, please */ static int primes[HASHTRY] = {31, 37, 41, 43, 47}; /* Some primes */ char *urlfile = strrchr(url, '/'); char *filename = NULL; StrAllocCopy(filename, path); if (filename && urlfile++) { /* We don't want two '/'s */ int digits = 1; unsigned hash; char *hashstr = NULL; char *replace = 0; char *ptr; /* Dummy used several places */ char format[10]; if (*(filename+strlen(filename)-1) != '/') /* But one is OK, though */ StrAllocCat(filename, "/"); if (limit) { /* Use hash method */ unsigned int residue = limit; while (residue /= 10) /* Find number of digits */ digits++; if ((hashstr = malloc(digits+1)) == NULL) outofmem(__FILE__, "HTFWriter_filename"); for(ptr=urlfile, hash=0; *ptr; ptr++) /* Calculate hash */ hash = *ptr + *primes * hash; hash %= limit; sprintf(format, "%%0%du", digits); /* Convert hash to string */ sprintf(hashstr, format, hash); *(hashstr+digits) = '\0'; StrAllocCat(filename, hashstr); #ifndef NO_GETPID /* RISK: Race conditions may occur if this is not added to the filename */ { char pidstr[10]; sprintf(pidstr, "-%d", (int) getpid()); StrAllocCat(filename, pidstr); } #endif if (suffix) { /* Add suffix */ if (*suffix != '.') StrAllocCat(filename, "."); StrAllocCat(filename, suffix); } replace = strrchr(filename, '/')+1; /* Remember place */ } else { /* Use url name */ char *urlptr = 0; /* Strip off any suffix */ StrAllocCopy(urlptr, urlfile); ptr = strrchr(urlptr, '.'); if (ptr) *ptr = '\0'; StrAllocCat(filename, urlptr); free(urlptr); if (suffix) { /* Add new suffix if any */ if (*suffix != '.') StrAllocCat(filename, "."); StrAllocCat(filename, suffix); } } { /* Try if file exists */ int cnt; FILE *fp; for (cnt=1; cnt= HASHTRY) { /* If no file name could be found */ free(filename); filename = NULL; } } FREE(hashstr); } return filename; } /* Character handling ** ------------------ */ PRIVATE void HTFWriter_put_character ARGS2(HTStream *, me, char, c) { fputc(c, me->fp); } /* String handling ** --------------- ** ** Strings must be smaller than this buffer size. */ PRIVATE void HTFWriter_put_string ARGS2(HTStream *, me, CONST char*, s) { if (*s) /* For vms :-( 10/04-94 */ fputs(s, me->fp); } /* Buffer write. Buffers can (and should!) be big. ** ------------ */ PRIVATE void HTFWriter_write ARGS3(HTStream *, me, CONST char*, s, int, l) { fwrite(s, 1, l, me->fp); } /* Free an HTML object ** ------------------- ** ** Note that the SGML parsing context is freed, but the created ** object is not, ** as it takes on an existence of its own unless explicitly freed. */ PRIVATE void HTFWriter_free ARGS1(HTStream *, me) { if (me->cache) { time_t finish_time; time(&finish_time); me->cache->load_delay = finish_time - me->cache->load_time; /* Actually, ought to use draft ANSI-C difftime() */ /* But that I bet is more portable in real life (@@?) */ } if (me->leave_open != YES) fclose(me->fp); if (me->end_command) { /* Temp file */ HTProgress(me->end_command); /* Tell user what's happening */ system(me->end_command); /* @@ Beware of security hole */ free (me->end_command); if (me->remove_on_close) { unlink(me->filename); } } if (me->callback) { (*me->callback)(me->request, me->filename); } if (me->filename) free(me->filename); free(me); } /* End writing */ PRIVATE void HTFWriter_abort ARGS2(HTStream *, me, HTError, e) { if (me->leave_open != YES) fclose(me->fp); if (me->end_command) { /* Temp file */ if (TRACE) fprintf(stderr, "HTFWriter: Aborting: file %s not executed.\n", me->filename ? me->filename : "???" ); free (me->end_command); if (me->remove_on_close) { unlink(me->filename); } } if (me->filename) free(me->filename); free(me); } /* Structured Object Class ** ----------------------- */ PRIVATE CONST HTStreamClass HTFWriter = /* As opposed to print etc */ { "FileWriter", HTFWriter_free, HTFWriter_abort, HTFWriter_put_character, HTFWriter_put_string, HTFWriter_write }; /* Subclass-specific Methods ** ------------------------- */ PUBLIC HTStream* HTFWriter_new ARGS2(FILE *, fp, BOOL, leave_open) { HTStream* me; if (!fp) return NULL; me = (HTStream*)calloc(sizeof(*me),1); if (me == NULL) outofmem(__FILE__, "HTFWriter_new"); me->isa = &HTFWriter; me->fp = fp; me->leave_open = leave_open; /* HENRIK 08/02-94 */ me->end_command = NULL; me->remove_on_close = NO; me->announce = NO; me->callback = NULL; return me; } /* Make system command from template ** --------------------------------- ** ** See mailcap spec for description of template. */ /* @@ to be written. sprintfs will do for now. */ /* Take action using a system command ** ---------------------------------- ** ** Creates temporary file, writes to it, executes system command ** on end-document. The suffix of the temp file can be given ** in case the application is fussy, or so that a generic opener can ** be used. */ PUBLIC HTStream* HTSaveAndExecute ARGS5( HTRequest *, request, void *, param, HTFormat, input_format, HTFormat, output_format, HTStream *, output_stream) #ifdef unix #define REMOVE_FILE unlink #endif #ifdef VMS #define REMOVE_FILE unlink /* ok? */ #endif #ifdef REMOVE_FILE { char *fnam; HTStream* me; if (HTSecure) { HTAlert("Can't save data to file -- please run WWW locally"); return HTBlackHole(); } if (!HTSaveLocallyDir) { if (TRACE) fprintf(stderr, "Save and execute turned off"); return HTBlackHole(); } me = (HTStream*)calloc(sizeof(*me),1); me = (HTStream*)calloc(sizeof(*me), 1); if (me == NULL) outofmem(__FILE__, "Save and execute"); me->isa = &HTFWriter; /* Let's find a hash name for this file */ if ((fnam = HTFWriter_filename(HTSaveLocallyDir, HTAnchor_physical(request->anchor), HTFileSuffix(input_format), 1000)) == NULL) { HTAlert("Can't find a suitable file name"); return NULL; } me->request = request; /* won't be freed */ me->fp = fopen (fnam, "w"); if (!me->fp) { HTAlert("Can't open temporary file!"); free(fnam); free(me); return NULL; } StrAllocCopy(me->filename, fnam); /* Make command to process file */ me->end_command = (char *)malloc ( (strlen (param) + 10+ 3*strlen(fnam)) * sizeof (char)); if (me == NULL) outofmem(__FILE__, "SaveAndExecute"); sprintf (me->end_command, param, fnam, fnam, fnam); me->remove_on_close = NO; me->announce = NO; free (fnam); return me; } #else /* can't do remove */ { return NULL; } #endif /* Save Locally ** ------------ ** ** Bugs: ** GUI Apps should open local Save panel here really. ** */ PUBLIC HTStream* HTSaveLocally ARGS5( HTRequest *, request, void *, param, HTFormat, input_format, HTFormat, output_format, HTStream *, output_stream) /* Not used */ { char *fnam = NULL; char *answer = NULL; HTStream* me; if (HTClientHost) { HTAlert("Can't save data to file -- please run WWW locally"); return HTBlackHole(); } if (!HTSaveLocallyDir) { if (TRACE) fprintf(stderr, "Save locally turned off"); return HTBlackHole(); } me = (HTStream*)calloc(sizeof(*me),1); if (me == NULL) outofmem(__FILE__, "SaveLocally"); me->isa = &HTFWriter; me->announce = YES; /* Let's find a 'human' file name for this file */ fnam = HTFWriter_filename(HTSaveLocallyDir, HTAnchor_physical(request->anchor), HTFileSuffix(input_format), 0); /* Save Panel */ answer = HTPrompt("Give name of file to save in", fnam ? fnam : ""); FREE(fnam); me->fp = fopen (answer, "w"); if (!me->fp) { HTAlert("Can't open local file to write into."); FREE(answer); free(me); return NULL; } me->callback = NULL; me->request = request; /* won't be freed */ me->filename = answer; /* Will be freed */ return me; } /* Cache handling ** -------------- */ PUBLIC HTList * HTCache = NULL; PUBLIC int HTCacheLimit = CACHE_LIMIT; PRIVATE void HTCache_remove ARGS2(HTList *, list, HTCacheItem *, item) { if (TRACE) fprintf(stderr, "Cache: Removing %s\n", item->filename); HTList_removeObject(list, item); HTList_removeObject(item->anchor->cacheItems, item); unlink(item->filename); free(item->filename); free(item); } /* This can be called for the main list or an anchor's list */ PUBLIC void HTCacheClear ARGS1(HTList *, list) { HTCacheItem * item; while ((item=HTList_objectAt(list, 0)) != NULL) { HTCache_remove(list, item); } } /* ------------------------------------------------------------------------- This function removes ALL cache files known to this session. The anchors ARE updated, but normally this function is for exiting purposes. Henrik 09/03-94 ------------------------------------------------------------------------- */ PUBLIC void HTCacheDeleteAll NOARGS { HTCacheItem * item; while ((item=HTList_objectAt(HTCache, 0)) != NULL) { HTCache_remove(HTCache, item); } } /* Remove a file from the cache to prevent too many files from being cached */ PRIVATE void limit_cache ARGS1(HTList * , list) { HTList * cur = list; HTCacheItem * item; time_t best_delay = 0; /* time_t in principle can be any arith type */ HTCacheItem* best_item = NULL; if (HTList_count(list) < HTCacheLimit) return; /* Limit not reached */ while (NULL != (item = (HTCacheItem*)HTList_nextObject(cur))) { if (best_delay == 0 || item->load_delay < best_delay) { best_delay = item->load_delay; best_item = item; } } if (best_item) HTCache_remove(list, best_item); } /* Cache Writer ** ------------------ ** */ PUBLIC HTStream* HTCacheWriter ARGS5( HTRequest *, request, void *, param, HTFormat, input_format, HTFormat, output_format, HTStream *, output_stream) { char *fnam; HTStream* me; if (HTClientHost) { if (TRACE) fprintf(stderr, "Only caching if WWW is run locally.\n"); return HTBlackHole(); } me = (HTStream*)calloc(sizeof(*me),1); if (me == NULL) outofmem(__FILE__, "CacheWriter"); me->isa = &HTFWriter; me->end_command = NULL; me->remove_on_close = NO; /* If needed, put into end_command */ me->announce = NO; /* Save the file under a suitably suffixed name */ fnam = HTFWriter_filename(HTCacheDir, HTAnchor_physical(request->anchor), HTFileSuffix(input_format), 0); me->filename = NULL; limit_cache(HTCache); /* Limit number (not size) of files */ me->fp = fopen (fnam, "w"); if (!me->fp) { HTAlert("Can't open local file to write into for callback."); free(fnam); free(me); return NULL; } /* Set up a cache record */ if (TRACE) fprintf(stderr, "Cache: Creating file %s\n", fnam); me->cache = (HTCacheItem*)calloc(sizeof(*me->cache),1); if (!me->cache)outofmem(__FILE__, "cache"); time(&me->cache->load_time); StrAllocCopy(me->cache->filename, fnam); me->cache->anchor = request->anchor; if (!request->anchor->cacheItems) request->anchor->cacheItems = HTList_new(); HTList_addObject(request->anchor->cacheItems, me->cache); me->cache->format = input_format; if (!HTCache) HTCache = HTList_new(); HTList_addObject(HTCache, me->cache); me->callback = request->callback; me->request = request; /* won't be freed */ me->filename = fnam; /* will be freed */ return me; } /* Save and Call Back ** ------------------ ** ** ** The special case is a kludge. Better is everything uses streams ** and nothing uses files. Then this routine will go too. :-)) */ PUBLIC HTStream* HTSaveAndCallBack ARGS5( HTRequest *, request, void *, param, HTFormat, input_format, HTFormat, output_format, HTStream *, output_stream) { HTStream * me; if (request->using_cache) { /* Special case! file wanted && cache hit */ (*request->callback)(request, ((HTCacheItem*)request->using_cache)->filename); return &HTBlackHoleInstance; /* @@@@@@@@@@@@@@ */ } else { me = HTCacheWriter(request, param, input_format, output_format, output_stream); if (me) { me->callback = request->callback; } } return me; }