/*****************************************************************************/ /* pcache.c This program can act as a command-line utility or CGI(plus)-compliant script. It is used to access the information about or contents of a WASD proxy cache file. With both CLI utility and CGI script the items reported on may be filtered according to the contents of the URL and/or the response header. The filter strings may contain the wildcard characters '*' (matching zero or more characters) and '%' (matching any single character). Any filter strings must be enclosed by wildcards unless the specified character lies on either the upper of lower boundary of the string (i.e. "*netscape*" matches the string "netscape" anywhere in the string, but "netscape*" will only match it at the start of the string). Filtering is also available based on maximum or minimum hits on the file/URL. The default behaviour is to generate a form for requesting other functionality. One major use is the profiling of the cache files listing the four main characteristics; age ("Last-Modified:"), when loaded, when last accessed, and by the number of hits, by hours and days, with the corresponding file count and blocks used and allocated. The next is to list cache file entries and then be able to select individual files for closer analysis. Two cache directory organizations are currently available. The first provides for a single level structure with a possible 256 directories at the top level and files organized immediate below these. This is the original structure and works well where files do not exceed 256 per directory (assuming a relatively uniform distribution of file names by the MD5 algorithm) for a total of approximately 65,000 files. For versions of VMS prior to V7.2 exceeding 256 files per directory incurs a significant performance penalty for some directory operations. A file in this structure cache might look like: WASD_CACHE_ROOT:[03]4A628C52356BCA71B507AA8E47ADF1.HTC The second organization involves two levels of directory, each with a maximum of 64 directories. Again assuming a relatively uniform distribution of file names by the MD5 algorithm, this allows for approximately 1,000,000 files before exceeding the 256 file per directory point. A file in this structure cache might look like: WASD_CACHE_ROOT:[00.34]A628C52356BCA71B507AA8E47ADF1.HTC FILE HEADER DATE/TIME FIELD USAGE --------------------------------- (for v9.0.0ff cache files, different to the original version - v6.0.0ff) BDT - backup contains the loaded date/time CDT - creation is used to hold a responses last-modified date/time RDT - revision contains the date/time the file was last accessed EDT - for cached responses, set to any supplied "Expires:" date/time for cached "negative" responses, the load time plus "negative" period CLI UTILITY ----------- Needs a privileged account (SYSPRV or SETPRV). Assign the foreign verb using: $ PCACHE == "$HT_EXE:PCACHE" Output may be redirected to any file using the /OUTPUT= qualifier. Just specifying $ PCACHE generates a profile of the cache files. As a CLI utility PCACHE may be used to list the contents of the entire cache dirctory tree. As this contains more than 80 columns it would need to be printed in portrait format. Specifying $ PCACHE /INDEX will produce such a listing of the complete tree. To list just a portion of the tree provide a file specification containing suitable wildcards as a parameter. For example: $ PCACHE WASD_CACHE_ROOT:[00]*.HTC or $ PCACHE WASD_CACHE_ROOT:[00.00]*.HTC When a single, fully specified file is provided as a parameter similar information is presented in a more readable format, as well as the response header stored in the cache file. For example: $ PCACHE WASD_CACHE_ROOT:[03]4A628C52356BCA71B507AA8E47ADF1.HTC or $ PCACHE WASD_CACHE_ROOT:[00.34]A628C52356BCA71B507AA8E47ADF1.HTC To provide this format report for all (or a portion) of the tree add the /FULL qualifier to a command line such as: $ PCACHE /INDEX /FULL To obtain the response header use: $ PCACHE WASD_CACHE_ROOT:[03]4A628C52356BCA71B507AA8E47ADF1.HTC /HEADER or $ PCACHE WASD_CACHE_ROOT:[00.34]A628C52356BCA71B507AA8E47ADF1.HTC /HEADER The /HEADER qualifier also takes an optional redirect file specification (although this could also be specified using /OUTPUT): $ PCACHE WASD_CACHE_ROOT:[03]4A~~~F1.HTC /HEADER=EXAMPLE.TXT or $ PCACHE WASD_CACHE_ROOT:[00.34]A~~~F1.HTC /HEADER=EXAMPLE.TXT This can also be used with a listing of all (or a portion) of the cache tree: $ PCACHE /INDEX /HEADER To extract the content (i.e. the response body) of a cache file into a standalone file use: $ PCACHE WASD_CACHE_ROOT:[03]4A628C52356BCA71B507AA8E47ADF1.HTC /BODY or $ PCACHE WASD_CACHE_ROOT:[00.34]A628C52356BCA71B507AA8E47ADF1.HTC /BODY For all but TEXT responses an output file would probably need to be specified using the /OUTPUT qualifier, or an file specification optional on the /BODY qualifier, as in the following example: $ PCACHE WASD_CACHE_ROOT:[03]4A~~~F1.HTC /BODY=EXAMPLE.GIF or $ PCACHE WASD_CACHE_ROOT:[00.34]A~~~F1.HTC /BODY=EXAMPLE.GIF To filter use the /FILTER= qualifier. This takes at most one of URL: or HEADER: (or U:, H:) specifying a wildcard string for either the URL or response header. To filter on both, two /FILTERs must be used. These examples show all three possibilities. $ PCACHE /INDEX FILTER=URL:*.html $ PCACHE /INDEX /FILTER=HEADER:*netscape* $ PCACHE /INDEX /FILTER=U:*.html /FILTER=H:*netscape* Other filters are available, based on minimum and/or maximum hits (accesses to the particular file/URL), hours since last accessed, hours since last loaded, adn finally hours since last-modified (age of document). $ PCACHE /INDEX /MINHITS=100 $ PCACHE /INDEX /MAXHITS=10 $ PCACHE /INDEX /MINHITS=10 /MAXHITS=100 $ PCACHE /INDEX /MINACCESS=24 /MAXACCESS=168 $ PCACHE /INDEX /MAXLOAD=48 $ PCACHE /INDEX /MINLASTMOD=672 CGI(PLUS) SCRIPT ---------------- To make this program available as a CGI script the following would need to be added to the HTTPD$CONFIG file, in the [AddType] grouping: .HTC application/x-script /cgi-bin/pcache WASD proxy cache file To use it as a CGUplus script (RECOMMENDED): .HTC application/x-script /cgiplus-bin/pcache WASD proxy cache file With this configured aaccessing a .HTC file from a directory listing of the cache directory tree returns the essential information from that file (dates, times, URL, response header) plus a number of buttons allowing the content (text, image, etc.) to be viewed, the file to be VMS DUMPed, ANA/RMSed or DELETEd. The cache directory will also need to be mapped in HTTPD$MAP: pass /wasd_cache_root/* The script could be mapped for abbreviated access: script+ /pcache* /cgi-bin/pcache* It is recommended to place the cache directory under some authorization control to prevent casual browsing and access of the contents. Something local, along the lines of: [VMS] /wasd_cache_root/* ~webadmin,131.185.250.*,r+w ; By default a profile of the cache is generated. http://host.name.domain/pcache To generate a listing of the cache contents use: http://host.name.domain/pcache?do=index Filtering on URL or response header may be accomplished using either or both query form field strings, as in the following examples. http://host.name.domain/pcache?url=*.gif http://host.name.domain/pcache?header=*netscape* http://host.name.domain/pcache?url=*.gif&header=*netscape* Filtering on the number of hits, last accessed, last loaded and last-modified are made in much the same way as for the CLI. http://host.name.domain/pcache?do=index&minhits=100 http://host.name.domain/pcache?do=index&maxhits=10 http://host.name.domain/pcache?do=index&minhits=10&maxhits=50 http://host.name.domain/pcache?do=index&minacc=24&maxacc=168 http://host.name.domain/pcache?do=index&maxload=48 http://host.name.domain/pcache?do=index&minage=672 Needs to be installed with SYSPRV privilege. $ INSTALL REPLACE CGI-BIN:[000000]PCACHE /PRIVILEGE=(SYSPRV) CGI VARIABLES ------------- WWW_FORM_DO "anarms", "delete", "dump", "index", "form", "response", "profile" WWW_FORM_HEADER wildcardable string allowing filtering on header contents WWW_FORM_MAXACC only files last accessed within the specified hours WWW_FORM_MAXHITS only files with no more than this many hits WWW_FORM_MAXLOAD only files last loaded within the specified hours WWW_FORM_MAXLAST only files last-modified within the specified hours WWW_FORM_MINACC only files last accessed more than the specified hours WWW_FORM_MINHITS only files with at least this many hits WWW_FORM_MINLAST only files last-modified more than the specified hours WWW_FORM_MINLOAD only files last loaded more than the specified hours WWW_FORM_URL wildcardable string allowing filtering on URL contents QUALIFIERS ---------- /AUTHORIZED requires all script access to be authorized /BODY[=file] (CLI) output body of the response (optional output redirect) /CHARSET= (CGI) "Content-Type: text/html; charset=..." /DBUG turns on all "if (Debug)" statements /DELETE (CLI) deletes matched files (CAUTION! no confirmation) /FULL (CLI) directory listing, with same information as single file /FILTER[=string] (CLI) filter on the supplied wildcardable string e.g. /FILTER=URL:*.HTML e.g. /FILTER=HEADER:*WASD* /HEADER[=file] (CLI) output header of the response (optional output redirect) /INDEX (CLI) generate a listing of all files in the cache /MAXACCESS= (CLI) only files last accessed within the specified hours /MAXLASTMOD= (CLI) only files last-modified within the specific hours /MAXAGE= (CLI) synonym for /maxlastmod= /MAXEXPIRES= (CLI) only files expiring within the specific hours /MAXHITS= (CLI) only files with no more than this many hits /MAXLOAD= (CLI) only files loaded within the specified hours /MINACCESS= (CLI) only files last accessed more than the specified hours /MINLASTMOD= (CLI) only files last-modified more than the specified hours /MAXAGE= (CLI) synonym for /minlastmod= /MINEXPIRES= (CLI) only files expiring after more than the specified hours /MINHITS= (CLI) only files with at least this many hits /MINLOAD= (CLI) only files last loaded more than the specified hours /OUTPUT=file (CLI) redirect output to specified file /[NO]PROFILE (CLI) include a profile of cache space usage (default) ENVIRONMENT VARIABLES (logicals or symbols) --------------------- PCACHE$DBUG turns on all "if (Debug)" statements PCACHE$PARAM equivalent to (overrides) the command line parameters/qualifiers (define as a system-wide logical) PCACHE$AUTHORIZED requires *all* script access to be authorized BUILD DETAILS ------------- See BUILD_PCACHE.COM procedure. COPYRIGHT --------- Copyright (C) 1999-2009 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version. http://www.gnu.org/licenses/gpl.txt VERSION HISTORY (update SOFTWAREVN as well!) --------------- 14-AUG-2009 MGD v1.6.2, WASD v10 moves from HT_CACHE_ROOT to WASD_CACHE_ROOT 12-MAY-2005 MGD v1.6.1, WASD v9.1 uses bit 1 of the RAT field to indicate the file contains GZIP compressed content 24-SEP-2004 MGD v1.6.0, changes in line with WASD v9.0 use of date/times, requires linking with CGILIB v1.7 or later 23-DEC-2003 MGD v1.5.1, minor conditional mods to support IA64 05-SEP-2003 MGD v1.5.0, enable SYSPRV to access cache files, check for account SYSPRV before CLI activating 03-APR-2002 MGD v1.4.1, bugfix; FaoPrint() use fwrite() 28-OCT-2000 MGD v1.4.0, use CGILIB object module, modifications for RELAXED_ANSI compilation 28-AUG-2000 MGD v1.3.0, flat256 and 64x64 cache directory organizations (this actually involved no code changes in PCACHE.C) 18-AUG-2000 MGD v1.2.0, add /AUTHORIZED and PCACHE$AUTHORIZED, bugfix; authorization check against "delete" 12-APR-2000 MGD v1.1.1, minor changes for CGILIB 1.4 29-JAN-2000 MGD no changes required for ODS-5 compliance ... it's not (see note in PROXYCACHE.C) 07-AUG-1999 MGD v1.1.0, use more of the CGILIB functionality 10-JAN-1999 MGD v1.0.0, initial development */ /*****************************************************************************/ #define SOFTWAREVN "1.6.2" #define SOFTWARENM "PCACHE" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif /* standard C header files */ #include #include #include #include #include #include #include #include /* VMS-related header files */ #include #include #include #include #include #include #include #include #include #include #include #include /* application header file */ #include #define boolean int #define true 1 #define false 0 #ifndef __VAX # pragma nomember_alignment #endif #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) (!((x) & STS$M_SUCCESS)) #define FI_LI __FILE__, __LINE__ #define PROXY_CACHE_FILE_VERSION_9 0x090000 #define PROXY_CACHE_FILE_VERSION_6 0x060000 /* value returned when a binary time is zero */ #define ZERO_TIME 999999999 #define TIME_HOURS(hrs) hrs == ZERO_TIME ? 0 : hrs #define ZERO_TIME_DAYS 999999999/24 #define TIME_DAYS(hrs) hrs == ZERO_TIME ? 0 : hrs/24 #define RAT_GZIP 0x01 /* (FAT$M_FORTRANCC, FAB$M_FTN) */ /* these structures are from PROXYSTRUCT.H (needless-to-say) */ struct AnIOsb { unsigned short Status; unsigned short Count; unsigned long Unused; }; struct ProxyCacheDescrStruct { int CacheVersion, UrlLength, HeaderLength; }; struct ProxyCacheRecordAttributes { unsigned char RecordType; unsigned char RecordAttrib; unsigned short RecordSize; unsigned short AllocatedVbnHi; unsigned short AllocatedVbnLo; unsigned short EndOfFileVbnHi; unsigned short EndOfFileVbnLo; unsigned short FirstFreeByte; unsigned char OfNoInterest1 [18]; }; struct ProxyCacheAscDates { unsigned short RevisionCount; unsigned char OfNoInterest [33]; }; struct ProxyCacheFileAcpStruct { unsigned short Channel, AcpFileNameLength; unsigned long BdtBinTime [2], CdtBinTime [2], EdtBinTime [2], RdtBinTime [2]; char AcpFileName [128]; struct ProxyCacheAscDates AscDates; struct ProxyCacheRecordAttributes RecAttr; struct fibdef FileFib; struct atrdef FileAtr [7]; struct dsc$descriptor FileFibAcpDsc, FileNameAcpDsc; struct AnIOsb AcpIOsb; }; char Utility [] = "PCACHE"; boolean CgiDoAnaRms, CgiDoDelete, CgiDoDump, CgiDoResponse, CliDoBody, CliDoDelete, CliDoFull, CliDoHeader, CliMustBeAuthorized, Debug, DoFile, DoIndex, DoNoProfile, DoProfile, IsCgiPlus, IsCgiScript, IsCliUtility, OpenAndReadFile, ThereHasBeenOutput; int FileCount, MaxAccessHours, MaxExpiresHours, MaxLastModHours, MaxHits, MaxLoadHours, MinAccessHours, MinExpiresHours, MinLastModHours, MinHits, MinLoadHours, VersionSixCount; unsigned long TotalStatTimerContext; char *CgiFormDoPtr, *CgiPathInfoPtr, *CgiPathTranslatedPtr, *CgiPlusEofPtr, *CgiRemoteUserPtr, *CgiRequestMethodPtr, *CgiScriptNamePtr, *CgiServerSoftwarePtr, *CgiEnvironmentPtr, *CharsetPtr, *CliCharsetPtr, *CliFileSpecPtr, *CliOutputPtr, *HeaderFilterPtr, *UrlFilterPtr; char FilterComment [512], SoftwareID [48]; struct ProfileStruct { int BlocksAllocated, BlocksUsed, Count; }; unsigned long SysPrvMask [2] = { PRV$M_SYSPRV, 0 }; #define PROFILE_DAYS 28 #define PROFILE_HOURS_MAX (PROFILE_DAYS * 24) char *HitRange [] = { " 0", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", " 9", " 10-19", " 20-29", " 30-39", " 40-49", " 50-59", " 60-69", " 70-79", " 80-89", " 90-99", "100-199", "200-299", "300-399", "400-499", "500-599", "600-699", "700-799", "800-899", "900-999", " >1000" }; struct ProfileStruct ProfileAccess [PROFILE_HOURS_MAX+2], ProfileExpires [PROFILE_HOURS_MAX+2], ProfileLoad [PROFILE_HOURS_MAX+2], ProfileModified [PROFILE_HOURS_MAX+2], ProfileRevisionCount [29]; /* required function prototypes */ void NeedsPrivilegedAccount (); char* SearchTextString (char*, char*, boolean, int*); char* SysGetMsg (int); char* VmsToPath (char*, char*); char* v10orPrev10 (char*, int); /*****************************************************************************/ /* */ main () { /*********/ /* begin */ /*********/ sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion()); if (getenv ("PCACHE$DBUG")) { Debug = true; if (getenv ("HTTP$INPUT")) fprintf (stdout, "Content-Type: text/plain\r\n\r\n"); } GetParameters (); if (getenv ("HTTP$INPUT")) CgiScript (); else CliUtility(); exit (SS$_NORMAL); } /*****************************************************************************/ /* Being used as a command-line utility. */ CliUtility () { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "CliUtility()\n"); NeedsPrivilegedAccount (); IsCliUtility = true; if (CliOutputPtr) { if (Debug) fprintf (stdout, "CliOutputPtr |%s|\n", CliOutputPtr); if (!(stdout = freopen (CliOutputPtr, "w", stdout))) exit (vaxc$errno); } if (!CliFileSpecPtr) CliFileSpecPtr = ""; sys$setprv (1, &SysPrvMask, 0, 0); SearchCache (CliFileSpecPtr); sys$setprv (0, &SysPrvMask, 0, 0); } /*****************************************************************************/ /* Because it can be installed with privileges (for CLI usage) ... */ void NeedsPrivilegedAccount () { static unsigned long PrivAcctMask [2] = { PRV$M_SETPRV | PRV$M_SYSPRV, 0 }; static long Pid = -1; static unsigned long JpiAuthPriv [2]; static struct { unsigned short buf_len; unsigned short item; void *buf_addr; void *ret_len; } JpiItems [] = { { sizeof(JpiAuthPriv), JPI$_AUTHPRIV, &JpiAuthPriv, 0 }, {0,0,0,0} }; int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "NeedsPrivilegedAccount()\n"); status = sys$getjpiw (0, &Pid, 0, &JpiItems, 0, 0, 0); if (VMSnok (status)) exit (status); if (!(JpiAuthPriv[0] & PrivAcctMask[0])) exit (SS$_NOSYSPRV); } /*****************************************************************************/ /* Being used as a CGI(plus) script. */ CgiScript () { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "CgiScript()\n"); IsCgiScript = true; CgiLibEnvironmentSetDebug (Debug); CgiLibEnvironmentInit (0, NULL, true); CgiLibResponseSetCharset (CliCharsetPtr); CgiLibResponseSetSoftwareID (SoftwareID); CgiLibResponseSetErrorMessage ("Reported by pCache"); IsCgiPlus = CgiLibEnvironmentIsCgiPlus (); if (IsCgiPlus) { for (;;) { /* block waiting for the next request */ CgiLibVar (""); ProcessRequest (); CgiLibCgiPlusEOF (); } } else ProcessRequest (); } /*****************************************************************************/ /* Being used as a CGI(plus) script. Get the required CGI variables and call search cache. */ ProcessRequest () { char *CgiPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessRequest()\n"); ThereHasBeenOutput = false; CgiRemoteUserPtr = CgiLibVar ("WWW_REMOTE_USER"); if (!CgiRemoteUserPtr[0] && (CliMustBeAuthorized || getenv("PCACHE$AUTHORIZED"))) { CgiLibResponseError (FI_LI, 0, "Authorization mandatory!"); return; } CgiLibResponseSetStreamMode (1); CgiEnvironmentPtr = CgiLibEnvironmentName (); CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE"); CgiRequestMethodPtr = CgiLibVar ("WWW_REQUEST_METHOD"); if (strcmp (CgiRequestMethodPtr, "GET")) { CgiLibResponseHeader (501, "text/html"); fprintf (stdout, "Not implemented!\n"); return; } CgiScriptNamePtr = CgiLibVar ("WWW_SCRIPT_NAME"); CgiPathInfoPtr = CgiLibVar ("WWW_PATH_INFO"); CgiPathTranslatedPtr = CgiLibVar ("WWW_PATH_TRANSLATED"); CgiFormDoPtr = CgiLibVar ("WWW_FORM_DO"); HeaderFilterPtr = CgiLibVar ("WWW_FORM_HEADER"); if (!*HeaderFilterPtr) HeaderFilterPtr = NULL; UrlFilterPtr = CgiLibVar ("WWW_FORM_URL"); if (!*UrlFilterPtr) UrlFilterPtr = NULL; CgiPtr = CgiLibVar ("WWW_FORM_MAXACC"); MaxAccessHours = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MAXEXPIRES"); MaxExpiresHours = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MAXHITS"); MaxHits = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MAXLAST"); /* bit of backward compatibility */ if (!CgiPtr[0]) CgiPtr = CgiLibVar ("WWW_FORM_MAXAGE"); MaxLastModHours = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MAXLOAD"); MaxLoadHours = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MINACC"); MinAccessHours = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MINEXPIRES"); MinExpiresHours = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MINHITS"); MinHits = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MINLAST"); /* bit of backward compatibility */ if (!CgiPtr[0]) CgiPtr = CgiLibVar ("WWW_FORM_MINAGE"); MinLastModHours = atoi(CgiPtr); CgiPtr = CgiLibVar ("WWW_FORM_MINLOAD"); MinLoadHours = atoi(CgiPtr); if (!CgiPathInfoPtr[0] && (!CgiFormDoPtr[0] || strsame(CgiFormDoPtr, "form", -1))) GenerateForm (); else { sys$setprv (1, &SysPrvMask, 0, 0); SearchCache (CgiPathTranslatedPtr); sys$setprv (1, &SysPrvMask, 0, 0); } } /*****************************************************************************/ /* */ GenerateForm (char *FileSpec) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GenerateForm()\n"); CgiLibResponseHeader (200, "text/html"); fprintf (stdout, "\n\ \n\ %s\n\ \n\ \n\ %s

\n\

\n\ \n\ \ \n\ \n\ \n\ \n\ \n\ \ \n\ \ \n\ \ \n\ \ \n\ \ \n\ \ \n\ \
Action:\n\ \n\ \
URL filter:
Header filter:
Last Accessed:\n\ Min.\n\ Max.\n\
Last Loaded:\n\ Min.\n\ Max.\n\
Last Modified:\n\ Min.\n\ Max.\n\
Expires:\n\ Min.\n\ Max.\n\
Hits:\n\ Min.\n\ Max.\n\
\n\ \n\ \n\
\n\
\n\ \n\ \n", SoftwareID, SoftwareID, CgiScriptNamePtr); ThereHasBeenOutput = false; } /*****************************************************************************/ /* Search the cache tree using a wildcard specification or fully-specified file, it doesn't matter. With wildcards multiple files will be found, with a full specified file only the one! Process each/the file. */ SearchCache (char *FileSpec) { static char ElapsedTime [32]; static $DESCRIPTOR (ElapsedFaoDsc, "!%D\0"); static $DESCRIPTOR (ElapsedTimeDsc, ElapsedTime); static unsigned long StatTimerElapsedTime = 1; int status; unsigned long ElapsedBinTime [2]; char *cptr; char DclCommand [256], ExpandedFileName [256], FileName [256]; struct FAB SearchFab; struct NAM SearchNam; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SearchCache() |%s|\n", FileSpec); /* initialize statistics timer */ if (VMSnok (status = lib$init_timer (&TotalStatTimerContext))) exit (status); /* we'll just take the chance of including an HTML-forbidden char here! */ FilterComment[0] = '\0'; if (UrlFilterPtr) sprintf (FilterComment+strlen(FilterComment), "\nURL=%s", UrlFilterPtr); if (HeaderFilterPtr) sprintf (FilterComment+strlen(FilterComment), "\nHeader=%s", HeaderFilterPtr); if (MinLastModHours) sprintf (FilterComment+strlen(FilterComment), "\nMin.Modified=%d", MinLastModHours); if (MaxLastModHours) sprintf (FilterComment+strlen(FilterComment), "\nMax.Modified=%d", MaxLastModHours); if (MinExpiresHours) sprintf (FilterComment+strlen(FilterComment), "\nMin.Expires=%d", MinExpiresHours); if (MaxExpiresHours) sprintf (FilterComment+strlen(FilterComment), "\nMax.Expires=%d", MaxExpiresHours); if (MinLoadHours) sprintf (FilterComment+strlen(FilterComment), "\nMin.Loaded=%d", MinLoadHours); if (MaxLoadHours) sprintf (FilterComment+strlen(FilterComment), "\nMax.Loaded=%d", MaxLoadHours); if (MinAccessHours) sprintf (FilterComment+strlen(FilterComment), "\nMin.Access=%d", MinAccessHours); if (MaxAccessHours) sprintf (FilterComment+strlen(FilterComment), "\nMax.Access=%d", MaxAccessHours); if (MinHits) sprintf (FilterComment+strlen(FilterComment), "\nMin.Hits=%d", MinHits); if (MaxHits) sprintf (FilterComment+strlen(FilterComment), "\nMax.Hits=%d", MaxHits); #define CACHE_ROOT_SPEC \ "?WASD_CACHE_ROOT:[*...]*.HTC;0,HT_CACHE_ROOT:[*...]*.HTC;0" SearchFab = cc$rms_fab; SearchFab.fab$l_dna = v10orPrev10(CACHE_ROOT_SPEC,-1); SearchFab.fab$b_dns = 27; SearchFab.fab$l_fna = FileSpec; SearchFab.fab$b_fns = strlen (FileSpec); SearchFab.fab$l_nam = &SearchNam; SearchNam = cc$rms_nam; SearchNam.nam$l_esa = ExpandedFileName; SearchNam.nam$b_ess = sizeof(ExpandedFileName)-1; SearchNam.nam$l_rsa = FileName; SearchNam.nam$b_rss = sizeof(FileName)-1; if (VMSnok (status = sys$parse (&SearchFab, 0, 0))) { if (IsCliUtility) exit (status); CgiLibResponseError (FI_LI, status, SearchFab.fab$l_dna); return; } SearchNam.nam$l_ver[SearchNam.nam$b_ver] = '\0'; if (Debug) fprintf (stdout, "ExpandedFileName |%s|\n", ExpandedFileName); /*************************/ /* what needs to be done */ /*************************/ /* done here because we need to check on the presence of wildcards */ if (IsCliUtility) { if (!(CliDoBody || CliDoHeader || CliDoFull)) { if (SearchNam.nam$l_fnb & NAM$M_WILDCARD) DoProfile = true; else DoFile = true; } if (CliDoFull) DoFile = true; if (DoIndex && !DoNoProfile) DoProfile= true; if (CliDoBody || CliDoHeader || CliDoFull || DoFile || DoIndex || HeaderFilterPtr || UrlFilterPtr) OpenAndReadFile = true; else OpenAndReadFile = false; } else { CgiDoAnaRms = CgiDoDelete = CgiDoDump = CgiDoResponse = DoFile = DoIndex = DoProfile = false; if (strsame (CgiFormDoPtr, "anarms", -1)) CgiDoAnaRms = true; else if (strsame (CgiFormDoPtr, "delete", -1)) { if (!CgiRemoteUserPtr[0]) { CgiLibResponseError (FI_LI, 0, "Authorization required!"); return; } CgiDoDelete = true; } else if (strsame (CgiFormDoPtr, "dump", -1)) CgiDoDump = true; else if (strsame (CgiFormDoPtr, "index", -1)) DoIndex = true; else if (strsame (CgiFormDoPtr, "profile", -1)) DoProfile = true; else if (strsame (CgiFormDoPtr, "response", -1)) CgiDoResponse = true; else { if (SearchNam.nam$l_fnb & NAM$M_WILDCARD) DoProfile = true; else DoFile = true; } if (DoIndex) DoProfile = true; if (CgiDoResponse || DoFile || DoIndex || HeaderFilterPtr || UrlFilterPtr) OpenAndReadFile = true; else OpenAndReadFile = false; } memset (ProfileAccess, 0, sizeof(ProfileAccess)); memset (ProfileExpires, 0, sizeof(ProfileExpires)); memset (ProfileLoad, 0, sizeof(ProfileLoad)); memset (ProfileModified, 0, sizeof(ProfileModified)); memset (ProfileRevisionCount, 0, sizeof(ProfileRevisionCount)); FileCount = VersionSixCount = 0; /********************/ /* file search loop */ /********************/ for (;;) { status = sys$search (&SearchFab, 0, 0); if (Debug) fprintf (stdout, "sys$search() %%X%08.08X\n", status); if (VMSnok (status)) break; *SearchNam.nam$l_ver = '\0'; if (Debug) fprintf (stdout, "FileName |%s|\n", FileName); if (IsCliUtility) ProcessFile (FileName); else if (IsCgiScript) { if (CgiDoAnaRms) { CgiLibResponseSetRecordMode (1); CgiLibResponseHeader (200, "text/plain"); sprintf (DclCommand, "ANALYZE/RMS %s", FileName); system (DclCommand); ThereHasBeenOutput = true; } else if (CgiDoDump) { CgiLibResponseSetRecordMode (1); CgiLibResponseHeader (200, "text/plain"); sprintf (DclCommand, "DUMP %s", FileName); system (DclCommand); ThereHasBeenOutput = true; } else ProcessFile (FileName); } *SearchNam.nam$l_ver = ';'; } if (status == RMS$_NMF) status = SS$_NORMAL; if (VMSnok (status)) { if (IsCliUtility) exit (status); CgiLibResponseError (FI_LI, status, ExpandedFileName); return; } if ((DoIndex || DoProfile) && !DoNoProfile) ProcessProfile (); if (DoIndex || DoProfile) { if (VMSnok (status = lib$stat_timer (&StatTimerElapsedTime, &ElapsedBinTime, &TotalStatTimerContext))) exit (status); if (VMSnok (sys$fao (&ElapsedFaoDsc, 0, &ElapsedTimeDsc, &ElapsedBinTime))) strcpy (ElapsedTime, "*ERROR*"); for (cptr = ElapsedTime; *cptr == ' ' || *cptr == '0' || *cptr == ':'; cptr++); CgiLibFaoPrint (stdout, "\nFinished: !20%D (!AZ elapsed)\n", 0, cptr); if (VersionSixCount) CgiLibFaoPrint (stdout, "Version 6 Cache Files: !UL\n", VersionSixCount); } if (IsCgiScript) { if (ThereHasBeenOutput && (DoIndex || DoFile || DoProfile)) CgiLibFaoPrint (stdout, "\n\n\n"); else if (!ThereHasBeenOutput) CgiLibResponseError (FI_LI, 0, "Nothing matched!"); } /* free timer */ if (VMSnok (status = lib$free_timer (&TotalStatTimerContext))) exit (status); } /*****************************************************************************/ /* */ ProcessProfile () { int idx, TotalCount, TotalAllocated, TotalUsed; char *BeginBoldPtr, *EndBoldPtr; char BlocksPercent [8], CountPercent [8]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessProfile()\n"); if (IsCliUtility) { BeginBoldPtr = ""; EndBoldPtr = ""; } else { BeginBoldPtr = ""; EndBoldPtr = ""; } if (IsCgiScript && DoProfile && !DoIndex) CgiLibFaoPrint (stdout, " !UL -->", FileCount); PrintProfile ("Modified", &ProfileModified); PrintProfile ("Expires", &ProfileExpires); PrintProfile ("Loaded", &ProfileLoad); PrintProfile ("Accessed", &ProfileAccess); CgiLibFaoPrint (stdout, "\n!AZ!10 !10< Count!> !4* !10< Used!> !10< Allocated!>\n\ !10*- !10*- !4* !10*- !10*-!AZ\n", BeginBoldPtr, EndBoldPtr); TotalCount = TotalAllocated = TotalUsed = 0; for (idx = 0; idx < 29; idx++) { if (!ProfileRevisionCount[idx].Count) continue; TotalCount += ProfileRevisionCount[idx].Count; TotalAllocated += ProfileRevisionCount[idx].BlocksAllocated; TotalUsed += ProfileRevisionCount[idx].BlocksUsed; } for (idx = 0; idx < 29; idx++) { if (!ProfileRevisionCount[idx].Count) continue; PercentOf (ProfileRevisionCount[idx].Count, TotalCount, CountPercent); PercentOf (ProfileRevisionCount[idx].BlocksAllocated, TotalAllocated, BlocksPercent); CgiLibFaoPrint (stdout, "!10 !10UL !4AZ !10UL !10UL !4AZ\n", HitRange[idx], ProfileRevisionCount[idx].Count, CountPercent, ProfileRevisionCount[idx].BlocksUsed, ProfileRevisionCount[idx].BlocksAllocated, BlocksPercent); } CgiLibFaoPrint (stdout, "\n!AZ!10* !10*- !4* !10*- !10*-\n\ !10 !10UL !4* !10UL !10UL!AZ\n", BeginBoldPtr, TotalCount, TotalUsed, TotalAllocated, EndBoldPtr); } /*****************************************************************************/ /* */ PrintProfile ( char *ProfileName, struct ProfileStruct *ProfileArrayPtr ) { int DayAllocated, DayCount, DayUsed, FieldWidth, Hour, NextDay, TotalCount, TotalBlocks; char *BeginBoldPtr, *EndBoldPtr, *GreaterThanPtr, *LessThanPtr; char BlocksPercent [8], CountPercent [8]; struct ProfileStruct *paptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "PrintProfile()\n"); if (IsCliUtility) { BeginBoldPtr = ""; EndBoldPtr = ""; GreaterThanPtr = ">"; LessThanPtr = "<"; FieldWidth = 10; } else { BeginBoldPtr = ""; EndBoldPtr = ""; GreaterThanPtr = ">"; LessThanPtr = "<"; FieldWidth = 13; } CgiLibFaoPrint (stdout, "\n!AZ!10 !10< Count!> !4 \ !10< Used!> !10< Allocated!> !4\n\ !10*- !10*- !4* !10*- !10*-!AZ\n", BeginBoldPtr, ProfileName, EndBoldPtr); paptr = ProfileArrayPtr; Hour = TotalCount = TotalBlocks = 0; while (Hour <= PROFILE_HOURS_MAX+1) { if (paptr->Count) { TotalCount += paptr->Count; TotalBlocks += paptr->BlocksAllocated; } Hour++; paptr++; } if (Debug) fprintf (stdout, "Count: %d Blocks: %d\n", TotalCount, TotalBlocks); paptr = ProfileArrayPtr; Hour = 0; while (Hour < PROFILE_HOURS_MAX) { if (Hour == 0) { if (paptr->Count) { PercentOf (paptr->Count, TotalCount, CountPercent); PercentOf (paptr->BlocksAllocated, TotalBlocks, BlocksPercent); CgiLibFaoPrint (stdout, "!#< !AZ1 hrs!> !10UL !4AZ !10UL !10UL !4AZ\n", FieldWidth, LessThanPtr, paptr->Count, CountPercent, paptr->BlocksUsed, paptr->BlocksAllocated, BlocksPercent); } Hour++; paptr++; continue; } if (Hour < 24) { if (paptr->Count) { PercentOf (paptr->Count, TotalCount, CountPercent); PercentOf (paptr->BlocksAllocated, TotalBlocks, BlocksPercent); CgiLibFaoPrint (stdout, "!10 !10UL !4AZ !10UL !10UL !4AZ\n", Hour, paptr->Count, CountPercent, paptr->BlocksUsed, paptr->BlocksAllocated, BlocksPercent); } Hour++; paptr++; continue; } if (Hour < PROFILE_HOURS_MAX) { /* accumulate 24 hours into a per-day total */ DayCount = DayUsed = DayAllocated = 0; NextDay = Hour + 24; while (Hour < NextDay) { DayCount += paptr->Count; DayUsed += paptr->BlocksUsed; DayAllocated += paptr->BlocksAllocated; Hour++; paptr++; } if (DayCount) { PercentOf (DayCount, TotalCount, CountPercent); PercentOf (DayAllocated, TotalBlocks, BlocksPercent); CgiLibFaoPrint (stdout, "!10 !10UL !4AZ !10UL !10UL !4AZ\n", NextDay/24, DayCount, CountPercent, DayUsed, DayAllocated, BlocksPercent); } continue; } } if (paptr->Count) { PercentOf (paptr->Count, TotalCount, CountPercent); PercentOf (paptr->BlocksAllocated, TotalBlocks, BlocksPercent); CgiLibFaoPrint (stdout, "!# !10UL !4AZ !10UL !10UL !4AZ\n", FieldWidth, GreaterThanPtr, Hour/24, paptr->Count, CountPercent, paptr->BlocksUsed, paptr->BlocksAllocated, BlocksPercent); } paptr++; if (paptr->Count) { PercentOf (paptr->Count, TotalCount, CountPercent); PercentOf (paptr->BlocksAllocated, TotalBlocks, BlocksPercent); CgiLibFaoPrint (stdout, "!#< none!> !10UL !4AZ !10UL !10UL !4AZ\n", FieldWidth-3, paptr->Count, CountPercent, paptr->BlocksUsed, paptr->BlocksAllocated, BlocksPercent); } } /*****************************************************************************/ /* */ PercentOf ( int SubTotal, int Total, char *String ) { static $DESCRIPTOR (PercentFaoDsc, "!3UL%\0"); static $DESCRIPTOR (StringDsc, ""); int Percent; float FloatPercent; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "PercentOf() %d %d = ", SubTotal, Total); if (Total) { FloatPercent = (float)SubTotal * 100.0 / (float)Total; Percent = (int)FloatPercent; if (FloatPercent - (float)Percent >= 0.5) Percent++; } else Percent = 0; if (Debug) fprintf (stdout, "%d%%\n", Percent); if (String) { if (Percent) { StringDsc.dsc$a_pointer = String; StringDsc.dsc$w_length = 5; if (VMSnok (sys$fao (&PercentFaoDsc, 0, &StringDsc, Percent))) strcpy (String, "****"); } else String[0] = '\0'; } return (Percent); } /*****************************************************************************/ /* This overly-long function receives a file specification from SearchCache() and processes it according to the information requested. */ int ProcessFile (char* FileName) { static $DESCRIPTOR (DeviceDsc, ""); boolean GzipCompressed; int status, idx, BlocksAllocated, BlocksUsed, AccessHours, ExpiresHours, LastModHours, LoadHours; unsigned long AllocatedVbn, EndOfFileVbn; unsigned long CurrentBinTime[2]; char ContentBuffer [4096], ExpandedFileName [256]; char *cptr, *sptr, *zptr, *HeaderPtr, *PathPtr, *UrlPtr; struct FAB FileFab; struct NAM FileNam; struct RAB FileRab; struct ProxyCacheFileAcpStruct *faptr; struct ProxyCacheDescrStruct ProxyCacheDescr; struct ProxyCacheFileAcpStruct FileAcpData; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessFile() |%s|\n", FileName); HeaderPtr = UrlPtr = NULL; if (IsCliUtility && !ThereHasBeenOutput) { if (DoIndex) { CgiLibFaoPrint (stdout, "!AZ, !20%D!AZ\n\ \n\ !50 URL\n\ !34 \ !34 \ !34 \ !34 Count\n\ \n", SoftwareID, 0, FilterComment); ThereHasBeenOutput = true; } else if (DoFile) { CgiLibFaoPrint (stdout, "!AZ, !20%D!AZ\n\n", SoftwareID, 0, FilterComment); ThereHasBeenOutput = true; } else if (DoProfile && !DoIndex) { CgiLibFaoPrint (stdout, "!AZ, !20%D!AZ\n", SoftwareID, 0, FilterComment); ThereHasBeenOutput = true; } } else if (IsCgiScript && !ThereHasBeenOutput) { if (DoIndex) { CgiLibResponseHeader (200, "text/html"); CgiLibFaoPrint (stdout, "\n\ \n\ !AZ !20%D\n\ \n\ \n\
!AZ\n\
!20%D!AZ\n\n\
        !50  URL\n\
        !34 \
!34 \
!34 \
!34 Count\n\
\n",
            SoftwareID, 0,
            SoftwareID, 0, FilterComment);
         fflush (stdout);
         ThereHasBeenOutput = true;
      }
      else
      if (DoFile)
      {
         CgiLibResponseHeader (200, "text/html",
                               "Expires: Fri, 13 JAN 1978 14:00:00 GMT\n");
         CgiLibFaoPrint (stdout,
"\n\
\n\
!AZ !20%D\n\
\n\
\n\
!AZ\n\
!20%D!AZ\n\n",
            SoftwareID, 0,
            SoftwareID, 0, FilterComment);
         fflush (stdout);
         ThereHasBeenOutput = true;
      }
      else
      if (DoProfile && !DoIndex)
      {
         CgiLibResponseHeader (200, "text/html");

         CgiLibFaoPrint (stdout,
"\n\
\n\
!AZ !20%D\n\
\n\
\n\
!AZ\n\
!20%D!AZ\
!6UL. !AZ %X!XL !AZ\n",
               ++FileCount, FileName, status, SysGetMsg(status));
         return (status);
      }
      else
      if (IsCliUtility)
         exit (status);
      else
      {
         CgiLibResponseError (FI_LI, status, FileName);
         return (status);
      }
   }

   /* terminate the expanded file name */
   *FileNam.nam$l_ver = '\0';

   if (OpenAndReadFile)
   {
      /*******************/
      /* connect the RAB */
      /*******************/

      FileRab = cc$rms_rab;
      FileRab.rab$l_fab = &FileFab;
      /* 2 buffers and read ahead performance option */
      FileRab.rab$b_mbf = 2;
      FileRab.rab$l_rop = RAB$M_RAH;

      if (VMSnok (status = sys$connect (&FileRab, 0, 0)))
      {
         sys$close (&FileFab, 0, 0);
         if (DoIndex)
         {
            if (IsCliUtility)
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            else
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            return (status);
         }
         else
         if (IsCliUtility)
            exit (status);
         else
         {
            CgiLibResponseError (FI_LI, status, FileName);
            exit (status | STS$M_INHIB_MSG);
         }
      }

      /*****************/
      /* file contents */
      /*****************/

      FileRab.rab$l_ubf = (char*)&ProxyCacheDescr;
      FileRab.rab$w_usz = sizeof(ProxyCacheDescr);
      if (VMSnok (status = sys$get (&FileRab, 0, 0)))
      {
         sys$close (&FileFab, 0, 0);
         if (DoIndex)
         {
            if (IsCliUtility)
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            else
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            return (status);
         }
         else
         if (IsCliUtility)
            exit (status);
         else
         {
            CgiLibResponseError (FI_LI, status, FileName);
            return (status);
         }
      }

      if (Debug)
         fprintf (stdout, "version: %08.08X\n", ProxyCacheDescr.CacheVersion);
      if (ProxyCacheDescr.CacheVersion != PROXY_CACHE_FILE_VERSION_9)
      {
         sys$close (&FileFab, 0, 0);
         if (DoIndex)
         {
            if (ProxyCacheDescr.CacheVersion == PROXY_CACHE_FILE_VERSION_6)
               VersionSixCount++;
            else
            {
               if (IsCliUtility)
                  CgiLibFaoPrint (stdout,
                     "!6UL. !AZ unknown cache file version\n",
                     ++FileCount, FileName);
               else
                  CgiLibFaoPrint (stdout,
                     "!6UL. !AZ unknown cache file version\n",
                     ++FileCount, FileName);
            }
            return (status);
         }
         else
         if (IsCliUtility)
         {
            if (ProxyCacheDescr.CacheVersion == PROXY_CACHE_FILE_VERSION_6)
               VersionSixCount++;
            else
            {
               fprintf (stdout,
"%%%s-E-VERSION, unknown cache file version\n \\%s\\\n",
                        Utility, FileName);
               exit (STS$K_ERROR | STS$M_INHIB_MSG);
            }
         }
         else
         {
            if (ProxyCacheDescr.CacheVersion == PROXY_CACHE_FILE_VERSION_6)
               VersionSixCount++;
            else
            {
               CgiLibResponseError (FI_LI, 0, "Unknown cache file version!");
               return (STS$K_ERROR);
            }
         }
      }

      if (!(UrlPtr = malloc(ProxyCacheDescr.UrlLength)))
      {
         status = vaxc$errno;
         sys$close (&FileFab, 0, 0);
         if (IsCliUtility)
            exit (status);
         else
            CgiLibResponseError (FI_LI, status, "malloc()");
         return (status);
      }
      FileRab.rab$l_ubf = UrlPtr;
      FileRab.rab$w_usz = ProxyCacheDescr.UrlLength;
      if (VMSnok (status = sys$get (&FileRab, 0, 0)))
      {
         sys$close (&FileFab, 0, 0);
         free (UrlPtr);
         if (DoIndex)
         {
            if (IsCliUtility)
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            else
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            return (status);
         }
         else
         if (IsCliUtility)
            exit (status);
         else
         {
            CgiLibResponseError (FI_LI, status, FileName);
            return (status);
         }
      }
      if (Debug) fprintf (stdout, "|%s|\n", UrlPtr);

      if (UrlFilterPtr)
      {
         /**************/
         /* URL filter */
         /**************/

         if (!SearchTextString (UrlPtr, UrlFilterPtr, false, NULL))
         {
            if (Debug) fprintf (stdout, "URL filtered out\n");
            sys$close (&FileFab, 0, 0);
            free (UrlPtr);
            return (SS$_NORMAL);
         }
      }

      if (CgiDoResponse)
      {
         /****************************/
         /* response header and body */
         /****************************/

         FileRab.rab$l_ubf = ContentBuffer;
         FileRab.rab$w_usz = sizeof(ContentBuffer);
         while (VMSok (status = sys$get (&FileRab, 0, 0)))
            fwrite (ContentBuffer, FileRab.rab$w_rsz, 1, stdout);

         if (Debug) fprintf (stdout, "sys$get() %%X%08.08X\n", status);
         sys$close (&FileFab, 0, 0);
         free (UrlPtr);
         if (status == RMS$_EOF) status = SS$_NORMAL;
         if (VMSnok (status))
            CgiLibResponseError (FI_LI, status, FileName);
         return (status);
      }

      /*******************/
      /* go on to header */
      /*******************/

      if (!(HeaderPtr = malloc(ProxyCacheDescr.HeaderLength+1)))
      {
         status = vaxc$errno;
         sys$close (&FileFab, 0, 0);
         if (DoIndex)
         {
            if (IsCliUtility)
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            else
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            return (status);
         }
         else
         if (IsCliUtility)
            exit (status);
         else
         {
            CgiLibResponseError (FI_LI, status, "malloc()");
            exit (status);
         }
      }
      FileRab.rab$l_ubf = HeaderPtr;
      FileRab.rab$w_usz = ProxyCacheDescr.HeaderLength;
      if (VMSnok (status = sys$get (&FileRab, 0, 0)))
      {
         sys$close (&FileFab, 0, 0);
         free (UrlPtr);
         free (HeaderPtr);
         if (DoIndex)
         {
            if (IsCliUtility)
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            else
               CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
                  ++FileCount, FileName, status, SysGetMsg(status));
            return (status);
         }
         else
         if (IsCliUtility)
            exit (status);
         else
         {
            CgiLibResponseError (FI_LI, status, FileName);
            return (status);
         }
      }
      HeaderPtr[ProxyCacheDescr.HeaderLength] = '\0';
      if (Debug) fprintf (stdout, "|%s|\n", HeaderPtr);

      if (HeaderFilterPtr)
      {
         /*****************/
         /* header filter */
         /*****************/

         if (!SearchTextString (HeaderPtr, HeaderFilterPtr, false, NULL))
         {
            if (Debug) fprintf (stdout, "header filtered out\n");
            sys$close (&FileFab, 0, 0);
            free (UrlPtr);
            free (HeaderPtr);
            return (SS$_NORMAL);
         }
      }

      if (CliDoHeader)
      {
         /***************/
         /* header only */
         /***************/

         fputs (HeaderPtr, stdout);

         sys$close (&FileFab, 0, 0);
         free (UrlPtr);
         free (HeaderPtr);

         return (status);
      }

      if (CliDoBody)
      {
         /**********************/
         /* response body only */
         /**********************/

         /* this would most commonly be to a redirected output file */
         FileRab.rab$l_ubf = ContentBuffer;
         FileRab.rab$w_usz = sizeof(ContentBuffer);
         while (VMSok (status = sys$get (&FileRab, 0, 0)))
            fwrite (ContentBuffer, FileRab.rab$w_rsz, 1, stdout);

         if (Debug) fprintf (stdout, "sys$get() %%X%08.08X\n", status);
         if (status == RMS$_EOF) status = SS$_NORMAL;

         sys$close (&FileFab, 0, 0);
         free (UrlPtr);
         free (HeaderPtr);

         return (status);
      }
   }

   /*****************/
   /* file ACP info */
   /*****************/

   memset (&FileAcpData, 0, sizeof(FileAcpData));
   faptr = &FileAcpData;

   /* assign a channel to the disk device containing the file */
   DeviceDsc.dsc$a_pointer = FileNam.nam$l_dev;
   DeviceDsc.dsc$w_length = FileNam.nam$b_dev;
   if (Debug)
      fprintf (stdout, "device |%*.*s|\n",
               FileNam.nam$b_dev, FileNam.nam$b_dev, FileNam.nam$l_dev);

   status = sys$assign (&DeviceDsc, &faptr->Channel, 0, 0, 0);
   if (Debug) fprintf (stdout, "sys$assign() %%X%08.08X\n", status);
   if (VMSnok (status))
   {
      if (IsCliUtility)
         exit (status);
      else
      {
         CgiLibResponseError (FI_LI, status, FileName);
         exit (status | STS$M_INHIB_MSG);
      }
   }

   /* set up the File Information Block for the ACP interface */
   faptr->FileFibAcpDsc.dsc$w_length = sizeof(struct fibdef);
   faptr->FileFibAcpDsc.dsc$a_pointer = (char*)&faptr->FileFib;

   memcpy (&faptr->FileFib.fib$w_did, &FileNam.nam$w_did, 6);

   faptr->FileNameAcpDsc.dsc$a_pointer = FileNam.nam$l_name;
   faptr->FileNameAcpDsc.dsc$w_length = FileNam.nam$b_name +
                                        FileNam.nam$b_type;
   if (Debug)
      fprintf (stdout, "name |%*.*s|\n",
               faptr->FileNameAcpDsc.dsc$w_length,
               faptr->FileNameAcpDsc.dsc$w_length,
               faptr->FileNameAcpDsc.dsc$a_pointer);

#ifdef __VAX
#define FILEATR_CAST (unsigned int)
#else
#define FILEATR_CAST (void*)
#endif
   faptr->FileAtr[0].atr$w_size = sizeof(faptr->BdtBinTime);
   faptr->FileAtr[0].atr$w_type = ATR$C_BAKDATE;
   faptr->FileAtr[0].atr$l_addr = FILEATR_CAST &faptr->BdtBinTime;
   faptr->FileAtr[1].atr$w_size = sizeof(faptr->CdtBinTime);
   faptr->FileAtr[1].atr$w_type = ATR$C_CREDATE;
   faptr->FileAtr[1].atr$l_addr = FILEATR_CAST &faptr->CdtBinTime;
   faptr->FileAtr[2].atr$w_size = sizeof(faptr->EdtBinTime);
   faptr->FileAtr[2].atr$w_type = ATR$C_EXPDATE;
   faptr->FileAtr[2].atr$l_addr = FILEATR_CAST &faptr->EdtBinTime;
   faptr->FileAtr[3].atr$w_size = sizeof(faptr->RdtBinTime);
   faptr->FileAtr[3].atr$w_type = ATR$C_REVDATE;
   faptr->FileAtr[3].atr$l_addr = FILEATR_CAST &faptr->RdtBinTime;
   faptr->FileAtr[4].atr$w_size = sizeof(faptr->AscDates);
   faptr->FileAtr[4].atr$w_type = ATR$C_ASCDATES;
   faptr->FileAtr[4].atr$l_addr = FILEATR_CAST &faptr->AscDates;
   faptr->FileAtr[5].atr$w_size = sizeof(faptr->RecAttr);
   faptr->FileAtr[5].atr$w_type = ATR$C_RECATTR;
   faptr->FileAtr[5].atr$l_addr = FILEATR_CAST &faptr->RecAttr;
   faptr->FileAtr[6].atr$w_size =
     faptr->FileAtr[6].atr$w_type = 0;
   faptr->FileAtr[6].atr$l_addr = 0;

   status = sys$qiow (0, faptr->Channel, IO$_ACCESS, 0, 0, 0, 
                      &faptr->FileFibAcpDsc, &faptr->FileNameAcpDsc, 0, 0,
                      &faptr->FileAtr, 0);
   if (Debug) fprintf (stdout, "sys$qiow() %%X%08.08X\n", status);

   sys$dassgn (faptr->Channel);

   if (VMSnok (status))
   {
      /* ACP error */
      if (FileFab.fab$w_ifi) sys$close (&FileFab, 0, 0);
      if (UrlPtr) free (UrlPtr);
      if (HeaderPtr) free (HeaderPtr);

      if (DoIndex)
      {
         if (IsCliUtility)
            CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
               ++FileCount, FileNam.nam$l_dir, status, SysGetMsg(status));
         else
            CgiLibFaoPrint (stdout, "!6UL. !AZ %X!XL !AZ\n",
               ++FileCount, FileNam.nam$l_dir, status, SysGetMsg(status));
         return (status);
      }
      else
      if (IsCliUtility)
         exit (status);
      else
      {
         CgiLibResponseError (FI_LI, status, FileName);
         return (status);
      }
   }

   GzipCompressed = faptr->RecAttr.RecordAttrib & RAT_GZIP;

   if ((MinHits && faptr->AscDates.RevisionCount < MinHits) ||
       (MaxHits && faptr->AscDates.RevisionCount > MaxHits))
   {
      /****************/
      /* min/max hits */
      /****************/

      if (FileFab.fab$w_ifi) sys$close (&FileFab, 0, 0);
      if (UrlPtr) free (UrlPtr);
      if (HeaderPtr) free (HeaderPtr);
      return (SS$_NORMAL);
   }

   /***************/
   /* usage/hours */
   /***************/

   AllocatedVbn = faptr->RecAttr.AllocatedVbnLo +
                  (faptr->RecAttr.AllocatedVbnHi << 16);

   EndOfFileVbn = faptr->RecAttr.EndOfFileVbnLo +
                  (faptr->RecAttr.EndOfFileVbnHi << 16);

   BlocksAllocated = AllocatedVbn;
   if (EndOfFileVbn == 1 && !faptr->RecAttr.FirstFreeByte)
      BlocksUsed = 0;
   else
      BlocksUsed = EndOfFileVbn;

   sys$gettim (&CurrentBinTime);
   AccessHours = ProxyCacheDeltaHours (&CurrentBinTime, &faptr->RdtBinTime);
   ExpiresHours = ProxyCacheDeltaHours (&CurrentBinTime, &faptr->EdtBinTime);
   LastModHours = ProxyCacheDeltaHours (&CurrentBinTime, &faptr->CdtBinTime);
   LoadHours = ProxyCacheDeltaHours (&CurrentBinTime, &faptr->BdtBinTime);

   if ((MinAccessHours && AccessHours < MinAccessHours) ||
       (MaxAccessHours && AccessHours > MaxAccessHours) ||
       (MinExpiresHours && ExpiresHours < MinExpiresHours) ||
       (MaxExpiresHours && ExpiresHours > MaxExpiresHours) ||
       (MinLastModHours && LastModHours < MinLastModHours) ||
       (MaxLastModHours && LastModHours > MaxLastModHours) ||
       (MinLoadHours && LoadHours < MinLoadHours) ||
       (MaxLoadHours && LoadHours > MaxLoadHours))
   {
      /*****************/
      /* min/max hours */
      /*****************/

      if (FileFab.fab$w_ifi) sys$close (&FileFab, 0, 0);
      if (UrlPtr) free (UrlPtr);
      if (HeaderPtr) free (HeaderPtr);
      return (SS$_NORMAL);
   }

   if (CgiDoDelete)
   {
      /*******************/
      /* delete the file */
      /*******************/

      if (FileFab.fab$w_ifi) sys$close (&FileFab, 0, 0);
      if (UrlPtr) free (UrlPtr);
      if (HeaderPtr) free (HeaderPtr);

      status = sys$erase (&FileFab, 0, 0);
      if (Debug) fprintf (stdout, "sys$erase() %%X%08.08X\n", status);
      CgiLibResponseHeader (200, "text/plain");
      if (VMSok (status))
         CgiLibFaoPrint (stdout, "%!AZ-I-DELETE, !AZ\n",
                         Utility, FileName);
      else
         CgiLibFaoPrint (stdout, "%!AZ-E-DELETE, !AZ\n-!AZ\n",
                         Utility, FileName, SysGetMsg(status)+1);
      ThereHasBeenOutput = true;
      fflush (stdout);
      return (status);
   }

   /**************/
   /* statistics */
   /**************/

   FileCount++;

   if (LastModHours < PROFILE_HOURS_MAX)
   {
      ProfileModified[LastModHours].Count++;
      ProfileModified[LastModHours].BlocksAllocated += BlocksAllocated;
      ProfileModified[LastModHours].BlocksUsed += BlocksUsed;
   }
   else
   if (LastModHours < ZERO_TIME_DAYS)
   {
      ProfileModified[PROFILE_HOURS_MAX].Count++;
      ProfileModified[PROFILE_HOURS_MAX].BlocksAllocated += BlocksAllocated;
      ProfileModified[PROFILE_HOURS_MAX].BlocksUsed += BlocksUsed;
   }
   else
   {
      ProfileModified[PROFILE_HOURS_MAX+1].Count++;
      ProfileModified[PROFILE_HOURS_MAX+1].BlocksAllocated += BlocksAllocated;
      ProfileModified[PROFILE_HOURS_MAX+1].BlocksUsed += BlocksUsed;
   }

   if (ExpiresHours < PROFILE_HOURS_MAX)
   {
      ProfileExpires[ExpiresHours].Count++;
      ProfileExpires[ExpiresHours].BlocksAllocated += BlocksAllocated;
      ProfileExpires[ExpiresHours].BlocksUsed += BlocksUsed;
   }
   else
   if (ExpiresHours < ZERO_TIME_DAYS)
   {
      ProfileExpires[PROFILE_HOURS_MAX].Count++;
      ProfileExpires[PROFILE_HOURS_MAX].BlocksAllocated += BlocksAllocated;
      ProfileExpires[PROFILE_HOURS_MAX].BlocksUsed += BlocksUsed;
   }
   else
   {
      ProfileExpires[PROFILE_HOURS_MAX+1].Count++;
      ProfileExpires[PROFILE_HOURS_MAX+1].BlocksAllocated += BlocksAllocated;
      ProfileExpires[PROFILE_HOURS_MAX+1].BlocksUsed += BlocksUsed;
   }

   if (LoadHours < PROFILE_HOURS_MAX)
   {
      ProfileLoad[LoadHours].Count++;
      ProfileLoad[LoadHours].BlocksAllocated += BlocksAllocated;
      ProfileLoad[LoadHours].BlocksUsed += BlocksUsed;
   }
   else
   if (LoadHours < ZERO_TIME_DAYS)
   {
      ProfileLoad[PROFILE_HOURS_MAX].Count++;
      ProfileLoad[PROFILE_HOURS_MAX].BlocksAllocated += BlocksAllocated;
      ProfileLoad[PROFILE_HOURS_MAX].BlocksUsed += BlocksUsed;
   }
   else
   {
      ProfileLoad[PROFILE_HOURS_MAX+1].Count++;
      ProfileLoad[PROFILE_HOURS_MAX+1].BlocksAllocated += BlocksAllocated;
      ProfileLoad[PROFILE_HOURS_MAX+1].BlocksUsed += BlocksUsed;
   }

   if (AccessHours < PROFILE_HOURS_MAX)
   {
      ProfileAccess[AccessHours].Count++;
      ProfileAccess[AccessHours].BlocksAllocated += BlocksAllocated;
      ProfileAccess[AccessHours].BlocksUsed += BlocksUsed;
   }
   else
   if (AccessHours < ZERO_TIME_DAYS)
   {
      ProfileAccess[PROFILE_HOURS_MAX].Count++;
      ProfileAccess[PROFILE_HOURS_MAX].BlocksAllocated += BlocksAllocated;
      ProfileAccess[PROFILE_HOURS_MAX].BlocksUsed += BlocksUsed;
   }
   else
   {
      ProfileAccess[PROFILE_HOURS_MAX+1].Count++;
      ProfileAccess[PROFILE_HOURS_MAX+1].BlocksAllocated += BlocksAllocated;
      ProfileAccess[PROFILE_HOURS_MAX+1].BlocksUsed += BlocksUsed;
   }

   /* rev cnt: 1,2,..9, 10-19,20-29...90-99, 100-199,200-299,900-999, 1000+ */
   if (faptr->AscDates.RevisionCount < 10)
      idx = faptr->AscDates.RevisionCount;
   else
   if (faptr->AscDates.RevisionCount < 100)
      idx = 9 + (faptr->AscDates.RevisionCount / 10);
   else
   if (faptr->AscDates.RevisionCount < 1000)
      idx = 18 + (faptr->AscDates.RevisionCount / 100);
   else
      idx = 28;

   ProfileRevisionCount[idx].Count++;
   ProfileRevisionCount[idx].BlocksAllocated += BlocksAllocated;
   ProfileRevisionCount[idx].BlocksUsed += BlocksUsed;

   if (DoProfile && !DoIndex)
   {
      /****************/
      /* profile only */
      /****************/

      if (FileFab.fab$w_ifi) sys$close (&FileFab, 0, 0);
      if (UrlPtr) free (UrlPtr);
      if (HeaderPtr) free (HeaderPtr);
      return (SS$_NORMAL);
   }

   /**********/
   /* report */
   /**********/

   if (IsCliUtility)
   {
      if (DoIndex)
      {
         CgiLibFaoPrint (stdout,
"!6UL. !AZ !11<(!UL/!UL)!>  !AZ\n\
        !20%D !13<(!UL/!UL)!> \
!20%D !13<(!UL/!UL)!> \
!20%D !13<(!UL/!UL)!> \
!20%D !13<(!UL/!UL)!> !UL\n",
            FileCount,
            FileNam.nam$l_dir, BlocksUsed, BlocksAllocated, UrlPtr,
            &faptr->CdtBinTime, TIME_HOURS(LastModHours),
                                TIME_DAYS(LastModHours),
            &faptr->EdtBinTime, TIME_HOURS(ExpiresHours),
                                TIME_DAYS(ExpiresHours),
            &faptr->BdtBinTime, TIME_HOURS(LoadHours),
                                TIME_DAYS(LoadHours),
            &faptr->RdtBinTime, TIME_HOURS(AccessHours),
                                TIME_DAYS(AccessHours),
            faptr->AscDates.RevisionCount);
      }
      else
      if (DoFile)
      {
         CgiLibFaoPrint (stdout,
"   Cache File: !AZ\n\
       Blocks: !UL/!UL\n\
Last-Modified: !20%D !7UL hours !6UL days\n\
      Expires: !20%D !7UL       !6UL\n\
       Loaded: !20%D !7UL       !6UL\n\
     Accessed: !20%D !7UL       !6UL\n\
 Access Count: !UL\n\
 GZIP encoded: !AZ\n\
          URL: !AZ\n\
+-------------------------\n\
!AZ\
+-------------------------\n",
            ExpandedFileName, BlocksUsed, BlocksAllocated,
            &faptr->CdtBinTime, TIME_HOURS(LastModHours),
                                TIME_DAYS(LastModHours),
            &faptr->EdtBinTime, TIME_HOURS(ExpiresHours),
                                TIME_DAYS(ExpiresHours),
            &faptr->BdtBinTime, TIME_HOURS(LoadHours),
                                TIME_DAYS(LoadHours),
            &faptr->RdtBinTime, TIME_HOURS(AccessHours),
                                TIME_DAYS(AccessHours),
            faptr->AscDates.RevisionCount,
            GzipCompressed ? "yes" : "no",
            UrlPtr,
            HeaderPtr);
      }
   }
   else
   if (IsCgiScript)
   {
      PathPtr = VmsToPath (NULL, ExpandedFileName);

      if (DoIndex)
      {
         CgiLibFaoPrint (stdout,
"!6UL. !AZ !11<(!UL/!UL)!>  \
!AZ\n\
        !20%D !13<(!UL/!UL)!> \
!20%D !13<(!UL/!UL)!> \
!20%D !13<(!UL/!UL)!> \
!20%D !13<(!UL/!UL)!> !UL\n",
            FileCount,
            CgiScriptNamePtr, PathPtr, FileNam.nam$l_dir,
            BlocksUsed, BlocksAllocated,
            UrlPtr, UrlPtr,
            &faptr->CdtBinTime, TIME_HOURS(LastModHours),
                                TIME_DAYS(LastModHours),
            &faptr->EdtBinTime, TIME_HOURS(ExpiresHours),
                                TIME_DAYS(ExpiresHours),
            &faptr->BdtBinTime, TIME_HOURS(LoadHours),
                                TIME_DAYS(LoadHours),
            &faptr->RdtBinTime, TIME_HOURS(AccessHours),
                                TIME_DAYS(AccessHours),
            faptr->AscDates.RevisionCount);

         /* ensure more immediate feedback when filtering cache files */
         if (HeaderFilterPtr ||
             UrlFilterPtr ||
             MinAccessHours || MaxAccessHours ||
             MinLastModHours || MaxLastModHours ||
             MinLoadHours || MaxLoadHours ||
             MinHits || MaxHits)
            fflush (stdout);
      }
      else
      if (DoFile)
      {
         CgiLibFaoPrint (stdout,
"   Cache File: !AZ\n\
       Blocks: !UL/!UL\n\
Last-Modified: !20%D !7UL hours !6UL days\n\
      Expires: !20%D !7UL       !6UL\n\
       Loaded: !20%D !7UL       !6UL\n\
     Accessed: !20%D !7UL       !6UL\n\
 Access Count: !UL\n\
 GZIP encoded: !AZ\n\
          URL: !AZ\n\
\n\

\ !AZ\
\ [VIEW] \ [DUMP] \ [ANA/RMS] \ [DELETE!!]\n", ExpandedFileName, BlocksUsed, BlocksAllocated, &faptr->CdtBinTime, TIME_HOURS(LastModHours), TIME_DAYS(LastModHours), &faptr->EdtBinTime, TIME_HOURS(ExpiresHours), TIME_DAYS(ExpiresHours), &faptr->BdtBinTime, TIME_HOURS(LoadHours), TIME_DAYS(LoadHours), &faptr->RdtBinTime, TIME_HOURS(AccessHours), TIME_DAYS(AccessHours), faptr->AscDates.RevisionCount, GzipCompressed ? "yes" : "no", UrlPtr, UrlPtr, HeaderPtr, CgiScriptNamePtr, PathPtr, CgiScriptNamePtr, PathPtr, CgiScriptNamePtr, PathPtr, CgiScriptNamePtr, PathPtr); } } /**********/ /* finish */ /**********/ if (FileFab.fab$w_ifi) sys$close (&FileFab, 0, 0); if (UrlPtr) free (UrlPtr); if (HeaderPtr) free (HeaderPtr); return (SS$_NORMAL); } /****************************************************************************/ /* Return the age in hours relative to the the supplied time. */ int ProxyCacheDeltaHours ( unsigned long *BinaryTimePtr, unsigned long *AgeBinaryTimePtr ) { static unsigned long LibDeltaHours = LIB$K_DELTA_HOURS; int status; unsigned long LastModHours; unsigned long BinTime[2], ResultBinTime [2]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProxyCacheDeltaHours()\n"); if (!AgeBinaryTimePtr[0] && !AgeBinaryTimePtr[1]) return (ZERO_TIME); if (!BinaryTimePtr) sys$gettim (BinaryTimePtr = (unsigned long*)&BinTime); status = lib$sub_times (BinaryTimePtr, AgeBinaryTimePtr, &ResultBinTime); if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status); if (status == LIB$_NEGTIM) return (0); status = lib$cvt_from_internal_time (&LibDeltaHours, &LastModHours, &ResultBinTime); if (Debug) fprintf (stdout, "lib$cvt_() %%X%08.08X\n", status); if (Debug) fprintf (stdout, "hours: %d\n", LastModHours); return (LastModHours); } /*****************************************************************************/ /* Convert a VMS file specification into a URL-style specification. For example: "DEVICE:[DIR1.DIR2]FILE.TXT" into "/device/dir1/dir2/file.txt". */ char* VmsToPath ( char *PathPtr, char *VmsPtr ) { static char Path [256]; char *pptr, *vptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "VmsToPath() |%s|\n", VmsPtr); vptr = VmsPtr; if (!(pptr = PathPtr)) pptr = PathPtr = Path; *pptr++ = '/'; /* copy the device and directory components */ while (*vptr) { if (*vptr == ':' && *(vptr+1) == '[') { vptr++; vptr++; /* remove any reference to a Master File Directory */ if (strncmp (vptr, "000000", 6) == 0) vptr += 6; else *pptr++ = '/'; } if (*vptr == '.') { if (vptr[1] == '.' && vptr[2] == '.') { *pptr++ = '/'; *pptr++ = *vptr++; *pptr++ = *vptr++; *pptr++ = *vptr++; } else { vptr++; *pptr++ = '/'; } } else if (*vptr == ']') { vptr++; *pptr++ = '/'; break; } else *pptr++ = tolower(*vptr++); } /* copy the file component */ while (*vptr) *pptr++ = tolower(*vptr++); *pptr++ = '\0'; *pptr = '\0'; if (Debug) fprintf (stdout, "PathPtr |%s|\n", PathPtr); return (PathPtr); } /*****************************************************************************/ /* String search allowing wildcard "*" (matching any multiple characters) and "%" (matching any single character). Returns NULL if not found or a pointer to start of matched string. */ char* SearchTextString ( char *InThat, char *This, boolean CaseSensitive, int *MatchedLengthPtr ) { /* wildcards implied at both ends of the search string */ #define IMPLIED_WILDCARDS 0 char *cptr, *sptr, *inptr, *RestartCptr, *RestartInptr, *MatchPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SearchTextString()\n|%s|%s|\n", This, InThat); if (MatchedLengthPtr) *MatchedLengthPtr = 0; if (!*(cptr = This)) return (NULL); inptr = MatchPtr = InThat; #if IMPLIED_WILDCARDS /* skip leading text up to first matching character (if any!) */ if (*cptr != '*' && *cptr != '%') { if (CaseSensitive) while (*inptr && *inptr != *cptr) inptr++; else while (*inptr && toupper(*inptr) != toupper(*cptr)) inptr++; if (Debug && !*inptr) fprintf (stdout, "1. NOT matched!\n"); if (!*inptr) return (NULL); cptr++; MatchPtr = inptr++; } #endif /* IMPLIED_WILDCARDS */ for (;;) { if (CaseSensitive) { while (*cptr && *inptr && *cptr == *inptr) { cptr++; inptr++; } } else { while (*cptr && *inptr && toupper(*cptr) == toupper(*inptr)) { cptr++; inptr++; } } #if IMPLIED_WILDCARDS if (!*cptr) { if (Debug) fprintf (stdout, "1. matched!\n"); if (MatchedLengthPtr) *MatchedLengthPtr = inptr - MatchPtr; return (MatchPtr); } #else if (!*cptr && !*inptr) { if (Debug) fprintf (stdout, "2. matched!\n"); if (MatchedLengthPtr) *MatchedLengthPtr = inptr - MatchPtr; return (MatchPtr); } #endif /* IMPLIED_WILDCARDS */ if (*cptr != '*' && *cptr != '%') { if (Debug) fprintf (stdout, "3. NOT matched!\n"); return (NULL); } if (*cptr == '%') { /* single char wildcard processing */ if (!*inptr) { if (Debug) fprintf (stdout, "4. NOT matched!\n"); return (NULL); } cptr++; inptr++; continue; } /* asterisk wildcard matching */ while (*cptr == '*') cptr++; /* an asterisk wildcard at end matches all following */ if (!*cptr) { if (Debug) fprintf (stdout, "5. matched!\n"); while (*inptr) inptr++; if (MatchedLengthPtr) *MatchedLengthPtr = inptr - MatchPtr; return (MatchPtr); } /* note the current position in the string (first after the wildcard) */ RestartCptr = cptr; for (;;) { /* find first char in InThat matching char after wildcard */ if (CaseSensitive) while (*inptr && *cptr != *inptr) inptr++; else while (*inptr && toupper(*cptr) != toupper(*inptr)) inptr++; /* if did not find matching char in InThat being searched */ if (Debug && !*inptr) fprintf (stdout, "6. NOT matched!\n"); if (!*inptr) return (NULL); /* note the current position in InThat being searched */ RestartInptr = inptr; /* try to match the remainder of the string and InThat */ if (CaseSensitive) { while (*cptr && *inptr && *cptr == *inptr) { cptr++; inptr++; } } else { while (*cptr && *inptr && toupper(*cptr) == toupper(*inptr)) { cptr++; inptr++; } } /* if reached the end of both string and InThat - match! */ #if IMPLIED_WILDCARDS if (!*cptr) { if (Debug) fprintf (stdout, "7. matched!\n"); if (MatchedLengthPtr) *MatchedLengthPtr = inptr - MatchPtr; return (MatchPtr); } #else if (!*cptr && !*inptr) { if (Debug) fprintf (stdout, "8. matched!\n"); if (MatchedLengthPtr) *MatchedLengthPtr = inptr - MatchPtr; return (MatchPtr); } #endif /* IMPLIED_WILDCARDS */ /* break to the external loop if we encounter another wildcard */ if (*cptr == '*' || *cptr == '%') break; /* lets have another go */ cptr = RestartCptr; /* starting the character following the previous attempt */ inptr = MatchPtr = RestartInptr + 1; } } } /*****************************************************************************/ /* */ char* SysGetMsg (int StatusValue) { static char Message [256]; short int Length; $DESCRIPTOR (MessageDsc, Message); sys$getmsg (StatusValue, &Length, &MessageDsc, 0, 0); Message[Length] = '\0'; if (Debug) fprintf (stdout, "SysGetMsg() |%s|\n", Message); return (Message); } /*****************************************************************************/ /* From [SRC.HTTPD]SUPPORT.C Support version 10 and pre-version-10 logical and file naming conventions. Look for one of multiple (usually just two but can be more) possible logical names. When one is successfully translated return a pointer to it's null terminated name. Otherwise test for a subsequent and return a pointer to it if found. If none is found then return NoneFound if zero or positive, return the LogicalName if minus one. The constants should be #defined in the manner of "?WASD_CONFIG_GLOBAL\0HTTPD$CONFIG\0". */ char* v10orPrev10 ( char *LogicalName, int NoneFound ) { static unsigned short Length; static char ValueString [128]; static $DESCRIPTOR (LogicalNameDsc, ""); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); static struct { unsigned short buf_len; unsigned short item; void *buf_addr; void *ret_len; } LnmItems [] = { { sizeof(ValueString)-1, LNM$_STRING, ValueString, &Length }, { 0,0,0,0 } }; int status; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (!LogicalName || LogicalName[0] != '?') return (LogicalName); /* stop at any logical name delimiting colon (perhaps of a file name) */ for (sptr = cptr = LogicalName+1; *sptr && *sptr != ':'; sptr++); while (*cptr) { LogicalNameDsc.dsc$a_pointer = cptr; LogicalNameDsc.dsc$w_length = sptr - cptr; status = sys$trnlnm (0, &LnmFileDevDsc, &LogicalNameDsc, 0, &LnmItems); if (VMSok (status)) return (cptr); /* scan past any logical-delimiting colon */ while (*sptr) sptr++; /* stop at any logical name delimiting colon (perhaps of a file name) */ for (cptr = (sptr += 1); *sptr && *sptr != ':'; sptr++); } if (NoneFound != -1) return ((char*)NoneFound); return (LogicalName+1); } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration symbol or logical containing the equivalent. */ GetParameters () { static char CommandLine [256]; static unsigned long Flags = 0; int status; unsigned short Length; char ch; char *aptr, *cptr, *clptr, *sptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetParameters()\n"); if (!(clptr = getenv ("PCACHE$PARAM"))) { /* get the entire command line following the verb */ if (VMSnok (status = lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags))) exit (status); (clptr = CommandLine)[Length] = '\0'; } aptr = NULL; ch = *clptr; for (;;) { if (aptr && *aptr == '/') *aptr = '\0'; if (!ch) break; *clptr = ch; if (Debug) fprintf (stdout, "clptr |%s|\n", clptr); while (*clptr && isspace(*clptr)) *clptr++ = '\0'; aptr = clptr; if (*clptr == '/') clptr++; while (*clptr && !isspace (*clptr) && *clptr != '/') { if (*clptr != '\"') { clptr++; continue; } cptr = clptr; clptr++; while (*clptr) { if (*clptr == '\"') if (*(clptr+1) == '\"') clptr++; else break; *cptr++ = *clptr++; } *cptr = '\0'; if (*clptr) clptr++; } ch = *clptr; if (*clptr) *clptr = '\0'; if (Debug) fprintf (stdout, "aptr |%s|\n", aptr); if (!*aptr) continue; if (strsame (aptr, "/AUTHORIZED", 4)) { CliMustBeAuthorized = true; continue; } if (strsame (aptr, "/BODY=", 4)) { CliDoBody = true; for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; CliOutputPtr = cptr+1; continue; } if (strsame (aptr, "/CHARSET=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; CliCharsetPtr = cptr; continue; } if (strsame (aptr, "/DBUG", -1)) { Debug = true; continue; } if (strsame (aptr, "/DELETE", 4)) { CliDoDelete = true; continue; } if (strsame (aptr, "/FULL", 4)) { CliDoFull = true; continue; } if (strsame (aptr, "/FILTER=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; cptr++; if (toupper(*cptr) == 'H') { while (*cptr && *cptr != '=' && *cptr != ':') cptr++; if (!*cptr) continue; HeaderFilterPtr = cptr+1; continue; } if (toupper(*cptr) == 'U') { while (*cptr && *cptr != '=' && *cptr != ':') cptr++; if (!*cptr) continue; UrlFilterPtr = cptr+1; continue; } continue; } if (strsame (aptr, "/HEADER=", 4)) { CliDoHeader = true; for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; CliOutputPtr = cptr; continue; } if (strsame (aptr, "/INDEX=", 4)) { DoIndex = true; continue; } if (strsame (aptr, "/MAXACCESSED=", 6)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MaxAccessHours = atoi(cptr); continue; } if (strsame (aptr, "/MAXEXPIRES=", 6)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MaxExpiresHours = atoi(cptr); continue; } if (strsame (aptr, "/MAXHITS=", 5)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MaxHits = atoi(cptr); continue; } if (strsame (aptr, "/MAXLASTMOD=", 6) || /* bit of backward compatibility */ strsame (aptr, "/MAXAGE=", 6)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MaxLastModHours = atoi(cptr); continue; } if (strsame (aptr, "/MAXLOAD=", 5)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MaxLoadHours = atoi(cptr); continue; } if (strsame (aptr, "/MINACCESSED=", 6)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MinAccessHours = atoi(cptr); continue; } if (strsame (aptr, "/MINEXPIRES=", 6)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MinExpiresHours = atoi(cptr); continue; } if (strsame (aptr, "/MINHITS=", 5)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MinHits = atoi(cptr); continue; } if (strsame (aptr, "/MINLASTMOD=", 6) || /* bit of backward compatibility */ strsame (aptr, "/MINAGE=", 6)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MinLastModHours = atoi(cptr); continue; } if (strsame (aptr, "/MINLOAD=", 5)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (!*cptr) continue; MinLoadHours = atoi(cptr); continue; } if (strsame (aptr, "/OUTPUT=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; CliOutputPtr = cptr; continue; } if (strsame (aptr, "/PROFILE=", 4)) { DoProfile = true; continue; } if (strsame (aptr, "/NOPROFILE=", 6)) { DoNoProfile = true; continue; } if (*aptr == '/') { fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); exit (STS$K_ERROR | STS$M_INHIB_MSG); } if (!CliFileSpecPtr) { CliFileSpecPtr = aptr; continue; } if (CliFileSpecPtr) { fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } } /****************************************************************************/ /* Does a case-insensitive, character-by-character string compare and returns true if two strings are the same, or false if not. If a maximum number of characters are specified only those will be compared, if the entire strings should be compared then specify the number of characters as 0. */ boolean strsame ( char *sptr1, char *sptr2, int count ) { while (*sptr1 && *sptr2) { if (toupper (*sptr1++) != toupper (*sptr2++)) return (false); if (count) if (!--count) return (true); } if (*sptr1 || *sptr2) return (false); else return (true); } /*****************************************************************************/