/*****************************************************************************/ /* HyperReader.c A CGI-compliant script that reads (many) Bookreader-format documents and presents them as HTML documents. AND REMEMBER ... HyperReader can be used as a CGIplus script. This may require the script to be purged from the server before new startup parameters come into effect. Use a command like the following: HTTPD /DO=DCL=PURGE SPECIFYING BOOKS ---------------- Books are specified in one of two ways, with the indicated precedence. 1. ?file= .......... request query string form field (VMS file spec) 2. /path/to/file ... path into VMS file spec via WWW_PATH_TRANSLATED If a book has a known shelf extension (see storage 'BookshelfTypes') a 302 redirection is generated to the shelf processor (/hypershelf), likewise if a book is not supplied. The assumption being that the user knows no better than to use HyperReader for everything! BOOKS ACCESSED VIA DECNET ------------------------- If any books are being accessed via DECnet, that is if a shelf contains anything like book\NODE::DISK:[DECW$BOOK]BOOK.DECW$BOOK\A Book the script may return the following error %RMS-E-NETBTS, network buffer too small for !UL byte record Hyperreader attempts to cope with varying record sizes by always providing enough space ;^) ... the "rab$w_usz = 32767;" in GetChunk(). Add the following to the HyperReader support DCL procedure, WASD_ROOT:[SCRIPT]HYPERREADER.COM for WASD, WWW_ROOT:[BIN]HYPERREADER.COM for OSU ... $ SET RMS /NETWORK_BLOCK_COUNT=127 OSU ENVIRONMENT --------------- Script responses are returned in OSU "raw" mode; the script taking care of the full response header and correctly carriage-controlled data stream, text or binary!! Uses the CGILIB.C to engage in the dialog phase generating, storing and then making available the equivalent of CGI variables. "VANILLA" CGI ENVIRONMENT ------------------------- Primarily for the likes of Netscape FastTrack. This environment can accomodate CGI variables that are not prefixed with "WWW_" and do not supply "KEY_xxxxx" or "FORM_xxxxx" (which must be derived from "QUERY_STRING"). Full HTTP stream (non-parsed header) is assumed as not supported so all output occurs with a CGI-compliant header line (e.g. "Status: 200 Success") and record-oriented output. ABOUT HYPERREADER ----------------- Sorry about this utility. When I (MGD) first began reverse-engineering the structure I didn't include copious notes (or even any ... well it was just investigation), so now (late '94) I have only a vague idea of what some sections are all about. Any new development has better commentary! Also, no guarantees are given on any of this code, its all fairly tenuous. The program has grown by accretion, more a vehicle of experimentation than a paradigm of software engineering. This utility will probably always exist in a below- version-1, as it will probably never be entirely satisfactory. Well, enough of apologies! Code is included that should provide an informational message within the generated HTML whenever the utility encounters Bookreader formatting it cannot process. During development/debugging this can be checked for clues. The code that generates the GIF output is, in part, derived from other works from the PBM suite. Some is copyright and used within the original licence conditions: * GIF Image compression - LZW algorithm implemented with Trie type * structure. * Written by Bailey Brown, Jr. * last change May 24, 1990 * file: compgif.c * * You may use or modify this code as you wish, as long as you mention * my name in your documentation. * * - Bailey Brown, Jr. BOOKREADER DOCUMENTS ARE NOT PLAIN TEXT --------------------------------------- They are not even marked-up text (in the same sense as HTML documents). Essentially they are a series of images pasted onto an X window. Most of the images happen to be from fonts, but even then characters are not handled as text. There is essentially no information within the file on the documentary function of any text in a book. This is one reason for the poor performance of the internal Bookreader print functionality (it must adopt a similar approach, mapping into PostScript "pages"). Bookreader format is not public. At least not that I could find. Hence considerable effort at reverse- engineering the structure (most originally during January 1992 fortunately, I wouldn't have the patience, time or interest now!) In any case, the structure is complex and I don't pretend to understand it all ... so the server tends to break occasionally. The server attempts to map a bit-addressed format (Bookreader) onto a character-cell format (text-page). The Bookreader format permits parts of a page to be generated non-sequentially, for example to place normal text, then place bolded text within it. This can occur on a per-line, per-section or per-page basis. So the entire "page" for a hypertext enviroment must be created by mapping into a "page" in memory. Sometimes this mapping is not perfect and character strings get garbled or poorly proportioned. Text is presented in a fixed font. This is due to two requirements: 1. The mapping from bit-addressed to cell-addressed described above requires a fixed character-cell matrix. 2. Maintaining correct layout for examples, tables, etc. As the Bookreader format specifies the positioning of the document's components, rather than information on their function, relative location must be maintained, generally eliminating the use of the more aesthetically pleasing proportional fonts. Some massaging of the resulting page occurs. This often involves educated guesswork. For instance, single blank lines preceding a line beginning with a lower-case alphabetic are most often an artifact of the mapping process rather than intended line breaks, and can be eliminated to produce a better layout. Similarly, bulleted and numbered lists can be enhanced by ensuring a blank line exists between elements. Unfortunately there are not many instances where the original intent can be clearly identified. As this is a stateless, client-server application any dynamic memory allocated will be released on image exit, and does not need to be explicitly released. -POSSIBLE- BOOKREADER BOOK STRUCTURE (REVERSE-ENGINEERED SO FAR!): ------------------------------------------------------------------ First Record ------------ fixed size of 0x3fe part type: short 0x00 (type=0x0001) part length: long 0x02 number of parts in book long 0x46 number of sections in book long 0x4a number of fonts in book long 0x52 VBN (512) of last part long 0x5a position in VBN of last part short 0x5e Text Records ------------ variable size part type: short 0x00 (type=0x0003) part length: long 0x02 previous text part number long 0x06 continued part long 0x0a (if > 1 then continues previous) sections of text in this part long 0x0e previous chunk part number long 0x12 chunk ends at part number long 0x16 . then from offset 0x1a . Sections within a text record ----------------------------- section type short 0x00 . if section type is 0x0012 (printable text) section length long 0x02 section data 2 long 0x06 this section number long 0x0a length of on-screen section long 0x1e section data 5 long 0x22 section data 6 long 0x26 section data 7 long 0x2a section data 8 long 0x2e section data 9 long 0x32 section data 10 long 0x36 . if section type is 0x0013 (figure) . if section type is 0x0014 (graphic (hotspots, etc)) . Printable text section ---------------------- type of text char 0x00 length of text section char 0x01 horizonal position short 0x02 vertical position short 0x04 font number char 0x06 unknown short 0x07 . text begins at offset 0x09, number of printable characters: text length char 0x00 1..text length characters then byte indicating decipoint(?) spacing between next printable characters SectionXref Record ------------------ variable size part type: short 0x00 (type=0x0006) part length: long 0x02 then starting at offset 0x06: Table of Contents Record ------------------------ variable size part type: short 0x00 (type=0x0007) part length: long 0x02 data 1 long 0x06 data 2 long 0x0a data 3 long 0x0e data 4 long 0x12 data 5 long 0x16 data 6 long 0x1a data 7 long 0x1e then starting at offset 0x22: a number of variable length units each comprising: (the series of units is terminated by a byte of 0x00 at offset 0x06) unknown short 0x00 unknown short 0x02 unknown short 0x04 null ('\0') terminted string at offset 0x06. References to Parts (symbols) Record ------------------------------------ variable size part type: short 0x00 (type=0x000d) part length: long 0x02 then starting at offset 0x06: (1 .. number of sections in the book) series of 32 byte units, each: null ('\0') terminted symbol string (can be just the number of the section) Font Record ----------- variable size part type: short 0x00 (type=0x0009) part length: long 0x02 then starting at offset 0x06: (1 .. number of fonts in the book) series of variable byte units, each: unknown short 0x00 unknown short 0x02 unknown short 0x04 font number (used in text) short 0x06 null ('\0') terminted font name string "Last" Record ------------- variable size part type: short 0x00 (type=0x0005) part length: long 0x02 then starting at offset 0x06: (1 .. number of parts in the book) series of 10 byte units, each: VBN (512) of part long 0x00 position in VBN of part short 0x04 length of this part long 0x06 CGI VARIABLES ------------- Server ... WWW_HTTP_IF_MODIFIED_SINCE if "304 Not modified" are desired (optional) WWW_HTTP_PRAGMA "no-cache" (optional) WWW_HTTP_REFERER "close" button becomes active WWW_PATH_INFO URL path to book WWW_PATH_TRANSLATED VMS file specification for book WWW_REQUEST_METHOD "GET" only is supported WWW_SCRIPT_NAME path to script (e.g. "/HyperReader") WWW_SERVER_CHARSET default character set of server WWW_SERVER_NAME host on which the script is executing WWW_SERVER_PORT server port WWW_SERVER_SOFTWARE HTTPd identifying string HyperReader ... WWW_FORM_CHUNK "chunk" number (section) of the book WWW_FORM_FILE VMS file name for book (when path cannot be supplied) WWW_FORM_GRAPHIC if not empty the get an image from the book WWW_FORM_MASSAGE if not empty then "massage" extra white space out WWW_FORM_REFERER overrides the HTTP_REFERER URL WWW_FORM_TITLE explicit title of book WWW_FORM_VARREC force record/block IO (debugging only) QUALIFIERS ---------- /BUTTONS= string containing button names /CHARSET= "Content-Type: text/html; charset=...", empty suppresses charset /DBUG turns on all "if (Debug)" statements (don't use with OSU) /HYPERSHELF= name of 'HyperShelf' script (defaults to "/hypershelf") /[NO]ODS5 control extended file specification (basically for testing) LOGICAL NAMES ------------- HYPERREADER$DBUG turns on all "if (Debug)" statements HYPERREADER$PARAM equivalent to (overrides) the command line parameters/qualifiers (define as a system-wide logical) HTTPD$GMT timezone offset from Greenwich Mean Time (e.g. "+09:30" and only needed if DTSS is not in use) BUILD DETAILS ------------- See BUILD_HYPERREADER.COM COPYRIGHT --------- Copyright (C) 1996-2014 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 any later version. http://www.gnu.org/licenses/gpl.txt HISTORY (also change SOFTWAREVN below!) ------- 06-OCT-2014 MGD v0.9.0, although now largely only of historical interest ... ... a nod to the twenty-first century (and happy 90th birthday Dad) 10-MAY-2005 MGD v0.8.27, SWS 2.0 ignore query string components supplied as command-line parameters differently to CSWS 1.2/3 23-DEC-2003 MGD v0.8.26, minor conditional mods to support IA64 12-APR-2003 MGD v0.8.25, link colour changed to 0000cc 20-AUG-2002 MGD v0.8.24, allow for HTTP/1.1 'Cache-control:' field used by Mozilla variants (instead of HTTP/1.0 'pragma:') 15-AUG-2002 MGD v0.8.23, massage source code (can't resist a fiddle) 08-DEC-2001 MGD v0.8.22, use RMS block IO for non-VAR record format books 01-JUL-2001 MGD v0.8.21, add 'SkipParameters' for direct OSU support 28-OCT-2000 MGD v0.8.20, use CGILIB object library 18-JAN-2000 MGD v0.8.19, support extended file specifications (ODS-5), improved GetChunk() record handling 07-AUG-1999 MGD v0.8.18, use more of the CGILIB functionality 24-APR-1999 MGD v0.8.17, use CGILIB.C, standard CGI environment (Netscape FastTrack) 02-OCT-1998 MGD v0.8.16, provide content-type "; charset=..." 08-AUG-1998 MGD v0.8.15, redirection if looks like meant for shelf processor, OSU output processing reworked, bugfix; TimeSetTimezone() 'Seconds' unsigned->signed 06-AUG-1998 MGD v0.8.14, accomodation for OSU ... reduce HTTP response header carriage control from to only (OSU/IE4 combination problematic) 01-AUG-1998 MGD v0.8.13, suppress table background colours if empty, accomodations for OSU environment 28-APR-1998 MGD v0.8.12, bit more playing around; book section descriptions (table-of-contents, etc) now generated from book content (non-English documents now work!!!!), remove X-bitmap image generation, some tidying up and cosmetic changes 13-MAR-1998 MGD v0.8.11, TimeSetGmt() modified to be in line with HTTPd, added check for too many chunks, sections and fonts 01-AUG-1997 MGD v0.8.10, 'HttpHasBeenOutput' not initialized for CGIplus 20-JUL-1997 MGD v0.8.9, added /BODY= qualifier (changed background colour) 07-JUL-1997 MGD v0.8.8, CGIplus capable, hopefully improved dynamic memory handling by setting a minimum allocation reducing fragmentation 30-JUN-1997 MGD v0.8.7, very minor changes to format, "Pragma: no-cache" now overrides "If-Modified-Since:" (as always, I look at this program and shudder :^) 16-AUG-1996 MGD v0.8.6, presentation changes (XBMs to GIFs) 23-FEB-1996 MGD v0.8.5, bugfix, after modifying the HTTPD$GMT format for the HTTPd server I had forgotten about some scripts 12-OCT-1995 MGD v0.8.4, 'path_translated' CGI variable now used to allow books to be specified using HTTP URL syntax; add 'Referer:', 'Last-Modified:'/'If-Modified-Since:' 02-AUG-1995 MGD v0.8.3, changed detection of table of contents, figures, etc., to substring in function FirstChunk(); changes to section processing, recognising figure types, etc. 24-MAY-1995 MGD v0.8.2, minor changes for AXP compatibility 15-APR-1995 MGD v0.8.1, made CGI-compliant, added "close" button functionality 12-NOV-1994 MGD v0.8.0, breakthrough in understanding the layout of text, I think! All page layup functions revised. 01-NOV-1994 MGD v0.7.0, support X-bitmap images, GIF images, X-bitmap ICON buttons, direct RMS file access (much more efficient) 07-OCT-1994 MGD v0.6.0, back from the dead ... for the hypertext environment 14-JAN-1992 MGD v0.5.0, initial development as "BREAD v0.5.0" ... because it was only half-baked :^) */ /*****************************************************************************/ #define SOFTWAREVN "0.9.0" #define SOFTWARENM "HYPERREADER" #define SOFTWARECR "Copyright (C) 1996-2014 Mark G.Daniel" #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 /* VMS-related header files */ #include #include #include #include #include #include #include #include #include #include /* application header file */ #include "enamel.h" #include "hyperreader.h" #include #ifndef __VAX # pragma nomember_alignment #endif /***************/ /* definitions */ /***************/ #ifndef __VAX # ifndef NO_ODS_EXTENDED # define ODS_EXTENDED 1 /* this is smaller than the technical maximum, but still quite large! */ # define ODS_MAX_FILE_NAME_LENGTH 511 # define ODS_MAX_FILESYS_NAME_LENGTH 264 # endif #endif #define ODS2_MAX_FILE_NAME_LENGTH 255 #ifndef ODS_MAX_FILE_NAME_LENGTH # define ODS_MAX_FILE_NAME_LENGTH ODS2_MAX_FILE_NAME_LENGTH #endif #if ODS_MAX_FILE_NAME_LENGTH < ODS2_MAX_FILE_NAME_LENGTH # define ODS_MAX_FILE_NAME_LENGTH ODS2_MAX_FILE_NAME_LENGTH #endif #define BOOL int #define true 1 #define false 0 #define VMSok(x) ((x) & STS$M_SUCCESS) #define VMSnok(x) !(((x) & STS$M_SUCCESS)) #define VMSwarning(x) (((x) & 0x7) == STS$K_WARNING) #define VMSerror(x) (((x) & 0x7) == STS$K_ERROR) #define VMSinfo(x) (((x) & 0x7) == STS$K_INFO) #define VMSfatal(x) (((x) & 0x7) == STS$K_SEVERR) #define FI_LI __FILE__, __LINE__ #define MAX_OUTLINE_LENGTH 128 #define MAX_DIVISIONS 16 #define LINES_ON_PAGE_PREALLOCATE 10 /* maximum of 1 is hardly consecutive ... but experience shows it looks ok! */ #define MAX_CONSECUTIVE_BLANK_LINES 1 /* Whenever a non-printable charater is encountered the "interim" is substituted in the text. Before the text is output these characters are used as markers for "lists" etc., as the text is "massaged". They are then replaced with the "final" character. These appear most often to be "bullets" for lists. */ #define INTERIM_NON_ASCII_CHAR 0x7f #define FINAL_NON_ASCII_CHAR '*' /* For characters of any fonts not supported substitute this character. These are usually "graphic"/"symbol" characters/fonts. */ #define NON_SUPPORTED_CHAR '~' /* seem to map bit-addressed to cell-addressed ok most of the time */ #define LINE_POSITIONING_UNITS 68 #define COLUMN_POSITIONING_UNITS 34 #define DEFAULT_BUTTONS \ "←Previous$↶Back=javascript:parent.history.go(-1)$\ Next→$Close$^Help=" DEFAULT_HYPERREADER_HELP /**********/ /* macros */ /**********/ /* This macro generates an address (pointer) to an output buffer character. It allows a linear array of structures to be addressed with a line/column value as if it was a matrix. A macro is used for speed (no function overhead), and readability (easier to read than the computation each time). */ #define OUT_CHAR_AT(Line, Column) \ ((struct OutCharStruct*)(OutPtr+(Line*MAX_OUTLINE_LENGTH)+Column)) /* try to avoid excessive fragmentation by specifying a minimum chunk */ #define MIN_HTML_STRING_ALLOC 128 /**************/ /* data types */ /**************/ struct ChunkDataStruct { unsigned long VBN; unsigned short VBNbyte; unsigned long Length; }; struct OutCharStruct { char c; char *htptr; }; struct FontStruct { int FontSize; BOOL FontBold; BOOL FontItalic; BOOL FontUnsupported; }; /* my divisions are the "Contents", "Index", etc. */ struct BookDivisionStruct { char *DescriptionPtr; int ChunkNumber; }; /******************/ /* global storage */ /******************/ char ErrorBookType [] = "Doesn't look like a book type!", ErrorCalloc [] = "allocating memory.", ErrorCallocBytes [] = "memory allocation of %d bytes failed", ErrorChunkRead [] = "chunk read problem.", ErrorImageSpecification [] = "Image specification incorrect.", ErrorRequestedChunk [] = "Could not find requested part of book!", ErrorStringOverflow [] = "String overflow!", ErrorTOC [] = "Could not locate table-of-contents!", ErrorTooManyDivisions [] = "Too many "divisions" in book! \ Recompile with increased MAX_DIVISIONS."; char *BookshelfTypes [] = { ".ODL", ".BKS", ".DECW$BOOKSHELF", "" }; char *BookTypes [] = { ".BKB", ".DECW$BOOK", "" }; char Utility [] = "HYPERREADER"; BOOL BookFileVarRec, Debug, DebugBytes, DebugFigures, DebugFonts, DebugGraphics, DebugHotspots, DebugChunks, DebugSections, DebugSymbols, DebugText, DoMassage, DoMassageOutput, ErrorReported, EnhanceText, IsCgiPlus, OsuEnvironment, StdCgiEnvironment, TimeAheadOfGmt; int BookFileLongestRecordLength, ChunkNumber, DynamicMemoryAllocated, EndGlobalVertical, GetChunkNumber, GlobalVertical, GlobalHorizontal, LastLineOnPage, LinesOnPage, LinesOutputToClient, NextChunkNumber, OdsExtended, PreviousChunkNumber, TablesChunkNumber, TotalBytesRead, TotalBytesWritten; #define TBW TotalBytesWritten long NumberOfChunks, NumberOfSections, NumberOfFonts, CurrentVBN, LastVBN, LastVBNlen; unsigned long IfModifiedSinceBinaryTime [2], TimeGmtDeltaBinary [2]; short LastVBNbyte; char BookSpecifiedByFile [ODS_MAX_FILE_NAME_LENGTH+1], BookTitle [256], ContentTypeCharset [64], DefaultButtons [] = DEFAULT_BUTTONS, DefaultStyle [] = DEFAULT_HYPERREADER_STYLE, ExpBookFileName [ODS_MAX_FILE_NAME_LENGTH+1], LastModifiedGmDateTime [32], SoftwareCopy [] = SOFTWARECR, SoftwareID [48], TimeGmtString [32], TimeGmtVmsString [32], UrlEncReferer [1024], UrlEncTitle [512]; char *BookFileNamePtr, *ButtonsPtr = DefaultButtons, *CgiEnvironmentPtr, *CgiFormChunkPtr, *CgiFormFilePtr, *CgiFormGraphicPtr, *CgiFormMassagePtr, *CgiFormRefererPtr, *CgiFormTitlePtr, *CgiFormVarRecPtr, *CgiHttpCacheControlPtr, *CgiHttpHostPtr, *CgiHttpPragmaPtr, *CgiHttpIfModifiedSincePtr, *CgiPathInfoPtr, *CgiPathTranslatedPtr, *CgiQueryStringPtr, *CgiRequestMethodPtr, *CgiRequestSchemePtr, *CgiScriptNamePtr, *CgiServerNamePtr, *CgiServerSoftwarePtr, *CgiServerPortPtr, *CharsetPtr, *CliCharsetPtr, *HyperShelfScriptNamePtr, *StyleSheetPtr = "", *UrlDoMassage, *UrlEncodedCgiPathInfoPtr; long *SectionChunkNumbersArrayPtr; struct FontStruct *FontArrayPtr; struct ChunkDataStruct *ChunkArrayPtr; /* HyperReader divisions are the "Contents", "Index", etc. */ struct BookDivisionStruct BookDivision [MAX_DIVISIONS+1]; int BookDivisionCount; struct OutCharStruct *OutPtr; unsigned char *ChunkBufferPtr; int PreviousChunkLength; unsigned char *ChunkPtr; unsigned char *EndChunkPtr; /* the five long words in the part header */ long PreviousChunk; long SectionsInChunk; long PreviousTopicChunk; long NextTopicChunk; long FinalChunkNumber; long CurrentSeek; long CurrentChunkLength; int CurrentChunk; short CurrentChunkType; struct ChunkDataStruct FirstChunk, FontChunk, ImageChunk, LastChunk, SectionXrefChunk, SectionHeadingsChunk, SymbolsChunk; struct FAB BookFileFab; struct RAB BookFileRab; #ifdef ODS_EXTENDED struct NAML BookFileNaml; #endif /* ODS_EXTENDED */ struct NAM BookFileNam; struct XABDAT BookFileXabDat; struct XABFHC BookFileXabFhc; /***********************/ /* function prototypes */ /***********************/ char* HtmlString (char*, int); char* ButtonBarButton (char*, char*, int, BOOL); /*****************************************************************************/ /* 'argc' and 'argv' are only required to support CgiLibEnvironmentInit(), in particular the OSU environment. */ main ( int argc, char *argv[] ) { int status; /*********/ /* begin */ /*********/ sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CgiLibEnvironmentVersion()); if (getenv ("HYPERREADER$DBUG")) Debug = true; if (Debug) CgiLibResponseHeader (200, "text/plain"); CgiLibEnvironmentSetDebug (Debug); CgiLibEnvironmentInit (argc, argv, false); GetParameters (); CgiLibResponseSetCharset (CliCharsetPtr); CgiLibResponseSetSoftwareID (SoftwareID); CgiLibResponseSetErrorMessage ("Reported by HyperReader"); if (StyleSheetPtr[0]) { char *cptr = calloc (1, 64+strlen(StyleSheetPtr)); sprintf (cptr, "\n", StyleSheetPtr); StyleSheetPtr = cptr; } IsCgiPlus = CgiLibEnvironmentIsCgiPlus (); CgiEnvironmentPtr = CgiLibEnvironmentName (); #ifdef ODS_EXTENDED OdsExtended = (GetVmsVersion() >= 72); ENAMEL_NAML_SANITY_CHECK #endif /* ODS_EXTENDED */ if (IsCgiPlus) { for (;;) { /* block waiting for the next request */ CgiLibVar (""); if (Debug) fputs ("Content-Type: text/plain\n", stdout); ProcessRequest (); CgiLibCgiPlusEOF (); } } else ProcessRequest (); exit (SS$_NORMAL); } /*****************************************************************************/ /* Get "command-line" parameters, whether from the command-line or from a configuration symbol or logical containing the equivalent. OSU scripts have the 'method', 'url' and 'protocol' supplied as P1, P2, P3 (these being detected and used by CGILIB), and are of no interest to this function. */ GetParameters () { static unsigned long Flags = 0; static char CommandLine [256]; int status, SkipParameters; unsigned short Length; char ch; char *aptr, *cptr, *clptr, *sptr; $DESCRIPTOR (CommandLineDsc, CommandLine); /*********/ /* begin */ /*********/ if (!(clptr = getenv ("HYPERREADER$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'; } /* if [C]SWS (VMS Apache) */ if (CgiLibEnvironmentIsApache()) { /* CSWS 1.2/3 look for something non-space outside of quotes */ for (cptr = clptr; *cptr; cptr++) { if (isspace(*cptr)) continue; if (*cptr != '\"') break; cptr++; while (*cptr && *cptr != '\"') cptr++; if (*cptr) cptr++; } /* CSWS 1.2/3 if nothing outside of quotes then ignore command line */ if (!*cptr) return; /* SWS 2.0 doesn't begin with /APACHE from DCL procedure wrapper */ if (!strsame (cptr, "/APACHE", 7)) return; } /* if OSU environment then skip P1, P2, P3 */ if (CgiLibEnvironmentIsOsu()) SkipParameters = 3; else SkipParameters = 0; aptr = NULL; ch = *clptr; for (;;) { if (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 (SkipParameters) { SkipParameters--; continue; } if (strsame (aptr, "/APACHE", 4)) { /* just skip this marker for command-line parameters under SWS 2.0 */ continue; } if (strsame (aptr, "/BUTTONS=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; if (*cptr == '+') { /* append buttons to defaults */ aptr = calloc (1, strlen(ButtonsPtr)+strlen(cptr)+2); strcpy (aptr, ButtonsPtr); strcat (aptr, "$"); strcat (aptr, cptr+1); ButtonsPtr = aptr; } else ButtonsPtr = cptr; 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, "/HYPERSHELF=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; HyperShelfScriptNamePtr = cptr; continue; } if (strsame (aptr, "/ODS5", 5)) { OdsExtended = true; continue; } if (strsame (aptr, "/NOODS5", 7)) { OdsExtended = false; continue; } if (strsame (aptr, "/STYLE=", 4)) { for (cptr = aptr; *cptr && *cptr != '='; cptr++); if (*cptr) cptr++; StyleSheetPtr = cptr; continue; } /* various levels of debugging (sigh!) */ /* if (strsame (aptr, "/BYT", 4)) { DebugBytes = true; continue; } if (strsame (aptr, "/CHU", 4)) { DebugChunks = true; continue; } if (strsame (aptr, "/DBU", 4)) { Debug = true; continue; } if (strsame (aptr, "/DEB", 4)) { Debug = true; continue; } if (strsame (aptr, "/FIG", 4)) { DebugFigures = true; continue; } if (strsame (aptr, "/FON", 4)) { DebugFonts = true; continue; } if (strsame (aptr, "/GRA", 4)) { DebugGraphics = true; continue; } if (strsame (aptr, "/HOT", 4)) { DebugHotspots = true; continue; } if (strsame (aptr, "/SEC", 4)) { DebugSections = true; continue; } if (strsame (aptr, "/SYM", 4)) { DebugSymbols = true; continue; } if (strsame (aptr, "/TEX", 4)) { DebugText = true; continue; } */ if (*aptr != '/') { fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n", Utility, aptr); exit (STS$K_ERROR | STS$M_INHIB_MSG); } else { fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n", Utility, aptr+1); exit (STS$K_ERROR | STS$M_INHIB_MSG); } } } /*****************************************************************************/ /* */ ProcessRequest () { int idx, status; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessRequest()\n"); CgiEnvironmentPtr = CgiLibEnvironmentName (); if (!getenv ("HYPERREADER$DBUG")) Debug = false; else Debug = true; ErrorReported = false; BookDivisionCount = DynamicMemoryAllocated = EndGlobalVertical = GlobalVertical = GlobalHorizontal = LastLineOnPage = LinesOnPage = LinesOutputToClient = NextChunkNumber = NextTopicChunk = PreviousChunkNumber = PreviousTopicChunk = TablesChunkNumber = 0; /*************************/ /* get the CGI variables */ /*************************/ /* initialize a timer for establishing page composition statistics */ lib$init_timer (0); CgiServerSoftwarePtr = CgiLibVar ("WWW_SERVER_SOFTWARE"); CgiRequestMethodPtr = CgiLibVar ("WWW_REQUEST_METHOD"); if (strcmp (CgiRequestMethodPtr, "GET")) { CgiLibResponseHeader (501, "text/html"); fprintf (stdout, "Not implemented!\n"); return; } CgiPathInfoPtr = CgiLibVar ("WWW_PATH_INFO"); CgiPathTranslatedPtr = CgiLibVar ("WWW_PATH_TRANSLATED"); CgiScriptNamePtr = CgiLibVar ("WWW_SCRIPT_NAME"); CgiQueryStringPtr = CgiLibVar ("WWW_QUERY_STRING"); CgiHttpCacheControlPtr = CgiLibVar ("WWW_HTTP_CACHE_CONTROL"); CgiHttpIfModifiedSincePtr = CgiLibVar ("WWW_HTTP_IF_MODIFIED_SINCE"); CgiHttpPragmaPtr = CgiLibVar ("WWW_HTTP_PRAGMA"); CgiFormFilePtr = CgiLibVar ("WWW_FORM_FILE"); if (!CgiFormFilePtr[0]) CgiFormFilePtr = CgiLibVar ("WWW_FORM_BOOK"); CgiFormChunkPtr = CgiLibVar ("WWW_FORM_CHUNK"); CgiFormGraphicPtr = CgiLibVar ("WWW_FORM_GRAPHIC"); CgiFormMassagePtr = CgiLibVar ("WWW_FORM_MASSAGE"); CgiFormTitlePtr = CgiLibVar ("WWW_FORM_TITLE"); /* this is really only for experimentation/debugging */ CgiFormVarRecPtr = CgiLibVar ("WWW_FORM_VARREC"); if (VMSnok (status = TimeSetGmt ())) { if (status != SS$_NOLOGNAM) { CgiLibResponseError (FI_LI, status, "GMT offset"); exit (SS$_NORMAL); } } if (CgiHttpIfModifiedSincePtr[0]) { if (VMSnok (HttpGmTime (CgiHttpIfModifiedSincePtr, &IfModifiedSinceBinaryTime))) { if (Debug) fprintf (stdout, "If-Modified-Since: NBG!\n"); IfModifiedSinceBinaryTime[0] = IfModifiedSinceBinaryTime[0] = 0; CgiHttpIfModifiedSincePtr = ""; } } /***********/ /* process */ /***********/ if (!HyperShelfScriptNamePtr) { if (CgiLibEnvironmentIsWasd() || CgiLibEnvironmentIsCgiPlus()) HyperShelfScriptNamePtr = DEFAULT_WASD_HYPERSHELF; else if (CgiLibEnvironmentIsOsu()) HyperShelfScriptNamePtr = DEFAULT_OSU_HYPERSHELF; else HyperShelfScriptNamePtr = DEFAULT_CGI_HYPERSHELF; } UrlEncodedCgiPathInfoPtr = (char*)CgiLibUrlEncode (CgiPathInfoPtr, -1, NULL, 0); if (CgiFormFilePtr[0]) { BookFileNamePtr = CgiFormFilePtr; sprintf (BookSpecifiedByFile, "file=%s&", CgiFormFilePtr); } else { BookFileNamePtr = CgiPathTranslatedPtr; BookSpecifiedByFile[0] = '\0'; } if (Debug) fprintf (stdout, "BookFileNamePtr |%s|\n", BookFileNamePtr); if (BookFileNamePtr[0]) { /* find file type (extension) */ for (cptr = BookFileNamePtr; *cptr; cptr++); if (*(cptr-1) == ';') *(cptr-1) = '\0'; while (cptr > BookFileNamePtr && *cptr != '.' && *cptr != ']' && *cptr != ':') cptr--; if (*cptr == '.') { /* check if looks like a bookshelf type */ for (idx = 0; BookshelfTypes[idx][0]; idx++) { if (Debug) fprintf (stdout, "|%s|%s|\n", BookshelfTypes[idx], cptr); if (!strsame (BookshelfTypes[idx], cptr, -1)) continue; /* looks like a shelf type! */ BookFileNamePtr = ""; break; } } } if (Debug) fprintf (stdout, "BookFileNamePtr |%s|\n", BookFileNamePtr); if (!BookFileNamePtr[0]) { /*******************************/ /* redirect to shelf processor */ /*******************************/ CgiLibResponseRedirect ("%!%s%s%s%s", HyperShelfScriptNamePtr, CgiPathInfoPtr, CgiQueryStringPtr[0] ? "?" : "", CgiQueryStringPtr); return; } /* check if looks like book type */ for (idx = 0; BookTypes[idx][0]; idx++) { if (Debug) fprintf (stdout, "|%s|%s|\n", BookTypes[idx], cptr); if (!strsame (BookTypes[idx], cptr, -1)) continue; /* looks like a book type! */ break; } if (!BookTypes[idx][0]) { CgiLibResponseError (FI_LI, 0, ErrorBookType); return; } CgiFormRefererPtr = CgiLibVar ("WWW_FORM_REFERER"); if (!CgiFormRefererPtr[0]) CgiFormRefererPtr = CgiLibVar ("WWW_HTTP_REFERER"); if (CgiFormRefererPtr[0]) CgiLibUrlEncode (CgiFormRefererPtr, -1, UrlEncReferer, -1); EnhanceText = true; TotalBytesRead = TBW = 0; if (CgiFormTitlePtr[0]) CgiLibUrlEncode (CgiFormTitlePtr, -1, UrlEncTitle, -1); if (toupper(CgiFormMassagePtr[0]) == 'N') DoMassage = false; else DoMassage = true; if (CgiFormGraphicPtr[0]) ImageReader(); else BookReader(); /************/ /* clean up */ /************/ if (BookFileFab.fab$w_ifi) sys$close (&BookFileFab, 0, 0); if (UrlEncodedCgiPathInfoPtr) free (UrlEncodedCgiPathInfoPtr); UrlEncodedCgiPathInfoPtr = NULL; if (ChunkBufferPtr) free (ChunkBufferPtr); ChunkBufferPtr = NULL; PreviousChunkLength = 0; if (ChunkArrayPtr) free (ChunkArrayPtr); ChunkArrayPtr = NULL; if (FontArrayPtr) free (FontArrayPtr); FontArrayPtr = NULL; if (SectionChunkNumbersArrayPtr) free (SectionChunkNumbersArrayPtr); SectionChunkNumbersArrayPtr = NULL; for (idx = 1; idx <= BookDivisionCount; idx++) { if (BookDivision[idx].DescriptionPtr) free (BookDivision[idx].DescriptionPtr); BookDivision[idx].DescriptionPtr = NULL; } PageSize (-1); MassageOutputLine (NULL, NULL, NULL); OutputPage (true); } /*****************************************************************************/ /* Retrieve a BRF (BookReader Format) image from within a document and send it to the client. Bookreader images are essentially uncompressed bitmaps. Horizontal and vertical coordinates position on the page, and height and width information allows the image to be reconstructed. */ ImageReader () { unsigned long ChunkLength = 0, DataLength, Horiz, ImageChunkOffset = 0, StartOfImageOffset = 0, VBN = 0, VBNbyte = 0; unsigned short HeightPixels, WidthPixels, SectionType; float FloatWidth; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ImageReader() |%s|\n", CgiFormGraphicPtr); /* The four numbers in the 'CgiFormGraphicPtr' are: (note that parts one and two constitute the RMS RFA of the record) 1. Virtual Block Number of the start of the part 2. starting byte position in the VBN 3. total length of the chunk 4. offset from start of chunk the image data begins This will allow the image to be retrieved very quickly by merely opening the file, reading the part (one or more records) via the RFA and jumping in to process the image data from the offset. */ sscanf (CgiFormGraphicPtr, "%d,%d,%d,%d", &VBN, &VBNbyte, &ChunkLength, &ImageChunkOffset); /* bit of a sanity check, VBNbyte can be zero of course, others cannot */ if (!VBN || VBNbyte > 511 || !ChunkLength || !ImageChunkOffset) { CgiLibResponseError (FI_LI, 0, ErrorImageSpecification); return; } if (VMSnok (OpenBookFile ())) return; ImageChunk.VBN = VBN; ImageChunk.VBNbyte = (unsigned short)VBNbyte; ImageChunk.Length = ChunkLength; if (VMSnok (GetChunk (&ImageChunk))) return; /* this is a good sanity check on the image specification */ if (ChunkLength != CurrentChunkLength) { CgiLibResponseError (FI_LI, 0, ErrorImageSpecification); return; } ChunkPtr = ChunkBufferPtr + ImageChunkOffset; /*********************/ /* process the image */ /*********************/ SectionType = *(unsigned short*)ChunkPtr; if (SectionType == 18) { /*******************************************************************/ /* the section 18 processing is ALL CONJECTURE ... 03-AUG-1995 MGD */ /*******************************************************************/ StartOfImageOffset = 42; DataLength = *(unsigned long*)(ChunkPtr+2) - StartOfImageOffset; /* fudge factor of 5.33 seems to work!? (must be another way!) */ WidthPixels = (unsigned short)((float)*(unsigned long*)(ChunkPtr+26) / 5.33); HeightPixels = DataLength / (WidthPixels / 8); } else if (SectionType == 19) { StartOfImageOffset = 50; DataLength = *(unsigned long*)(ChunkPtr+2); WidthPixels = *(unsigned short*)(ChunkPtr+46); HeightPixels = *(unsigned short*)(ChunkPtr+48); } if (DebugFigures) { DumpLongWords ("IMAGE: ", ChunkPtr+2, 12); fprintf (stdout, "DataLength : 0x%08.08lx (%d)\n", DataLength, DataLength); fprintf (stdout, "WidthPixels : %d\n", WidthPixels); fprintf (stdout, "HeightPixels : %d\n", HeightPixels); } GifImage (WidthPixels, HeightPixels, ChunkPtr+StartOfImageOffset, DataLength-StartOfImageOffset); } /*****************************************************************************/ /* Construct the page (requested portion of the book), output HTML to provide a page (book title) heading, etc., output the page constructed, then some more HTML to finish off the page. */ BookReader () { int idx; char *sptr; char EscapedBookFileName [ODS_MAX_FILE_NAME_LENGTH+1], UnixDateTime [32]; unsigned long UnixTime; struct tm *UnixTmPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "BookReader()\n"); if (VMSnok (OpenBookFile ())) return; /* This string is added to an HREF="..." link after the part number. It propagates whether the book contents are being "massaged" or not. */ if (DoMassageOutput = DoMassage) UrlDoMassage = ""; else UrlDoMassage = "&massage=no"; /************************/ /* process book details */ /************************/ /* get information of the physical "structure" of the book */ GetFirstChunk (); GetLastChunk (); GetFonts (); GetSectionXref (); GetChunkNumber = atol (CgiFormChunkPtr); if (Debug) fprintf (stdout, "CgiFormChunkPtr |%s| GetChunkNumber: %d\n", CgiFormChunkPtr, GetChunkNumber); if (!GetChunkNumber) { if (!BookDivisionCount) { CgiLibResponseError (FI_LI, 0, ErrorTOC); return; } GetChunkNumber = BookDivision[1].ChunkNumber; } if (GetChunkNumber < 1 || GetChunkNumber > NumberOfChunks) { CgiLibResponseError (FI_LI, 0, ErrorRequestedChunk); return; } for (idx = 1; idx <= BookDivisionCount; idx++) { if (BookDivision[idx].ChunkNumber == GetChunkNumber) { /* These pages do not have bold/italic-text tags and are not output-massaged at all. They look and "perform" better that way! */ DoMassageOutput = false; break; } } /*********************/ /* HTTP header, etc. */ /*********************/ time (&UnixTime); UnixTmPtr = localtime (&UnixTime); if (!strftime (UnixDateTime, sizeof(UnixDateTime), "%a, %d %b %Y %T", UnixTmPtr)) strcpy (UnixDateTime, "[error]"); if (Debug) fprintf (stdout, "UnixDateTime |%s|\n", UnixDateTime); TBW += CgiLibResponseHeader (200, "text/html", "Last-Modified: %s\n", LastModifiedGmDateTime); CgiLibHtmlEscape (ExpBookFileName, -1, EscapedBookFileName, sizeof(ExpBookFileName)); TBW += fprintf (stdout, "\n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n", SoftwareID, SoftwareCopy, CgiEnvironmentPtr, UnixDateTime, EscapedBookFileName, BookFileVarRec ? "record" : "block"); /******************/ /* construct page */ /******************/ if (VMSnok (GetChunk (&(ChunkArrayPtr[GetChunkNumber])))) return; ProcessChunk (); PreviousChunkNumber = PreviousTopicChunk; if (NextTopicChunk && NextTopicChunk < NumberOfChunks) NextChunkNumber = NextTopicChunk; /***************/ /* output page */ /***************/ TBW += fprintf (stdout, "HyperReader ... %s\n\ \n\ %s\ \n\ \n\
%s
\n", BookTitle, DefaultStyle, StyleSheetPtr, BookTitle); ButtonBar (1); TBW += fprintf (stdout, "
");

   OutputPage (false);

   TBW += fprintf (stdout, "
\n"); ButtonBar (2); Statistics (); TBW += fprintf (stdout, "\n\n"); } /*****************************************************************************/ /* */ Statistics () { static char StatisticsFao [] = "\n"; static long LibElapsedTime = 1, LibCpuTime = 2; unsigned long ElapsedTime [2]; unsigned long CpuTime; unsigned short Length; char String [512]; $DESCRIPTOR (StatisticsFaoDsc, StatisticsFao); $DESCRIPTOR (StringDsc, String); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "Statistics()\n"); lib$stat_timer (&LibElapsedTime, &ElapsedTime, 0); lib$stat_timer (&LibCpuTime, &CpuTime, 0); sys$fao (&StatisticsFaoDsc, &Length, &StringDsc, &ElapsedTime, CpuTime/6000, CpuTime/100, CpuTime%100, TotalBytesRead, /* this is a close approximation, total so far plus guess-timate */ TBW + sizeof(StatisticsFao), DynamicMemoryAllocated, LinesOnPage, LastLineOnPage, LinesOutputToClient); String[Length] = '\0'; fputs (String, stdout); } /*****************************************************************************/ /* The "page" of output has been constructed in the matrix of 'OutCharStruct' elements. Scan through this as if it was a page of text, starting at line one and character one, moving across the columns to the last character, then moving to the next line, etc. The page 'OutCharStruct' elements consist of a single character (any character mapped from the Bookreader source) and a pointer to 'char'. This pointer will, most often, be NULL. However, when HTML formatting (e.g. bolding) or linkage (e.g. a Bookreader "hotspot") is required a string is dynamically allocated to contain the required HTML and pointed to by this pointer in the appropriate character location. If the HTML pointer exists copy the string to the output buffer first then any Bookreader character in the character variable. */ OutputPage (BOOL Reset) { static int PreBlankLineCount = 0; BOOL TerminateAndOutputLine; int lcnt, ccnt, PostBlankLineCount = 0, LineLength; char *cptr, *eptr, *lptr, *zptr; char Line [8192]; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "OutputPage() %d/%d\n", LastLineOnPage, LinesOnPage); if (Reset) { /* reset after use */ PreBlankLineCount = 0; return; } /*******************/ /* loop thru lines */ /*******************/ for (lcnt = 1; lcnt <= LastLineOnPage; lcnt++) { if (Debug) fprintf (stdout, "%d\n", lcnt); /* 'eptr' points at last space character copied into output 'Line' */ zptr = (eptr = lptr = Line) + sizeof(Line)-8; /* point at the first character structure in the output buffer */ ocptr = OUT_CHAR_AT(lcnt,0); /************************/ /* loop thru characters */ /************************/ for (ccnt = 0; ccnt < MAX_OUTLINE_LENGTH; ccnt++) { if (lptr >= zptr) { CgiLibResponseError (FI_LI, 0, ErrorStringOverflow); return; } /* insert any HTML associated with the character position */ cptr = ocptr->htptr; if (cptr) { while (*cptr && lptr < zptr) *lptr++ = *cptr++; /* be sure to terminate after any included HTML */ eptr = lptr; } /* add the character from buffer, filter HTML-forbidden characters */ switch (ocptr->c) { case '&' : strcpy (lptr, "&"); lptr += 4; break; case '<' : strcpy (lptr, "<"); lptr += 3; break; case '>' : strcpy (lptr, ">"); lptr += 3; break; default : *lptr = ocptr->c; } /* if the character was not a space then note the position */ if (*lptr != ' ') eptr = lptr+1; lptr++; ocptr++; } /**********************/ /* end character loop */ /**********************/ if (eptr == Line) { /**************/ /* blank line */ /**************/ PreBlankLineCount++; } else { /******************/ /* non-blank line */ /******************/ /* go back to the last character copied, terminate line after that */ lptr = eptr; *lptr++ = '\n'; *lptr = '\0'; LineLength = lptr - Line; if (DoMassageOutput) MassageOutputLine (Line, &PreBlankLineCount, &PostBlankLineCount); else { /* replace all of the substituted symbol characters */ for (lptr = Line; *lptr; lptr++) if (*lptr == INTERIM_NON_ASCII_CHAR) *lptr = FINAL_NON_ASCII_CHAR; } if (PreBlankLineCount) { if (DoMassageOutput) { /* ensure multitudes of blank lines are not output */ if (PreBlankLineCount > MAX_CONSECUTIVE_BLANK_LINES) PreBlankLineCount = MAX_CONSECUTIVE_BLANK_LINES; /* absorb any page-leading blank lines, output others */ if (LinesOutputToClient) while (PreBlankLineCount--) { TBW += fprintf (stdout, "\n"); LinesOutputToClient++; } else; } else { /* when not massaging the text output all blank lines */ while (PreBlankLineCount--) { TBW += fprintf (stdout, "\n"); LinesOutputToClient++; } } PreBlankLineCount = 0; } TBW += fprintf (stdout, "%s", Line); LinesOutputToClient++; /* post-blank-lines are only created by the massage function */ if (PostBlankLineCount) { PreBlankLineCount = PostBlankLineCount; PostBlankLineCount = 0; } } } if (!DoMassageOutput) while (PreBlankLineCount--) { TBW += fprintf (stdout, "\n"); LinesOutputToClient++; } } /*****************************************************************************/ /* "Massage" the string of characters constructed from the matrix of 'OutCharStruct's before it is output to the client. This "massage" attempts to make certain corrections to enhance the layout of the text, these are clearly indicated and described within the function. */ MassageOutputLine ( char *Line, int *PreBlankLineCountPtr, int *PostBlankLineCountPtr ) { static char LastCharOfPreviousLine = '\0'; char *cptr, *lptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MassageOutputLine() |%s|\n", Line); if (!Line) { /* initialize */ LastCharOfPreviousLine = '\0'; return; } /**************************/ /* blank line suppression */ /**************************/ if (*PreBlankLineCountPtr == 1) { /* There is one blank line immediately before this line. Check to see if this line begins with a lower-case alphabetic. If it does then it is probable (though not certain) that the blank line was inserted by bit-to-character mapping inconsistancy. If it is lower-case then absorb the blank line. Step over tags. */ lptr = Line; while (*lptr == ' ') lptr++; while (*lptr == '<') { while (*lptr && *lptr != '>') lptr++; if (*lptr) lptr++; while (*lptr == ' ') lptr++; } if (*lptr == '(') *PreBlankLineCountPtr = 0; else if (islower(*lptr) && (!ispunct(LastCharOfPreviousLine) || LastCharOfPreviousLine == '-' || LastCharOfPreviousLine == '_' || LastCharOfPreviousLine == ',' || LastCharOfPreviousLine == ';')) *PreBlankLineCountPtr = 0; } /*******************************/ /* bulleted and numbered lists */ /*******************************/ for (cptr = lptr = Line; *lptr; lptr++) { if (cptr == Line && *lptr == NON_SUPPORTED_CHAR) { /* Symbol characters tend to be used for the "bullets" in lists. In this case they would be the first character on the line and followed by a space character. Ensure that the line is preceded by at least one blank line for "good" spacing. If 'cptr' equals the start of the line then no non-space characters have been encountered. */ if (cptr == Line && *(lptr+1) == ' ' && !*PreBlankLineCountPtr) *PreBlankLineCountPtr += 1; *lptr = FINAL_NON_ASCII_CHAR; } else if (cptr == Line && isdigit(*lptr)) { /* Numbered lists and headings (at least section headings begin a line with (for example) "1. ", "1.1 ", etc. Detect this and ensure the line is preceded by at least one blank line for "good" spacing. If 'cptr' equals the start of the line then no non-space characters have been encountered. */ while (isdigit(*lptr)) lptr++; if (*lptr == '.') { while (isdigit(*lptr) || *lptr == '.') lptr++; if (*lptr == ' ') *PreBlankLineCountPtr += 1; } } /* note the presence of a non-space character */ if (*lptr != ' ') cptr = lptr + 1; } /***********************************************/ /* take note of the last character on the line */ /***********************************************/ cptr = lptr = Line; while (*lptr) if (*lptr != ' ' && *lptr != '\n') cptr = lptr++; else lptr++; LastCharOfPreviousLine = *cptr; } /*****************************************************************************/ /* Creates a "page" in-memory. The page comprises 1..n of the structure 'OutCharStruct'. The macro OUT_CHAR_AT() allows this linear array of structures to be addressed with a line/column value as if it was a matrix. This essentially allows it to be treated as a matrix corresponding to a page of 'MAX_OUTLINE_LENGTH' characters wide and 'NewLinesOnPage' length. To increase efficiency, more than the requested 'NewLinesOnPage' can be created, reducing the number of dynamic memory (re)allocations required. */ PageSize (int NewLinesOnPage) { int ccnt, status, Count, BytesToAllocate; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "PageSize() %d %d/%d\n", LastLineOnPage, LinesOnPage, NewLinesOnPage); if (NewLinesOnPage < 0) { LastLineOnPage = 0; if (!LinesOnPage) return; if (OutPtr) { for (Count = 0; Count <= LinesOnPage; Count++) { if (Debug) fprintf (stdout, "Count: %d\n", Count); ocptr = OUT_CHAR_AT(Count,0); for (ccnt = 0; ccnt < MAX_OUTLINE_LENGTH; ccnt++) { if (ocptr->htptr) free (ocptr->htptr); ocptr++; } } } LinesOnPage = 0; if (OutPtr) free (OutPtr); OutPtr = NULL; return; } if (NewLinesOnPage <= LinesOnPage && OutPtr) return; /* If the number of lines on the page exceeds a certain threshold assume its going to be a large page with many lines and pre-allocate some. If it looks like being a very big page, increase preallocation. */ if (LinesOnPage > LINES_ON_PAGE_PREALLOCATE * 30) NewLinesOnPage += LINES_ON_PAGE_PREALLOCATE * 10; else if (LinesOnPage > LINES_ON_PAGE_PREALLOCATE * 15) NewLinesOnPage += LINES_ON_PAGE_PREALLOCATE * 5; else if (LinesOnPage > LINES_ON_PAGE_PREALLOCATE * 5) NewLinesOnPage += LINES_ON_PAGE_PREALLOCATE * 2; else NewLinesOnPage += LINES_ON_PAGE_PREALLOCATE; BytesToAllocate = (NewLinesOnPage+1)*MAX_OUTLINE_LENGTH*sizeof(struct OutCharStruct); if (!OutPtr) OutPtr = calloc (1, BytesToAllocate); else OutPtr = realloc (OutPtr, BytesToAllocate); if (!OutPtr) { char String [256]; status = vaxc$errno; sprintf (String, ErrorCallocBytes, (NewLinesOnPage+1)*MAX_OUTLINE_LENGTH*sizeof(struct OutCharStruct)); CgiLibResponseError (FI_LI, status, String); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } DynamicMemoryAllocated += BytesToAllocate; /* scan through all added "lines" */ if (!LinesOnPage) LinesOnPage = -1; while (LinesOnPage < NewLinesOnPage) { LinesOnPage++; ocptr = OUT_CHAR_AT(LinesOnPage,0); /* scan through all "characters" on that "line" */ for (ccnt = 0; ccnt < MAX_OUTLINE_LENGTH; ccnt++) { /* put a space into the character member, NULL the HTML pointer */ ocptr->c = ' '; ocptr->htptr = NULL; ocptr++; } } } /*****************************************************************************/ /* Dynamically allocate memory for a string. If there is no existing string then allocate the specified size. If there is an existing string pointed to allocate enough for the existing string plus the specified size. Return a pointer to the dynamically allocated string. */ char* HtmlString ( char *HtmlStringPtr, int StringSize ) { int status; /*********/ /* begin */ /*********/ if (!HtmlStringPtr) { StringSize++; if (Debug) fprintf (stdout, "HtmlString() NULL %d\n", StringSize); StringSize = ((StringSize / MIN_HTML_STRING_ALLOC) + 1) * MIN_HTML_STRING_ALLOC; if (Debug) fprintf (stdout, "StringSize: %d\n", StringSize); HtmlStringPtr = calloc (1, StringSize); *HtmlStringPtr = '\0'; } else { StringSize += strlen(HtmlStringPtr); if (Debug) fprintf (stdout, "HtmlString() |%s| %d\n", HtmlStringPtr, StringSize); StringSize = ((StringSize / MIN_HTML_STRING_ALLOC) + 1) * MIN_HTML_STRING_ALLOC; if (Debug) fprintf (stdout, "StringSize: %d\n", StringSize); HtmlStringPtr = realloc (HtmlStringPtr, StringSize); } if (!HtmlStringPtr) { char String [256]; status = vaxc$errno; sprintf (String, ErrorCallocBytes, StringSize); CgiLibResponseError (FI_LI, status, String); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } DynamicMemoryAllocated += StringSize; return (HtmlStringPtr); } /*****************************************************************************/ /* This function comes from original code, so I'm a bit hazy on its functionality. */ ProcessChunk () { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessChunk()\n"); if (DebugChunks) { fprintf (stdout, "ChunkType: %d\n", CurrentChunkType); fprintf (stdout, "ChunkLength: %ld\n", CurrentChunkLength); } if (CurrentChunkType == 3) { /*****************/ /* document text */ /*****************/ ProcessDocument (); return; } else if (CurrentChunkType == 4) { /***********************************/ /* contents, tables, figure, index */ /***********************************/ ProcessContentsEtc(); return; } else { /***********/ /* unknown */ /***********/ InsertComment (GlobalHorizontal, GlobalVertical, "Sorry. HyperReader does not recognize the document format.", __LINE__); ChunkPtr += CurrentChunkLength; return; } } /*****************************************************************************/ /* Process a series of sections of the document body. These sections can comprise text, graphics, figures, etc. */ ProcessDocument () { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessDocument()\n"); PreviousChunk = *(long*)(ChunkBufferPtr+6); SectionsInChunk = *(long*)(ChunkBufferPtr+14); PreviousTopicChunk = *(long*)(ChunkBufferPtr+18); NextTopicChunk = *(long*)(ChunkBufferPtr+22); if (DebugChunks) { DumpLongWords ("DOCUMENT BODY:", ChunkPtr+2, 6); fprintf (stdout, "Chunk: %d\n", CurrentChunk); fprintf (stdout, "ChunkType: %d\n", CurrentChunkType); fprintf (stdout, "ChunkLength: %d\n", CurrentChunkLength); fprintf (stdout, "PreviousChunk: %d\n", PreviousChunk); fprintf (stdout, "SectionsInChunk: %d\n", SectionsInChunk); fprintf (stdout, "PreviousTopicChunk %d\n", PreviousTopicChunk); fprintf (stdout, "NextTopicChunk: %d\n", NextTopicChunk); } ChunkPtr = ChunkBufferPtr + 26; while (ChunkPtr < EndChunkPtr) ProcessSection (); } /*****************************************************************************/ /* Process a single section of the document body. A section can comprise text, graphics, figures, etc. */ ProcessSection () { unsigned long SectionHorizOffset, SectionHorizUnits, SectionLength, SectionSubType, SectionVertOffset, SectionVertUnits; unsigned short SectionType; unsigned char *EndSectionChunkPtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessSection()\n"); SectionType = *(unsigned short*)ChunkPtr; SectionSubType = *(unsigned long*)(ChunkPtr+6); SectionLength = *(unsigned long*)(ChunkPtr+2); SectionHorizOffset = *(unsigned long*)(ChunkPtr+18); SectionVertOffset = *(unsigned long*)(ChunkPtr+22); SectionHorizUnits = *(unsigned long*)(ChunkPtr+26); SectionVertUnits = *(unsigned long*)(ChunkPtr+30); EndSectionChunkPtr = ChunkPtr + SectionLength; if (Debug) { fprintf (stdout, "\nSectionType: %d\n", SectionType); fprintf (stdout, "SectionSubType: %d\n", SectionSubType); fprintf (stdout, "SectionLength: %d\n", SectionLength); fprintf (stdout, "SectionHorizOffset: %d\n", SectionHorizOffset); fprintf (stdout, "SectionVertOffset: %d\n", SectionVertOffset); fprintf (stdout, "SectionHorizUnits: %d\n", SectionHorizUnits); fprintf (stdout, "SectionVertUnits: %d\n", SectionVertUnits); } if (SectionType == 18 || SectionType == 19) { /********************/ /* multi-functional */ /********************/ if (SectionType == 18) { /* adjust the vertical origin for all parts in this section */ GlobalVertical = EndGlobalVertical; EndGlobalVertical += SectionVertUnits; } if (DebugSections) { DumpLongWords ("SECTION 18:", ChunkPtr+2, 10); fprintf (stdout, "ChunkPtr: %d\n", ChunkPtr); fprintf (stdout, "next ChunkPtr %d\n", ChunkPtr+SectionLength); } if (SectionSubType == 2) { /********/ /* text */ /********/ ChunkPtr += 42; while (ChunkPtr < EndSectionChunkPtr) if (!ProcessTextSection (GlobalHorizontal, GlobalVertical, NULL, NULL)) break; ChunkPtr = EndSectionChunkPtr; } else if (SectionSubType == 3) { /****************************************/ /* some sort of X line-drawing language */ /****************************************/ /* fprintf (stdout, "\n%s\n", ChunkPtr+42); DumpBytes ("Section 18 SubType 3", ChunkPtr+42, SectionLength-42); */ InsertComment (GlobalHorizontal, GlobalVertical, "Sorry. HyperReader cannot reproduce this figure.", __LINE__); ChunkPtr = EndSectionChunkPtr; } else if (SectionSubType == 4 || SectionSubType == 5) { /****************/ /* bitmap image */ /****************/ PlaceImage (GlobalHorizontal+SectionHorizOffset, GlobalVertical); } else { /***********/ /* unknown */ /***********/ InsertComment (GlobalHorizontal, GlobalVertical, "Sorry. HyperReader does not recognize the document format.", __LINE__); ChunkPtr = EndSectionChunkPtr; } return; } else if (SectionType == 20 || SectionType == 21) { /***********/ /* hotspot */ /***********/ ProcessHotSpot (GlobalHorizontal, GlobalVertical); return; } else if (SectionType == 22 || SectionType == 23) { /*******************/ /* graphic overlay */ /*******************/ ProcessGraphicOverlay (); return; } else { /***********/ /* unknown */ /***********/ InsertComment (GlobalHorizontal, GlobalVertical, "Sorry. HyperReader does not recognize the document format.", __LINE__); ChunkPtr = EndSectionChunkPtr; return; } } /*****************************************************************************/ /* Process a chunk containing a table of contents, tables, figures, or an index. Strictly control the line and column placement according to what we want, don't pay attention to any information associated with the text. */ ProcessContentsEtc () { int Count; unsigned short DataType; unsigned long DataLength, HotChunkNumber, Column, Line = 0, Horizontal, Vertical, Length, Width; char NestedLevel; unsigned char *NextChunkPtr, *chptr; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessContentsEtc()\n"); EnhanceText = false; ChunkPtr = ChunkBufferPtr + 0x06; while (ChunkPtr < EndChunkPtr) { DataType = *(unsigned short*)ChunkPtr; DataLength = *(unsigned long*)(ChunkPtr+2); NestedLevel = *(ChunkPtr+6); if (DebugChunks) { DumpLongWords ("CONTENTS:", ChunkPtr, 5); fprintf (stdout, "DataType: %d\n", DataType); fprintf (stdout, "DataLength: %d\n", DataLength); fprintf (stdout, "NestedLevel: %d\n", NestedLevel); } NextChunkPtr = ChunkPtr + DataLength; chptr = ChunkPtr + 20; while (*chptr == 2 || *chptr == 3) chptr += (unsigned char)*(chptr+1); while (*chptr++); /* I'm buggered if I know how this next statement decides! */ if (chptr == NextChunkPtr-4) HotChunkNumber = *(unsigned long*)chptr; else HotChunkNumber = 0; ChunkPtr += 20; /* on all but the first line add a blank line before level 1 items */ if (Line++) if (NestedLevel == 1) Line++; /* indent the lines of text according to the nesting level */ Column = 0; for (Count = NestedLevel * 2; Count; Count--) Column++; while (*ChunkPtr == 3 || *ChunkPtr == 2) { Column = PlaceText (Line, Column); Column++; } Column--; if (HotChunkNumber) { Horizontal = SkipContentsItemNumber (Line) * COLUMN_POSITIONING_UNITS; Vertical = Line * LINE_POSITIONING_UNITS; Length = (Column * COLUMN_POSITIONING_UNITS) - Horizontal; Width = LINE_POSITIONING_UNITS; PlaceHotSpot (0, 0, Horizontal, Vertical, Length, Width, HotChunkNumber); } /* scan past null-terminated text */ while (*ChunkPtr++); ChunkPtr = NextChunkPtr; } } /*****************************************************************************/ /* Return the column position of the first character following an item number such as "1.", "1.2", "1.2.3", "1-2", "1-2-3", "A.", "A.1", "A-1", "REF-1", etc. If the line does not begin with such a string then return the column position of the first non-space character. This allows HTML links to start on the description rather than item number of a contents line. */ int SkipContentsItemNumber (int Line) { int Column, FirstNonSpaceColumn; char ccnt; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "SkipContentsItemNumber()\n"); Column = 0; ocptr = OUT_CHAR_AT(Line,Column); /* skip leading white-space */ while (Column < MAX_OUTLINE_LENGTH && ocptr->c == ' ') { ocptr++; Column++; } if (Column >= MAX_OUTLINE_LENGTH) return (0); FirstNonSpaceColumn = Column; if (isdigit(ocptr->c)) { /* Line starts with a digit. Scan across anything like a heading number, e.g. "1", "1.1", "1-1", etc. */ while (Column < MAX_OUTLINE_LENGTH && (isalnum(ocptr->c) || ocptr->c == '.' || ocptr->c == '-')) { ocptr++; Column++; } if (Column >= MAX_OUTLINE_LENGTH) return (FirstNonSpaceColumn); else; } else if (isalpha(ocptr->c)) { /* Line starts with an alphabetic. Check if its a single alphabetic such an Appendix (e.g. "A Topic"), or an alphabetic with sub-numbering (e.g. "A-1 Topic", "REF-1 Topic"). */ ocptr++; Column++; if (Column >= MAX_OUTLINE_LENGTH) return (FirstNonSpaceColumn); /* so far its been a single alphabetic, check if followed by a space */ if (ocptr->c != ' ') { /* not a space, step over any contiguous alphas or digits */ while (Column < MAX_OUTLINE_LENGTH && (isalpha(ocptr->c) || isdigit(ocptr->c))) { ocptr++; Column++; } if (Column >= MAX_OUTLINE_LENGTH) return (FirstNonSpaceColumn); /* if its not one of these two then its not a contents-"number" */ if (!(ocptr->c == '.' || ocptr->c == '-')) return (FirstNonSpaceColumn); /* step over any following alphas, digits, "." or "-" */ while (Column < MAX_OUTLINE_LENGTH && (isalpha(ocptr->c) || isdigit(ocptr->c) || ocptr->c == '.' || ocptr->c == '-')) { ocptr++; Column++; } if (Column >= MAX_OUTLINE_LENGTH) return (FirstNonSpaceColumn); else; } else; } else return (FirstNonSpaceColumn); /* scan across the intervening white-space */ while (Column < MAX_OUTLINE_LENGTH && ocptr->c == ' ') { ocptr++; Column++; } if (Column >= MAX_OUTLINE_LENGTH) return (FirstNonSpaceColumn); return (Column); } /*****************************************************************************/ /* Process a section of document text. Locate it from the horizontal and vertical origins provided. */ ProcessTextSection ( unsigned long H_Origin, unsigned long V_Origin ) { int Line, Column; long DataLength; unsigned short Horizontal; unsigned short Vertical; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessTextSection() H_Origin: %d V_Origin: %d\n", H_Origin, V_Origin); if (*ChunkPtr == 1) { /***********/ /* graphic */ /***********/ ProcessGraphicLine (); return (true); } else if (*ChunkPtr == 2 || *ChunkPtr == 3) { /***************/ /* actual text */ /***************/ Horizontal = *(unsigned short*)(ChunkPtr+2); Vertical = *(unsigned short*)(ChunkPtr+4); Line = (V_Origin + Vertical) / LINE_POSITIONING_UNITS; Column = (H_Origin + Horizontal) / COLUMN_POSITIONING_UNITS; PlaceText (Line, Column); return (true); } else { /***********/ /* unknown */ /***********/ InsertComment (H_Origin, V_Origin, "Sorry. HyperReader does not recognize the document format.", __LINE__); return (false); } } /*****************************************************************************/ /* These seem to do things like draw boxes around SDML () text and horizontal lines (e.g. the lines delimiting s) etc. It looks like a single line drawing functionality. These are ignored for HTML. */ ProcessGraphicLine () { long DataLength; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessGraphicLine()\n"); DataLength = *(unsigned char*)(ChunkPtr+1); if (DebugGraphics) { fprintf (stdout,"DataLength: %08.08X (%d)\n", DataLength, DataLength); DumpLongWords ("GRAPHIC LINE:", ChunkPtr+2, (DataLength-2)/4); } ChunkPtr += DataLength; } /*****************************************************************************/ /* These seem to do things like place a block of shading (stipple) over a section of text. It must be some sort of graphical "block" functionality. The data structure appears similar to figures (images). These are ignored for HTML. */ ProcessGraphicOverlay () { long DataLength; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessGraphicOverlay()\n"); DataLength = *(long*)(ChunkPtr+2); if (DebugGraphics) { fprintf (stdout,"DataLength: %08.08X (%d)\n", DataLength, DataLength); DumpLongWords ("GRAPHIC OVERLAY:", ChunkPtr+2, (DataLength-2)/4); } ChunkPtr += DataLength; } /*****************************************************************************/ /* Create a hotspot by placing HTML link(s) around text in the specified location(s). Handles single and two-line-spanning hotspots. */ ProcessHotSpot ( unsigned long H_Origin, unsigned long V_Origin ) { unsigned char *chptr; unsigned long DataLength, Horizontal, Vertical, Length, Width, HotChunkNumber, GraphicCount, SpanningDataLength; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ProcessHotSpot()\n"); DataLength = *(unsigned long*)(ChunkPtr+2); HotChunkNumber = *(unsigned long*)(ChunkPtr+34); SpanningDataLength = *(unsigned long*)(ChunkPtr+38); if (DebugHotspots) { DumpLongWords ("HOTSPOT:", ChunkPtr+2, 10); fprintf (stdout,"DataLength: %d\n", DataLength); fprintf (stdout,"HotChunkNumber: %d\n", HotChunkNumber); fprintf (stdout,"SpanningDataLength: %d\n", SpanningDataLength); if (SpanningDataLength) DumpLongWords ("SPANNING HOTSPOT:", ChunkPtr+42, SpanningDataLength / sizeof(long)); } if (SpanningDataLength) { /* "Spanning hotspots" are generated when a hotspot spans more than one line. Bookreader seems to draw a series of graphic lines starting with the top text line, bottom left, up and around and then down to the next line, and around that text. Emulate this by drawing two hotspots. If the hotspot spans three lines? Tough! */ chptr = ChunkPtr + 42; GraphicCount = *(unsigned long*)chptr; if (DebugHotspots) fprintf (stdout,"GraphicCount: %d\n", GraphicCount); chptr += 4; Horizontal = *(unsigned long*)(chptr+8); Vertical = *(unsigned long*)(chptr+12); Length = *(unsigned long*)(chptr+16) - Horizontal; Width = *(unsigned long*)(chptr+4) - Vertical; PlaceHotSpot (H_Origin, V_Origin, Horizontal, Vertical, Length, Width, HotChunkNumber); chptr += 32; Horizontal = *(unsigned long*)(chptr+16); Vertical = *(unsigned long*)(chptr+28); Length = *(unsigned long*)chptr - Horizontal; Width = *(unsigned long*)(chptr+20) - Vertical; PlaceHotSpot (H_Origin, V_Origin, Horizontal, Vertical, Length, Width, HotChunkNumber); } else { /* single line hotspot */ Horizontal = *(unsigned long*)(ChunkPtr+18); Vertical = *(unsigned long*)(ChunkPtr+22); Length = *(unsigned long*)(ChunkPtr+26); Width = *(unsigned long*)(ChunkPtr+30); PlaceHotSpot (H_Origin, V_Origin, Horizontal, Vertical, Length, Width, HotChunkNumber); } ChunkPtr += DataLength; } /*****************************************************************************/ /* Create a hotspot by placing the start of an HTML link at the line and column represented by horizontal/vertical, and then placing the end of the link at horizontal+length/vertical+width. */ PlaceHotSpot ( unsigned long H_Origin, unsigned long V_Origin, unsigned long Horizontal, unsigned long Vertical, unsigned long Length, unsigned long Width, unsigned long HotChunkNumber ) { int ccnt, StringLength, Line, Column; char String [1024]; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "PlaceHotSpot()\n"); if (DebugHotspots) { fprintf (stdout,"H_Origin: %d\n", H_Origin); fprintf (stdout,"V_Origin: %d\n", V_Origin); fprintf (stdout,"Horizontal: %d\n", Horizontal); fprintf (stdout,"Vertical: %d\n", Vertical); fprintf (stdout,"Length: %d\n", Length); fprintf (stdout,"Width: %d\n", Width); fprintf (stdout,"HotChunkNumber: %d\n", HotChunkNumber); } /* Attempt to place this on the line containing the text to be "hotspot"ed. 'Vert' is the vertical location, 'Width' must be the the width of the hotspot area. Three-quarters down the 'Width' value is used to calculate the line position. BTW; seems to work well ... most of the time! Use bit-wise shifting to enhance the speed of the divisions: excerpt equals: "(((Vert + (Width / 2) + (Width / 4))" */ Line = (V_Origin + Vertical + (Width >> 1) + (Width >> 2)) / LINE_POSITIONING_UNITS; if (Line > LinesOnPage) PageSize (Line); if (Line > LastLineOnPage) LastLineOnPage = Line; /* If this is a totally blank line then its probably an artifact of the mapping process. Attempt a correction by stepping to the next line. */ for (;;) { ocptr = OUT_CHAR_AT(Line,0); for (ccnt = 0; ccnt < MAX_OUTLINE_LENGTH; ccnt++) { if (ocptr->c != ' ') break; ocptr++; } if (ccnt < MAX_OUTLINE_LENGTH || Line >= LinesOnPage) break; Line++; } /* Calculate the hotspot start column. If it lands on a space then scan forward to the first non-space character encountered. */ Column = (H_Origin + Horizontal) / COLUMN_POSITIONING_UNITS; if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; ocptr = OUT_CHAR_AT(Line,Column); for (ccnt = Column; ccnt < MAX_OUTLINE_LENGTH; ccnt++) { if (ocptr->c != ' ') break; ocptr++; } if (ccnt < MAX_OUTLINE_LENGTH) Column = ccnt; /* place the start of hotspot into the text */ StringLength = sprintf (String, "", CgiScriptNamePtr, UrlEncodedCgiPathInfoPtr, BookSpecifiedByFile, (int)SectionChunkNumbersArrayPtr[HotChunkNumber], UrlEncReferer, UrlEncTitle, UrlDoMassage); ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, StringLength); strcat (ocptr->htptr, String); /* Calculate the hotspot end column. If it lands on a space then scan backward to the first non-space character encountered. */ if (Column + Length / COLUMN_POSITIONING_UNITS <= Column) { Column++; if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; } else { Column += (Length / COLUMN_POSITIONING_UNITS) - 1; if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; ocptr = OUT_CHAR_AT(Line,Column); for (ccnt = Column; ccnt > 0; ccnt--) { if (ocptr->c != ' ') break; ocptr--; } if (ccnt > 0) Column = ccnt + 1; if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; } /* place the end of hotspot into the text */ ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, 4); strcat (ocptr->htptr, ""); } /*****************************************************************************/ /* Place a "string" of text characters into the "page". With Bookreader documents a single "string" of characters NEVER spans multiple lines. The "string" is pointed to by 'ChunkPtr'. */ int PlaceText ( int Line, int Column ) { static int CallCount = 0; int idx, count, FontNumber; unsigned char TextType; unsigned char Length; unsigned short Horizontal; unsigned short Vertical; unsigned short Unknown; unsigned char *Text; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Line > LinesOnPage) PageSize (Line); if (Line > LastLineOnPage) LastLineOnPage = Line; if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; TextType = *ChunkPtr; Length = *(ChunkPtr+1); FontNumber = *(ChunkPtr+6); Unknown = *(unsigned short*)(ChunkPtr+7); if (DebugText) { fprintf (stdout, "\nPlaceText()\n\ Len: %d ?: %d Line: %d Column: %d Font: %d Bld: %d It: %d Sym: %d\n", Length, Unknown, Line, Column, FontNumber, FontArrayPtr[FontNumber].FontBold, FontArrayPtr[FontNumber].FontItalic, FontArrayPtr[FontNumber].FontUnsupported); } /********************/ /* text enhancement */ /********************/ if (EnhanceText) { if (FontArrayPtr[FontNumber].FontBold) { ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, 3); strcat (ocptr->htptr, ""); } else if (FontArrayPtr[FontNumber].FontItalic) { ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, 3); strcat (ocptr->htptr, ""); } } /*********************************************/ /* put the characters into the output buffer */ /*********************************************/ Text = ChunkPtr+9; idx = 0; while (idx < Length-8) { count = (int)Text[idx++]; while (count-- > 0) { if (Column < MAX_OUTLINE_LENGTH) { if (FontArrayPtr[FontNumber].FontUnsupported || Text[idx] < 0x20 || (Text[idx] > 0x7f && Text[idx] < 0xa0)) OUT_CHAR_AT(Line,Column)->c = NON_SUPPORTED_CHAR; else OUT_CHAR_AT(Line,Column)->c = Text[idx]; if (DebugText) { /* fprintf (stdout, "%c", OUT_CHAR_AT(Line,Column)->c); */ fprintf (stdout, "%c(%02.02x)[%d] ", OUT_CHAR_AT(Line,Column)->c, OUT_CHAR_AT(Line,Column)->c, Column); } } Column++; idx++; } if (idx < Length-9) Column += (Text[idx] / COLUMN_POSITIONING_UNITS) + 1; idx++; } if (DebugText) fputc ('\n', stdout); if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; /************************/ /* end text enhancement */ /************************/ if (EnhanceText) { if (FontArrayPtr[FontNumber].FontBold) { ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, 4); strcat (ocptr->htptr, ""); } else if (FontArrayPtr[FontNumber].FontItalic) { ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, 4); strcat (ocptr->htptr, ""); } } ChunkPtr += Length; return (Column); } /*****************************************************************************/ /* Put a link into the text to retrieve the image. */ PlaceImage ( unsigned long H_Origin, unsigned long V_Origin ) { int ccnt, Length, Line, Column; long DataLength, Horizontal, Offset, Vertical; char String [2048]; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "PlaceImage() H_Origin: %d V_Origin: %d\n", H_Origin, V_Origin); DataLength = *(long*)(ChunkPtr+2); Horizontal = *(long*)(ChunkPtr+18); Vertical = *(long*)(ChunkPtr+22); if (Debug) { DumpLongWords ("FIGURE:", ChunkPtr+2, 12); fprintf (stdout,"DataLength : %d\n", DataLength); fprintf (stdout,"Horizontal : %d\n", Horizontal); fprintf (stdout,"Vertical : %d\n", Vertical); } Line = (V_Origin + Vertical) / LINE_POSITIONING_UNITS; if (!Line) Line = 1; if (Line > LinesOnPage) PageSize (Line); if (Line > LastLineOnPage) LastLineOnPage = Line; /* Without the ability to flow text around an image (which current browsers lack) and we're on a line containing text attempt to step back to a blank line. */ for (;;) { ocptr = OUT_CHAR_AT(Line,0); for (ccnt = 0; ccnt < MAX_OUTLINE_LENGTH; ccnt++) { if (ocptr->c != ' ') break; ocptr++; } if (ccnt >= MAX_OUTLINE_LENGTH || Line <= 1) break; Line--; } Column = (H_Origin + Horizontal) / COLUMN_POSITIONING_UNITS; if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; if (DebugFigures) fprintf (stdout,"Line: %d Column: %d\n", Line, Column); /* The four numbers in the URI are: (note that parts one and two constitute the RMS RFA of the record) 1. Virtual Block Number of the start of the part 2. starting byte position in the VBN 3. total length of the chunk 4. offset from start of chunk the image data begins This will allow the image to be retrieved very quickly by merely opening the file, reading the part (one or more records) via the RFA and jumping in to process the image data from the offset. */ Offset = ChunkPtr - ChunkBufferPtr; Length = sprintf (String, "\"<image>\"\n", CgiScriptNamePtr, UrlEncodedCgiPathInfoPtr, BookSpecifiedByFile, ChunkArrayPtr[GetChunkNumber].VBN, ChunkArrayPtr[GetChunkNumber].VBNbyte, ChunkArrayPtr[GetChunkNumber].Length, Offset); ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, Length); strcat (ocptr->htptr, String); ChunkPtr += DataLength; } /*****************************************************************************/ /* Read the font information from the respective part of the Bookreader file. Font 'numbers' represent the font being specified within bookreader text. Note the point value of each font, and which font 'number' is bold, italic, symbol, etc. This information is used when mapping the text. */ GetFonts () { int count, FontCount; short Short1, Short2, Short3, FontNumber; char *fptr, *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetFonts()\n"); GetChunk (&FontChunk); fptr = (char*)ChunkBufferPtr+6; for (FontCount = 0; FontCount < NumberOfFonts; FontCount++) { Short1 = *(short*)fptr; Short2 = *(short*)(fptr+2); Short3 = *(short*)(fptr+4); FontNumber = *(short*)(fptr+6); if (DebugFonts) fprintf (stdout, "Font \ #%03d 0x%04.04x 0x%04.04x 0x%04.04x 0x%04.04x\n|%s|\n", FontNumber, Short1, Short2, Short3, FontNumber, fptr+8); FontArrayPtr[FontNumber].FontSize = 0; count = 0; cptr = (char*)fptr+8; while (*cptr && count < 8) if (*cptr++ == '-') count++; FontArrayPtr[FontNumber].FontSize = atoi (cptr) / 10; /* push the font name to upper case for easy strstr() below */ for (cptr = fptr+8; *cptr; cptr++) *cptr = toupper(*cptr); FontArrayPtr[FontNumber].FontBold = FontArrayPtr[FontNumber].FontItalic = FontArrayPtr[FontNumber].FontUnsupported = false; /* "*-interim dm-*" has some interesting graphics/symbol characters */ if (strstr (fptr+8, "INTERIM DM")) FontArrayPtr[FontNumber].FontUnsupported = true; else if (strstr (fptr+8, "SYMBOL")) FontArrayPtr[FontNumber].FontUnsupported = true; else if (strstr (fptr+8, "BOLD")) FontArrayPtr[FontNumber].FontBold = true; else if (strstr (fptr+8, "-I-")) FontArrayPtr[FontNumber].FontItalic = true; fptr += 8; while (*fptr) fptr++; fptr++; } } /*****************************************************************************/ /* This function comes from original code, so I'm a bit hazy on its functionality. */ GetSectionXref () { int idx; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetSectionXref()\n"); GetChunk (&SectionXrefChunk); cptr = (char*)ChunkBufferPtr + 0x06; for (idx = 0; idx < NumberOfSections; idx++) { SectionChunkNumbersArrayPtr[idx] = *(long*)cptr; /* if (DebugChunks) fprintf (stdout, "Content %d. 0x%08.08lx\n", idx, *(long*)cptr); */ cptr += sizeof(long); } } /*****************************************************************************/ /* This function comes from original code, so I'm a bit hazy on its functionality. */ GetFirstChunk () { int status, SegmentNameLength; short SegmentType, SegmentLength; long SegmentData1, SegmentData2, SegmentData3, SegmentChunkNumber; char *chptr, *SegmentNamePtr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetFirstChunk()\n"); CurrentSeek = 0; ChunkNumber = 0; FirstChunk.VBN = 1; FirstChunk.VBNbyte = 0; FirstChunk.Length = 1022; GetChunk (&FirstChunk); NumberOfChunks = *(long*)(ChunkBufferPtr+0x46); NumberOfSections = *(long*)(ChunkBufferPtr+0x4a); NumberOfFonts = *(long*)(ChunkBufferPtr+0x52); LastVBN = *(long*)(ChunkBufferPtr+0x5a); LastVBNbyte = *(short*)(ChunkBufferPtr+0x5e); LastVBNlen = *(short*)(ChunkBufferPtr+0x60); LastChunk.VBN = LastVBN; LastChunk.VBNbyte = LastVBNbyte; LastChunk.Length = LastVBNlen; strcpy (BookTitle, (char*)ChunkBufferPtr+0x7f); if (DebugChunks) { fprintf (stdout, "ChunkType: %04.04X (%d)\n", CurrentChunkType, CurrentChunkType); fprintf (stdout, "ChunkLength: %08.08X (%ld)\n", CurrentChunkLength, CurrentChunkLength); fprintf (stdout, "NumberOfChunks: %08.08X (%ld)\n", NumberOfChunks, NumberOfChunks); fprintf (stdout, "NumberOfSections: %08.08X (%ld)\n", NumberOfSections, NumberOfSections); fprintf (stdout, "NumberOfFonts: %08.08X (%ld)\n", NumberOfFonts, NumberOfFonts); fprintf (stdout, "LastVBN: %08.08X (%ld)\n", LastVBN, LastVBN); fprintf (stdout, "LastVBNbyte: %04.04X (%d)\n", (int)LastVBNbyte, (int)LastVBNbyte); fprintf (stdout, "LastVBNlen: %04.04X (%d)\n", (int)LastVBNlen, (int)LastVBNlen); fprintf (stdout, "BookTitle: |%s|\n", BookTitle); } ChunkArrayPtr = calloc (sizeof(struct ChunkDataStruct), NumberOfChunks); if (!ChunkArrayPtr) { status = vaxc$errno; CgiLibResponseError (FI_LI, status, ErrorCalloc); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } SectionChunkNumbersArrayPtr = calloc (sizeof(int), NumberOfSections); if (!SectionChunkNumbersArrayPtr) { status = vaxc$errno; CgiLibResponseError (FI_LI, status, ErrorCalloc); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } FontArrayPtr = calloc (sizeof(struct FontStruct), NumberOfFonts); if (!FontArrayPtr) { status = vaxc$errno; CgiLibResponseError (FI_LI, status, ErrorCalloc); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } chptr = (char*)ChunkBufferPtr + 0x128; while (*(short*)chptr) { SegmentType = *(short*)chptr; SegmentLength = *(short*)(chptr+2); SegmentData1 = *(long*)(chptr+4); SegmentData2 = *(long*)(chptr+10); SegmentData3 = *(long*)(chptr+11); SegmentChunkNumber = *(long*)(chptr+15); SegmentNamePtr = chptr+20; if (Debug && SegmentType == 12) { fprintf (stdout, "Segment; Type: %04.04X Length: %04.04X SegmentNamePtr: |%s|\n\ Data1: %08.08X Data2: %08.08X Data3: %08.08X ChunkNumber: %08.08X\n", SegmentType, SegmentLength, SegmentNamePtr, SegmentData1, SegmentData2, SegmentData3, SegmentChunkNumber); } if (SegmentType == 12) { if (++BookDivisionCount >= MAX_DIVISIONS) { CgiLibResponseError (FI_LI, 0, ErrorTooManyDivisions); return; } BookDivision[BookDivisionCount].DescriptionPtr = calloc (1, SegmentNameLength = strlen(SegmentNamePtr) + 1); if (!BookDivision[BookDivisionCount].DescriptionPtr) { status = vaxc$errno; CgiLibResponseError (FI_LI, status, ErrorCalloc); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } memcpy (BookDivision[BookDivisionCount].DescriptionPtr, SegmentNamePtr, SegmentNameLength); BookDivision[BookDivisionCount].ChunkNumber = SegmentChunkNumber; } chptr += SegmentLength; } } /*****************************************************************************/ /* The last "chunk" contains a series of ten-byte structures containing the Record File Address (RFA) of each "chunk" (the VBN and byte of VBN), and the total length of the "chunk", which can span individual records. */ GetLastChunk () { int count, SeekPos; short ChunkType; char *chptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetLastChunk()\n"); /* last record/part is the table of parts */ GetChunk (&LastChunk); if (DebugChunks) fprintf (stdout, "(Xref = 2, Headings = 3, Symbols = 4, Fonts = 5)\n\ Chunks \ num VBN VBNbyte len\n"); chptr = (char*)ChunkBufferPtr + 6; for (count = 0; count < NumberOfChunks; count++) { ChunkArrayPtr[count].VBN = *(long*)chptr; ChunkArrayPtr[count].VBNbyte = *(short*)(chptr+4); ChunkArrayPtr[count].Length = *(long*)(chptr+6); chptr += 10; /* if (DebugChunks) { fprintf (stdout, "Chunk \ %3d 0x%08.08lx 0x%04.04x 0x%08.08lx\n", count, ChunkArrayPtr[count].VBN, (int)ChunkArrayPtr[count].VBNbyte, ChunkArrayPtr[count].Length); } */ } /* >>>>> THE FOLLOWING IS AN AREA FOR POSSIBLE IMPROVEMENT <<<<< */ for (count = 0; count < NumberOfChunks; count++) { GetChunk (&ChunkArrayPtr[count]); if (CurrentChunkType == 0x0006) { SectionXrefChunk.VBN = ChunkArrayPtr[count].VBN; SectionXrefChunk.VBNbyte = ChunkArrayPtr[count].VBNbyte; SectionXrefChunk.Length = ChunkArrayPtr[count].Length; } else if (CurrentChunkType == 0x0007) { SectionHeadingsChunk.VBN = ChunkArrayPtr[count].VBN; SectionHeadingsChunk.VBNbyte = ChunkArrayPtr[count].VBNbyte; SectionHeadingsChunk.Length = ChunkArrayPtr[count].Length; } else if (CurrentChunkType == 0x0009) { FontChunk.VBN = ChunkArrayPtr[count].VBN; FontChunk.VBNbyte = ChunkArrayPtr[count].VBNbyte; FontChunk.Length = ChunkArrayPtr[count].Length; break; } else if (CurrentChunkType == 0x000d) { SymbolsChunk.VBN = ChunkArrayPtr[count].VBN; SymbolsChunk.VBNbyte = ChunkArrayPtr[count].VBNbyte; SymbolsChunk.Length = ChunkArrayPtr[count].Length; } } } /*****************************************************************************/ /* A 'chunk' consist of 1..n bytes, read beginning a specified byte within a specified VBN of the Bookreader file. Dynamically allocate (or reallocate) space for the "chunk" buffer. The record structure is a little complex, and I haven't bothered to fathom it all out ... this works for HYPERREADER. If there is only one record in the chunk then it is straight-forward, read the record, end of story. If there are two or more records then ALL BUT THE LAST record have record-related data (not Bookreader information) in the LAST 10 BYTES and ALL in the FIRST 6 BYTES of each record. HYPERREADER concatenates all records making up a chunk and these 16 bytes information can be safely ignored. Instead of expensive memory copying of record data to adjust these offsets on subsequent reads, the last 10 bytes of the previous record are just written over and the first 6 bytes of the next record positioned to overwrite the 6 bytes prior to the last 10 of the previous record. These 6 bytes are buffered and restored. */ GetChunk (struct ChunkDataStruct *ChunkDataPtr) { int status, RecordCount = 0, ChunkBufferRemaining; unsigned char Buffer6 [6]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetChunk() VBN: %d VBNbyte %d Length: %d\n", ChunkDataPtr->VBN, ChunkDataPtr->VBNbyte, ChunkDataPtr->Length); /* allocate enough memory to buffer the entire "chunk" of the book */ if (Debug) fprintf (stdout, "PreviousChunkLength: %d ChunkDataPtr->Length: %d\n", PreviousChunkLength, ChunkDataPtr->Length); if (PreviousChunkLength < ChunkDataPtr->Length) { if (ChunkBufferPtr) free (ChunkBufferPtr); ChunkBufferPtr = calloc (1, PreviousChunkLength = ChunkDataPtr->Length + BookFileLongestRecordLength); if (!ChunkBufferPtr) { status = vaxc$errno; CgiLibResponseError (FI_LI, status, ErrorCalloc); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } } DynamicMemoryAllocated += ChunkDataPtr->Length; /* prepare for reading the record via Record File Address (RFA) */ BookFileRab.rab$l_rfa0 = ChunkDataPtr->VBN; BookFileRab.rab$w_rfa4 = ChunkDataPtr->VBNbyte; BookFileRab.rab$b_rac = RAB$C_RFA; BookFileRab.rab$l_ubf = (char*)ChunkBufferPtr; ChunkBufferRemaining = ChunkDataPtr->Length + BookFileLongestRecordLength; if (ChunkBufferRemaining <= BookFileLongestRecordLength) BookFileRab.rab$w_usz = ChunkBufferRemaining; else BookFileRab.rab$w_usz = BookFileLongestRecordLength; /* accumulate the total bytes read in global storage to be used later */ CurrentChunkLength = 0; for (;;) { if (Debug) fprintf (stdout, "ChunkBufferRemaining: %d BookFileRab.rab$w_usz: %d\n", ChunkBufferRemaining, BookFileRab.rab$w_usz); if (BookFileVarRec) status = sys$get (&BookFileRab, 0, 0); else status = GetRecord (&BookFileRab); if (VMSnok (status)) break; if (Debug) fprintf (stdout, "RecordCount: %d BookFileRab.rab$w_rsz: %d\n", RecordCount, BookFileRab.rab$w_rsz); TotalBytesRead += BookFileRab.rab$w_rsz; /* on record 2 ... last restore the overwritten 6 bytes */ if (RecordCount++) memcpy (BookFileRab.rab$l_ubf, Buffer6, 6); /* break if we've reached the requested chunk size */ if ((CurrentChunkLength += BookFileRab.rab$w_rsz) >= ChunkDataPtr->Length) break; /* Append any subsequent record to what has been read before. Ignore tha last 10 bytes of the record just read, and also overwrite the 6 bytes prior to that, buffering it first so that it may be restored afterwards. As this is not the last record reduce the accumulated chunk length by 10 + 6. */ BookFileRab.rab$l_ubf += BookFileRab.rab$w_rsz - 16; ChunkBufferRemaining -= BookFileRab.rab$w_rsz - 16; if (ChunkBufferRemaining <= BookFileLongestRecordLength) BookFileRab.rab$w_usz = ChunkBufferRemaining; else BookFileRab.rab$w_usz = BookFileLongestRecordLength; CurrentChunkLength -= 16; memcpy (Buffer6, BookFileRab.rab$l_ubf, 6); /* the first read is by RFA, subsequent reads are done sequentially */ BookFileRab.rab$b_rac = RAB$C_SEQ; } if (VMSnok (status)) { CgiLibResponseError (FI_LI, status, ErrorChunkRead); exit (status | STS$M_INHIB_MSG); } ChunkPtr = ChunkBufferPtr; EndChunkPtr = ChunkBufferPtr + CurrentChunkLength; CurrentChunkType = *(short*)ChunkBufferPtr; if (Debug) fprintf (stdout, "ChunkPtr: %d EndChunkPtr: %d CurrentChunkLength: %d\n", ChunkPtr, EndChunkPtr, CurrentChunkLength); if (DebugBytes) DumpBytes ("GETCHUNK:", ChunkBufferPtr, CurrentChunkLength); return (SS$_NORMAL); } /*****************************************************************************/ /* For file formats that are non-VAR, as are (often?) NFS-served books which are (always?) UDF (undefined), use this function to emulate VAR record format record 'get's from the file. Uses block IO and some jiggery-pokery with the RFA and previous RSZ fields as appropriate. Bit clunky but I guess that fits in well with the rest of the application. */ int GetRecord (struct RAB *rabptr) { int status, BlockCount, BlockNumber; unsigned long vbn; unsigned short byte, usz, rsz; char *ubf, *recptr; /* maximum buffer space needed will be 32767 plus two blocks */ char BlockBuffer [(512*64)+(512*2)]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetRecord() rac:%d usz:%d rsz:%d rfa0:%d rfa4:%d\n", rabptr->rab$b_rac, rabptr->rab$w_usz, rabptr->rab$w_rsz, rabptr->rab$l_rfa0, rabptr->rab$w_rfa4); rabptr->rab$w_usz = BookFileLongestRecordLength; if (rabptr->rab$b_rac != RAB$C_RFA) { /* calculate VBN/byte of the next record using previous record size */ vbn = rabptr->rab$w_rsz / 512; byte = rabptr->rab$w_rsz % 512; rabptr->rab$l_rfa0 += vbn; if (rabptr->rab$w_rfa4 + byte < 512) rabptr->rab$w_rfa4 += byte; else { rabptr->rab$l_rfa0++; rabptr->rab$w_rfa4 = byte - 512; } if (Debug) fprintf (stdout, "rfa0:%d rfa4:%d\n", rabptr->rab$l_rfa0, rabptr->rab$w_rfa4); } BlockNumber = rabptr->rab$l_rfa0; BlockCount = (rabptr->rab$w_usz / 512) + 2; if (Debug) fprintf (stdout, "bkt:%d cnt:%d\n", BlockNumber, BlockCount); ubf = rabptr->rab$l_ubf; usz = rabptr->rab$w_usz; rabptr->rab$l_bkt = BlockNumber; rabptr->rab$l_ubf = BlockBuffer; rabptr->rab$w_usz = BlockCount * 512; status = sys$read (&BookFileRab, 0, 0); if (Debug) fprintf (stdout, "sys$read() %%X%08.08X\n", status); rabptr->rab$l_ubf = ubf; rabptr->rab$w_usz = usz; if (VMSnok (status)) return (status); recptr = BlockBuffer + rabptr->rab$w_rfa4; rsz = *(unsigned short*)recptr; if (Debug) fprintf (stdout, "rsz:%d\n", rsz); if (rsz > usz) return (RMS$_UBF); recptr += 2; memcpy (rabptr->rab$l_ubf, recptr, rsz); rabptr->rab$w_rsz = rsz; return (status); } /*****************************************************************************/ /* */ int OpenBookFile () { int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "OpenBookFile() %s\n", BookFileNamePtr); /* file access block */ BookFileFab = cc$rms_fab; BookFileFab.fab$b_fac = FAB$M_GET | FAB$M_BRO; BookFileFab.fab$l_dna = "DECW$BOOK:.DECW$BOOK"; BookFileFab.fab$b_dns = 20; BookFileFab.fab$b_shr = FAB$M_SHRGET; BookFileFab.fab$l_xab = &BookFileXabDat; BookFileXabDat = cc$rms_xabdat; BookFileXabDat.xab$l_nxt = &BookFileXabFhc; BookFileXabFhc = cc$rms_xabfhc; #ifdef ODS_EXTENDED if (OdsExtended) { BookFileFab.fab$l_fna = (char*)-1; BookFileFab.fab$b_fns = 0; BookFileFab.fab$l_nam = (struct namdef*)&BookFileNaml; ENAMEL_RMS_NAML(BookFileNaml) BookFileNaml.naml$l_long_filename = BookFileNamePtr; BookFileNaml.naml$l_long_filename_size = strlen(BookFileNamePtr); BookFileNaml.naml$l_long_expand = ExpBookFileName; BookFileNaml.naml$l_long_expand_alloc = sizeof(ExpBookFileName)-1; } else #endif /* ODS_EXTENDED */ { BookFileFab.fab$l_fna = BookFileNamePtr; BookFileFab.fab$b_fns = strlen(BookFileNamePtr); BookFileFab.fab$l_nam = &BookFileNam; BookFileNam = cc$rms_nam; BookFileNam.nam$l_esa = ExpBookFileName; BookFileNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH; } if (VMSok (status = sys$open (&BookFileFab, 0, 0))) { /* terminate 'ExpBookFileName' at the version delimiter */ #ifdef ODS_EXTENDED if (OdsExtended) *BookFileNaml.naml$l_long_ver = '\0'; else #endif /* ODS_EXTENDED */ *BookFileNam.nam$l_ver = '\0'; if (*CgiFormVarRecPtr) { if (*CgiFormVarRecPtr == '1' || toupper(*CgiFormVarRecPtr) == 'Y') BookFileVarRec = true; else BookFileVarRec = false; } else if (BookFileFab.fab$b_rfm == FAB$C_VAR) BookFileVarRec = true; else BookFileVarRec = false; /* record access block */ BookFileRab = cc$rms_rab; BookFileRab.rab$l_fab = &BookFileFab; if (BookFileVarRec) { /* 3 buffers, read ahead performance option */ BookFileRab.rab$b_mbf = 3; BookFileRab.rab$l_rop = RAB$M_RAH; } else BookFileRab.rab$l_rop = RAB$M_BIO; if (CgiHttpIfModifiedSincePtr[0]) { if (VMSnok (status = ModifiedSince (&IfModifiedSinceBinaryTime, &BookFileXabDat.xab$q_rdt))) { /* book has not been modified since the date/time, don't send */ return (status); } } HttpGmTimeString (LastModifiedGmDateTime, &BookFileXabDat.xab$q_rdt); if (Debug) fprintf (stdout, "lrl: %d\n", BookFileXabFhc.xab$w_lrl); if (!(BookFileLongestRecordLength = BookFileXabFhc.xab$w_lrl)) BookFileLongestRecordLength = 32767; return (sys$connect (&BookFileRab, 0, 0)); } if (CgiFormTitlePtr[0]) CgiLibResponseError (FI_LI, status, CgiFormTitlePtr); else if (CgiPathInfoPtr[0]) CgiLibResponseError (FI_LI, status, CgiPathInfoPtr); else CgiLibResponseError (FI_LI, status, CgiFormFilePtr); return (status); } /*****************************************************************************/ /* Create a series of navigation buttons, e.g. "next", "previous", "contents", "tables", "figures", "index", etc. These are provided at the top and bottom of each page. The buttons can be either highlighted anchors (if the button represents an available link) or just "there" (if there is no relevant link, i.e. on the "contents" page it is redundant to have a "contents" link) as appropriate. */ ButtonBar (int Top1Bottom2) { int idx; char *cptr, *sptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ButtonBar()\n"); TBW += fprintf (stdout, "
\n", Top1Bottom2 == 1 ? " butbartop" : ""); cptr = ButtonsPtr; /* previous */ cptr = ButtonBarButton (cptr, NULL, PreviousChunkNumber, DoMassage); /* back */ cptr = ButtonBarButton (cptr, "", -1, false); /* next */ cptr = ButtonBarButton (cptr, NULL, NextChunkNumber, DoMassage); /* divisions provided in the book itself */ for (idx = 1; idx <= BookDivisionCount; idx++) { if (GetChunkNumber == BookDivision[idx].ChunkNumber) ButtonBarButton (BookDivision[idx].DescriptionPtr, "", 0, DoMassage); else ButtonBarButton (BookDivision[idx].DescriptionPtr, "", BookDivision[idx].ChunkNumber, DoMassage); } /* close */ if (CgiFormRefererPtr[0]) cptr = ButtonBarButton (cptr, CgiFormRefererPtr, -1, false); else cptr = ButtonBarButton (cptr, NULL, -1, false); /* further buttons (starting with "help") */ while (*cptr) cptr = ButtonBarButton (cptr, NULL, -1, false); TBW += fprintf (stdout, "
\n"); } /*****************************************************************************/ /* Generate a single "button" inside the context created by ButtonBar(). */ char* ButtonBarButton ( char *ButtonLabel, char *ButtonPath, int ChunkNumber, BOOL NavMassage ) { char *cptr, *mptr, *sptr, *tptr, *uptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ButtonBarButton() |%s|\n", ButtonLabel); if (NavMassage) mptr = ""; else mptr = "&massage=no"; if (!*ButtonLabel) for (cptr = ButtonLabel = "*ERROR*"; *cptr; cptr++); else for (cptr = ButtonLabel; *cptr && *cptr != '=' && *cptr != '$'; cptr++); if (*ButtonLabel == '^') { ButtonLabel++; tptr = " target=\"_blank\""; } else tptr = ""; if (ChunkNumber == 0) { /* no chunk number therefore no link, just a label */ TBW += fprintf (stdout, "%*.*s\n", cptr-ButtonLabel, cptr-ButtonLabel, ButtonLabel); } else if (ChunkNumber > 0) { /* link to this particular chunk of the book */ TBW += fprintf (stdout, "%*.*s\n", CgiScriptNamePtr, UrlEncodedCgiPathInfoPtr, BookSpecifiedByFile, ChunkNumber, UrlEncReferer, UrlEncTitle, mptr, cptr-ButtonLabel, cptr-ButtonLabel, ButtonLabel); } else { /* -1 == SPECIAL CASE, create non-chunk button */ if (*cptr == '=') for (sptr = uptr = cptr+1; *sptr && *sptr != '$'; sptr++); else if (ButtonPath) sptr = (uptr = ButtonPath) + strlen(ButtonPath); else sptr = (uptr = "*ERROR*") + 8; if (!memcmp(uptr,"javascript:", 11)) TBW += fprintf (stdout, "\n", tptr, sptr-uptr, sptr-uptr, uptr, cptr-ButtonLabel, cptr-ButtonLabel, ButtonLabel); else TBW += fprintf (stdout, "%*.*s\n", tptr, sptr-uptr, sptr-uptr, uptr, cptr-ButtonLabel, cptr-ButtonLabel, ButtonLabel); } while (*cptr && *cptr != '$') cptr++; while (*cptr && *cptr == '$') cptr++; return (cptr); } /*****************************************************************************/ /* Return a bitmap image to the client as a GIF image. The code in these functions is implemented in accordance with Compuserve's Graphic Interchange Format Programming Reference specification, version 89a, 31st July 1990. The LZW compression employed by the GIF algorithm is implemented using code derived from the PBM suite. Two functions, virtually unmodified, are employed, GifCompress() ... formally called 'compgif()', and GifPackBits() ... formally called 'pack_bits()'. The original commentary and copyright notice remains as per the code author's request. */ GifImage ( int WidthPixels, int HeightPixels, unsigned char *ImageBytePtr, int ByteCount ) { static int Background = 0, BitsPerPixel = 1; /* background (index 0) is white, foreground (index 1) is black */ static unsigned char Red [] = { 0xff, 0x00 }, Green [] = { 0xff, 0x00 }, Blue [] = { 0xff, 0x00 }; int idx, Byte, LeftOffset, TopOffset, Resolution, ColorMapSize, InitCodeSize; char *bptr; char Buffer [512]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GifImage()\n"); CgiLibResponseHeader (200, "image/gif", "Last-Modified: %s\n", LastModifiedGmDateTime); /* initialize the following functions */ GifNextPixel (ImageBytePtr, ByteCount); GifPackBits (-1, -1); ColorMapSize = 1 << BitsPerPixel; LeftOffset = TopOffset = 0; Resolution = BitsPerPixel; /* BookReader Format (BRF) images are always whole bytes. Image width specifications (in bits) do not always appear to be so! If the width is not an even number of bits (making up a whole byte) then make it so by rounding up to the next whole byte number of bits. Seems to work OK! */ if (WidthPixels & 0x7) WidthPixels = ((WidthPixels >> 3) + 1) << 3; /* the initial code size */ if( BitsPerPixel <= 1 ) InitCodeSize = 2; else InitCodeSize = BitsPerPixel; /**************************/ /* GIF Data Stream header */ /**************************/ /* accumulate bytes into buffer before output */ bptr = Buffer; strcpy (bptr, "GIF89a"); bptr += 6; /*****************************/ /* Logical Screen Descriptor */ /*****************************/ /* width and height of logical screen */ *bptr++ = WidthPixels & 0xff; *bptr++ = (WidthPixels >> 8) & 0xff; *bptr++ = HeightPixels & 0xff; *bptr++ = (HeightPixels >> 8) & 0xff; /* indicate that there is a global colour map */ Byte = 0x80; /* OR in the resolution */ Byte |= (Resolution - 1) << 5; /* OR in the Bits per Pixel */ Byte |= (BitsPerPixel - 1); /* write it out */ *bptr++ = Byte; /* Background colour */ *bptr++ = Background; /* pixel aspect ratio */ *bptr++ = 0; /***********************/ /* Global Colour Table */ /***********************/ for (idx = 0; idx < ColorMapSize; idx++) { *bptr++ = Red[idx]; *bptr++ = Green[idx]; *bptr++ = Blue[idx]; } /****************************************/ /* Graphic Control Extension descriptor */ /****************************************/ /* extension introducer and graphic control label */ *bptr++ = 0x21; *bptr++ = 0xf9; /* fixed size of the following data block */ *bptr++ = 0x04; /* Transparency Index is provided */ *bptr++ = 0x01; /* no data in these */ *bptr++ = 0x00; *bptr++ = 0x00; /* Transparent Color Index value, BACKGROUND SHOULD BE TRANSPARENT */ *bptr++ = 0x00; /* block terminator */ *bptr++ = 0x00; /********************/ /* Image descriptor */ /********************/ /* write an Image separator */ *bptr++ = 0x2c; /* location of image within logical screen */ *bptr++ = LeftOffset & 0xff; *bptr++ = (LeftOffset >> 8) & 0xff; *bptr++ = TopOffset & 0xff; *bptr++ = (TopOffset >> 8) & 0xff; /* width and height of image within logical screen */ *bptr++ = WidthPixels & 0xff; *bptr++ = (WidthPixels >> 8) & 0xff; *bptr++ = HeightPixels & 0xff; *bptr++ = (HeightPixels >> 8) & 0xff; /* no local color table, image is not interlaced, not ordered, etc. */ *bptr++ = 0x00; /**************************/ /* table-based image data */ /**************************/ /* write out the initial code size */ *bptr++ = InitCodeSize; /* transfer what we've accumlated in the local buffer */ fwrite (Buffer, bptr-Buffer, 1, stdout); /* LZW compress the data using PBM-derived algorithm and code */ GifCompress (InitCodeSize + 1); /****************************************/ /* end of image data and GIF terminator */ /****************************************/ /* write out a zero-length packet (to end the series), and terminator */ fwrite ("\0;", 2, 1, stdout); } /*****************************************************************************/ /* Get a series of pixels from the bitmap. This function is called by GifCompress() to scan the image. Bit 0 through to bit 7 of the current byte are returned as 1 or 0 before the next byte is pointed to. When the total number of bytes have been transmitted return EOF. This function is initialized by a first call, setting the values of the byte pointer and number of bytes in the image. */ GifNextPixel ( unsigned char *ImageBytePtr, int ImageByteCount ) { static unsigned int BitPosition, ByteCount; static unsigned char *BytePtr; /*********/ /* begin */ /*********/ if (ImageBytePtr) { /* initialize */ BytePtr = ImageBytePtr; ByteCount = ImageByteCount; BitPosition = 1; return; } if (!BitPosition) { /* bits 0 through to 7 have been returned, move to next byte */ if (!ByteCount) return (EOF); ByteCount--; BytePtr++; BitPosition = 1; } if (*BytePtr & BitPosition) { /* next call will return the next most significant bit (if <= 7) */ BitPosition = (BitPosition << 1) & 0xff; /* the tested bit position contained a 1 (foreground) */ return (1); } else { /* next call will return the next most significant bit (if <= 7) */ BitPosition = (BitPosition << 1) & 0xff; /* the tested bit position contained a 0 (background) */ return (0); } } /****************************************************************************/ /* * This software is copyrighted as noted below. It may be freely copied, * modified, and redistributed, provided that the copyright notice is * preserved on all copies. * * There is no warranty or other guarantee of fitness for this software, * it is provided solely "as is". Bug reports or fixes may be sent * to the author, who may or may not act on them as he desires. * * You may not include this software in a program or other software product * without supplying the source, or without informing the end-user that the * source is available for no extra charge. * * If you modify this software, you should include a notice giving the * name of the person performing the modification, the date of modification, * and the reason for such modification. */ /* compgif.c */ /* * * GIF Image compression - LZW algorithm implemented with Trie type * structure. * Written by Bailey Brown, Jr. * last change May 24, 1990 * file: compgif.c * * You may use or modify this code as you wish, as long as you mention * my name in your documentation. * * - Bailey Brown, Jr. * */ #define MAXIMUMCODE 4095 /* 2**maximum_code_size */ #define BLOCKSIZE 256 /* max block byte count + 1 */ #define NULLPREFIX -1 typedef struct str_table_entry { int code; int prefix; int suffix; } strTableEntry; typedef struct str_table_node { strTableEntry entry; struct str_table_node *left; struct str_table_node *right; struct str_table_node *children; } strTableNode, *strTableNodePtr, **strTable; /* ******************************************************************** * compgif() recieves pointers to an input function and an output * * stream, and the code size as parameters and outputs successive * * blocks of LZW compressed gif data. The calling routine should * * have aready written the GIF file header out to the output file. * * It assumes that there will be no more than 8 bits/pixel and that * * each data item comes from successive bytes returned by infun. * ******************************************************************** */ int GifCompress (int code_size) { strTable heap; /* our very own memory manager */ int heap_index; int clear_code, end_code, cur_code; int i, found, num_colors, prefix, compress_size; int cur_char, end_of_data, bits_per_pix; strTableNodePtr cur_node; strTable root; /* root of string table for LZW compression is */ /* an array of 2**bits_per_pix pointers to atomic nodes */ heap_index = 0; heap = (strTable)malloc(sizeof(strTableNodePtr)*MAXIMUMCODE); if (!heap) printf("can't allocate heap"); for (i=0; i < MAXIMUMCODE; i++) { heap[i] = (strTableNodePtr)malloc(sizeof(strTableNode)); if (!heap[i]) { /* printf("can't allocate heap"); */ int status; status = vaxc$errno; CgiLibResponseError (FI_LI, status, ErrorCalloc); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } } bits_per_pix = code_size - 1; compress_size = code_size; num_colors = 1<<(bits_per_pix); clear_code = num_colors; end_code = clear_code + 1; cur_code = end_code + 1; prefix = NULLPREFIX; root = (strTable)malloc(sizeof(strTableNodePtr)*num_colors); if (!root) { /* printf("memory allocation failure (root)"); */ int status; status = vaxc$errno; CgiLibResponseError (FI_LI, status, "malloc()"); /* definitely exit if we can't get memory!!! */ exit (SS$_NORMAL); } for(i=0; ientry.code = i; root[i]->entry.prefix = NULLPREFIX; root[i]->entry.suffix = i; root[i]->left = NULL; root[i]->right = NULL; root[i]->children = NULL; } /* initialize output block */ GifPackBits(compress_size, -1); GifPackBits(compress_size, clear_code); end_of_data = 0; if ((cur_char = GifNextPixel(NULL, 0)) == EOF) printf("premature end of data"); while (!end_of_data) { prefix = cur_char; cur_node = root[prefix]; found = 1; if((cur_char = GifNextPixel(NULL, 0)) == EOF) { end_of_data = 1; break; } while(cur_node->children && found) { cur_node = cur_node->children; while(cur_node->entry.suffix != cur_char) { if (cur_char < cur_node->entry.suffix) { if (cur_node->left) cur_node = cur_node->left; else { cur_node->left = heap[heap_index++]; cur_node = cur_node->left; found = 0; break; } } else { if (cur_node->right) cur_node = cur_node->right; else { cur_node->right = heap[heap_index++]; cur_node = cur_node->right; found = 0; break; } } } if (found) { prefix = cur_node->entry.code; if((cur_char = GifNextPixel(NULL, 0)) == EOF) { end_of_data = 1; break; } } } if (end_of_data) break; if (found) { cur_node->children = heap[heap_index++]; cur_node = cur_node->children; } cur_node->children = NULL; cur_node->left = NULL; cur_node->right = NULL; cur_node->entry.code = cur_code; cur_node->entry.prefix = prefix; cur_node->entry.suffix = cur_char; GifPackBits(compress_size, prefix); if (cur_code > ((1<<(compress_size))-1)) compress_size++; if (cur_code < MAXIMUMCODE) { cur_code++; } else { heap_index = num_colors; /* reinitialize string table */ for (i=0; i < num_colors; i++ ) root[i]->children = NULL; GifPackBits(compress_size, clear_code); compress_size = bits_per_pix + 1; cur_code = end_code + 1; } } GifPackBits(compress_size, prefix); GifPackBits(compress_size, end_code); GifPackBits(compress_size, -1); for (i=0; i < MAXIMUMCODE; i++) free(heap[i]); free(heap); free(root); return (1); } /* ************************************************************************ * GifPackBits() packs the bits of the codes generated by gifenc() into * * a 1..256 byte output block. The first byte of the block is the * * number 0..255 of data bytes in the block. To flush or initialize * * the block, pass a negative argument. * ************************************************************************ */ GifPackBits (int compress_size, int prefix) { static int cur_bit = 8; static unsigned char block[BLOCKSIZE] = { 0 }; int i, left_over_bits; if (compress_size == -1 && prefix == -1) { /* initialize */ cur_bit = 8; block[0] = 0; return; } /* if we are about to excede the bounds of block or if the flush code (code_bis < 0) we output the block */ if((cur_bit + compress_size > (BLOCKSIZE-1)*8) || (prefix < 0)) { /* handle case of data overlapping blocks */ if ((left_over_bits = (((cur_bit>>3) + ((cur_bit & 7) != 0))<<3) - cur_bit) != 0) { for (i=0; i < left_over_bits; i++) { if (prefix & (1<>3] |= (char)(1<<(cur_bit & 7)); /* note n>>3 == n/8 and n & 7 == n % 8 */ cur_bit++; } } compress_size -= left_over_bits; prefix = prefix>>left_over_bits; block[0] = (unsigned char)((cur_bit>>3) - 1); if (block[0]) fwrite (block, block[0]+1, 1, stdout); for(i=0; i < BLOCKSIZE; i++) block[i] = 0; cur_bit = 8; } if (prefix >= 0) { for (i=0; i < compress_size; i++) { if (prefix & (1<>3] |= (unsigned char)(1<<(cur_bit & 7)); /* note n>>3 == n/8 and n & 7 == n % 8 */ cur_bit++; } } return (1); } /*****************************************************************************/ /* */ InsertComment ( int Horizontal, int Vertical, char *Explanation, int SourceCodeLineNumber ) { int Length, Line, Column; char String [256]; struct OutCharStruct *ocptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "InsertComment()\n"); Line = Vertical / LINE_POSITIONING_UNITS; if (!Line) Line = 1; if (Line > LinesOnPage) PageSize (Line); if (Line > LastLineOnPage) LastLineOnPage = Line; Column = Horizontal / COLUMN_POSITIONING_UNITS; if (Column >= MAX_OUTLINE_LENGTH) Column = MAX_OUTLINE_LENGTH-1; if (Debug) fprintf (stdout, "Line: %d Column: %d\n", Line, Column); Length = sprintf (String, "%s ", Explanation, SourceCodeLineNumber); ocptr = OUT_CHAR_AT(Line,Column); ocptr->htptr = HtmlString (ocptr->htptr, Length); strcat (ocptr->htptr, String); } /*****************************************************************************/ /* For debugging purposes. */ DumpBytes ( char *Comment, char *BytePtr, int NumberOfBytes ) { static char HexDigits [] = "0123456789abcdef"; int bcnt, ccnt; char *bptr, *sptr; char String [256]; /*********/ /* begin */ /*********/ fprintf (stdout, "\n%s Byte DUMP beginning: %08.08x Bytes: %d\n", Comment, BytePtr, NumberOfBytes); while (NumberOfBytes > 0) { sptr = String; bptr = BytePtr; sprintf (sptr, "\n%08.08x : ", bptr); sptr += 12; ccnt = 16; bcnt = NumberOfBytes; while (ccnt-- && bcnt--) { *sptr++ = HexDigits[*bptr >> 4]; *sptr++ = HexDigits[*bptr++ & 0x0f]; *sptr++ = ' '; } bptr = BytePtr; strcpy (sptr, "\n "); sptr += 12; ccnt = 16; bcnt = NumberOfBytes; while (ccnt-- && bcnt--) { if (isprint(*bptr)) { *sptr++ = ' '; *sptr++ = *bptr++; *sptr++ = ' '; } else { *sptr++ = ' '; *sptr++ = '^'; *sptr++ = ' '; bptr++; } } *sptr++ = '\n'; *sptr = '\0'; fputs (String, stdout); NumberOfBytes -= 16; BytePtr += 16; } fputs ("\n", stdout); } /*****************************************************************************/ /* For debugging purposes. */ DumpLongWords ( char *Comment, unsigned long *lwptr, int NumberOfLongWords ) { int count = 1; /*********/ /* begin */ /*********/ fprintf (stdout, "\n%s Longword DUMP beginning: %08.08x, Longwords: %d\n", Comment, lwptr, NumberOfLongWords); while (NumberOfLongWords-- > 0) { fprintf (stdout, "%2d. %08.08x : 0x%08.08x : %10d : %5d %5d : %3d %3d %3d %3d\n", count++, lwptr, *lwptr, *lwptr, *lwptr >> 16, *lwptr & 0xffff, (*lwptr & 0xff000000) >> 24, (*lwptr & 0xff0000) >> 16, (*lwptr & 0xff00) >> 8, *lwptr & 0xff); lwptr++; } fputs ("\n", stdout); } /*****************************************************************************/ /* If the object has been modified since the specified date and time then return a normal status indicating that the data transfer is to continue. If not modified then send a "not modified" HTTP header and return an error status to indicate the object should not be sent. */ int ModifiedSince ( unsigned long *SinceBinaryTimePtr, unsigned long *BinaryTimePtr ) { static unsigned long OneSecondDelta [2] = { -10000000, -1 }; int status; unsigned long AdjustedBinTime [2], ScratchBinTime [2]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "ModifiedSince()\n"); /* if request asks for a "reload" (not cached) then give it regardless */ if (strsame (CgiHttpPragmaPtr, "no-cache", -1)) return (SS$_NORMAL); if (strsame (CgiHttpCacheControlPtr, "no-cache", 8)) return (SS$_NORMAL); if (strsame (CgiHttpCacheControlPtr, "no-store", 8)) return (SS$_NORMAL); if (strsame (CgiHttpCacheControlPtr, "max-age=0", 9)) return (SS$_NORMAL); /* Add one second to the modified time. Ensures a negative time for VMS where fractional seconds may result in inconclusive results when the target time is being specified in whole seconds. */ if (VMSnok (status = lib$add_times (SinceBinaryTimePtr, &OneSecondDelta, &AdjustedBinTime))) { CgiLibResponseError (FI_LI, status, "lib$add_times()"); return (status); } if (Debug) fprintf (stdout, "sys$add_times() %%X%08.08X\n", status); /* if a positive time results the file has been modified */ if (VMSok (status = lib$sub_times (BinaryTimePtr, &AdjustedBinTime, &ScratchBinTime))) return (status); if (Debug) fprintf (stdout, "sys$sub_times() %%X%08.08X\n", status); if (status != LIB$_NEGTIM) { CgiLibResponseError (FI_LI, status, "sys$sub_times()"); return (status); } CgiLibResponseHeader (304, "text/html"); fprintf (stdout, "Not modified!\n"); return (LIB$_NEGTIM); } /*****************************************************************************/ /* Create an HTTP format Greenwich Mean Time (UTC) time string in the storage pointed at by 'TimeString', e.g. "Fri, 25 Aug 1995 17:32:40 GMT" (RFC 1123). This must be at least 30 characters capacity. If 'BinTimePtr' is null the time string represents the current time. If it points to a quadword, VMS time value the string represents that time. 'TimeString' must point to storage large enough for 31 characters. */ int HttpGmTimeString ( char *TimeString, unsigned long *BinTimePtr ) { static char *DayNames [] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" }; static char *MonthName [] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static $DESCRIPTOR (HttpTimeFaoDsc, "!AZ, !2ZW !AZ !4ZW !2ZW:!2ZW:!2ZW GMT"); static $DESCRIPTOR (TimeStringDsc, ""); int status; unsigned long BinTime [2], GmTime [2]; unsigned short Length; unsigned short NumTime [7]; unsigned long DayOfWeek; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "HttpGmTimeString()\n"); if (!BinTimePtr) sys$gettim (&GmTime); else { GmTime[0] = BinTimePtr[0]; GmTime[1] = BinTimePtr[1]; } if (VMSnok (status = TimeAdjustGMT (true, &GmTime))) return (status); status = sys$numtim (&NumTime, &GmTime); if (Debug) fprintf (stdout, "sys$numtim() %%X%08.08X %d %d %d %d %d %d %d\n", status, NumTime[0], NumTime[1], NumTime[2], NumTime[3], NumTime[4], NumTime[5], NumTime[6]); if (VMSnok (status = lib$day_of_week (&GmTime, &DayOfWeek))) return (status); if (Debug) fprintf (stdout, "lib$day_of_week() %%X%08.08X is %d\n", status, DayOfWeek); /* set the descriptor address and size of the resultant time string */ TimeStringDsc.dsc$w_length = 30; TimeStringDsc.dsc$a_pointer = TimeString; if (VMSnok (status = sys$fao (&HttpTimeFaoDsc, &Length, &TimeStringDsc, DayNames[DayOfWeek], NumTime[2], MonthName[NumTime[1]], NumTime[0], NumTime[3], NumTime[4], NumTime[5]))) { if (Debug) fprintf (stdout, "sys$fao() %%X%08.08X\n", status); TimeString[0] = '\0'; } else TimeString[Length] = '\0'; if (Debug) fprintf (stdout, "|%s|\n", TimeString); return (status); } /*****************************************************************************/ /* Given a string such as "Fri, 25 Aug 1995 17:32:40 GMT" (RFC 1123), or "Friday, 25-Aug-1995 17:32:40 GMT" (RFC 1036), create an internal, local, binary time with the current GMT offset. See complementary function HttpGmTimeString(). */ int HttpGmTime ( char *TimeString, unsigned long *BinTimePtr ) { static char *MonthName [] = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; int status; unsigned short Length; unsigned short NumTime [7] = { 0,0,0,0,0,0,0 }; unsigned long DayOfWeek; char *tptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "HttpGmTime() |%s|\n", TimeString); tptr = TimeString; /* hunt straight for the comma after the weekday name! */ while (*tptr && *tptr != ',') tptr++; if (*tptr) tptr++; /* span white space between weekday name and date */ while (*tptr && isspace(*tptr)) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the date and then skip to month name */ if (isdigit(*tptr)) NumTime[2] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; while (*tptr && (*tptr == '-' || isspace(*tptr))) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the month number from the name and skip to the year */ for (NumTime[1] = 1; NumTime[1] <= 12; NumTime[1]++) if (strsame (tptr, MonthName[NumTime[1]], 3)) break; if (NumTime[1] > 12) return (STS$K_ERROR); while (*tptr && isalpha(*tptr)) tptr++; while (*tptr && (*tptr == '-' || isspace(*tptr))) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the year and then skip to the hour */ if (isdigit(*tptr)) { NumTime[0] = atoi (tptr); if (NumTime[0] < 100) NumTime[0] += 1900; } while (*tptr && isdigit(*tptr)) tptr++; while (*tptr && isspace(*tptr)) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!*tptr) return (STS$K_ERROR); /* get the hour, minute and second */ if (isdigit(*tptr)) NumTime[3] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; if (*tptr == ':') tptr++; if (isdigit(*tptr)) NumTime[4] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; if (*tptr == ':') tptr++; if (isdigit(*tptr)) NumTime[5] = atoi (tptr); while (*tptr && isdigit(*tptr)) tptr++; if (*tptr == ':') tptr++; if (!*tptr) return (STS$K_ERROR); /* the only thing remaining should be the "GMT" */ while (*tptr && isspace(*tptr)) tptr++; if (Debug) fprintf (stdout, "tptr |%s|\n", tptr); if (!strsame (tptr, "GMT", 3)) return (STS$K_ERROR); /*******************************************/ /* convert what looks like legitimate GMT! */ /*******************************************/ if (Debug) fprintf (stdout, "NumTime[] %d %d %d %d %d %d %d\n", NumTime[0], NumTime[1], NumTime[2], NumTime[3], NumTime[4], NumTime[5], NumTime[6]); status = lib$cvt_vectim (&NumTime, BinTimePtr); if (VMSnok (status)) return (status); if (Debug) fprintf (stdout, "lib$cvt_vectim() %%X%08.08X\n", status); return (TimeAdjustGMT (false, BinTimePtr)); } /*****************************************************************************/ /* Determine the offset from GMT (UTC) using either the HTTPD$GMT or SYS$TIMEZONE_DIFFERENTIAL logicals. If HTTPD$GMT is not defined time should be set from the timezone differential logical. Function RequestBegin() calls this every hour to recheck GMT offset and detect daylight saving or other timezone changes. */ int TimeSetGmt () { static BOOL UseTimezoneDifferential = false; int status; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "TimeSetGmt()\n"); if (!UseTimezoneDifferential) { status = TimeSetHttpdGmt(); /* return if error and that error was not that the name did not exist */ if (VMSok (status) || status != SS$_NOLOGNAM) return (status); } UseTimezoneDifferential = true; return (TimeSetTimezone()); } /*****************************************************************************/ /* The SYS$TIMEZONE_DIFFERENTIAL logical contains the number of seconds offset from GMT (UTC) as a positive (ahead) or negative (behind) number. Set the 'TimeGmtString' global storage to a "+hh:mm" or "-hh:mm" string equivalent, and the 'TimeAheadOfGmt' global boolean and 'TimeGmtVmsString' delta-time global string. */ int TimeSetTimezone () { static unsigned short Length; static $DESCRIPTOR (TimezoneLogicalNameDsc, "SYS$TIMEZONE_DIFFERENTIAL"); static $DESCRIPTOR (LnmSystemDsc, "LNM$SYSTEM"); static $DESCRIPTOR (TimeGmtStringFaoDsc, "!AZ!2ZL:!2ZL"); static $DESCRIPTOR (TimeGmtVmsStringFaoDsc, "0 !2ZL:!2ZL"); static $DESCRIPTOR (TimeGmtStringDsc, TimeGmtString); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } LnmItems [] = { { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length }, { 0,0,0,0 } }; int status; long Hours, Minutes, Seconds; char *SignPtr; $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "TimeSetTimezone()\n"); status = sys$trnlnm (0, &LnmSystemDsc, &TimezoneLogicalNameDsc, 0, &LnmItems); if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status); if (VMSnok (status)) return (status); TimeGmtString[Length] = '\0'; if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString); Seconds = atol(TimeGmtString); if (Seconds < 0) { TimeAheadOfGmt = false; Seconds = -Seconds; SignPtr = "-"; } else { TimeAheadOfGmt = true; SignPtr = "+"; } Hours = Seconds / 3600; Minutes = (Seconds - Hours * 3600) / 60; if (Debug) fprintf (stdout, "%d %s%d:%d\n", Seconds, SignPtr, Hours, Minutes); sys$fao (&TimeGmtStringFaoDsc, &Length, &TimeGmtStringDsc, SignPtr, Hours, Minutes); TimeGmtString[Length] = '\0'; if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString); sys$fao (&TimeGmtVmsStringFaoDsc, &Length, &TimeGmtVmsStringDsc, Hours, Minutes); TimeGmtVmsString[Length] = '\0'; if (Debug) fprintf (stdout, "TimeGmtVmsString |%s|\n", TimeGmtVmsString); TimeGmtVmsStringDsc.dsc$w_length = Length; if (VMSnok (status = sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary))) return (status); if (TimeGmtDeltaBinary[0] || TimeGmtDeltaBinary[1]) return (status); /* time must have been zero, make it one, one-hundreth of a second */ TimeGmtDeltaBinary[0] = -100000; TimeGmtDeltaBinary[1] = -1; return (SS$_NORMAL); } /*****************************************************************************/ /* Translate the logical HTTPD$GMT (defined to be something like "+10:30" or "- 01:15") and convert it into a delta time structure and store in 'TimeGmtDeltaBinary'. Store whether it is in advance or behind GMT in boolean 'TimeAheadOfGmt'. Store the logical string in 'TimeGmtString'. */ int TimeSetHttpdGmt () { static unsigned short Length; static $DESCRIPTOR (GmtLogicalNameDsc, "HTTPD$GMT"); static $DESCRIPTOR (LnmFileDevDsc, "LNM$FILE_DEV"); static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } LnmItems [] = { { sizeof(TimeGmtString)-1, LNM$_STRING, TimeGmtString, &Length }, { 0,0,0,0 } }; int status; char *cptr, *sptr; $DESCRIPTOR (TimeGmtVmsStringDsc, TimeGmtVmsString); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "TimeSetHttpdGmt()\n"); status = sys$trnlnm (0, &LnmFileDevDsc, &GmtLogicalNameDsc, 0, &LnmItems); if (Debug) fprintf (stdout, "sys$trnlnm() %%X%08.08X\n", status); if (VMSnok (status)) return (status); TimeGmtString[Length] = '\0'; if (Debug) fprintf (stdout, "TimeGmtString |%s|\n", TimeGmtString); if (TimeGmtString[0] == '$') return (SS$_NORMAL); if (*(cptr = TimeGmtString) == '-') TimeAheadOfGmt = false; else TimeAheadOfGmt = true; if (*cptr == '+' || *cptr == '-') cptr++; sptr = TimeGmtVmsString; *sptr++ = '0'; *sptr++ = ' '; while (*cptr) *sptr++ = *cptr++; *sptr = '\0'; if (Debug) fprintf (stdout, "TimeGmtVmsString |%s|\n", TimeGmtVmsString); TimeGmtVmsStringDsc.dsc$w_length = sptr - TimeGmtVmsString; if (VMSnok (status = sys$bintim (&TimeGmtVmsStringDsc, &TimeGmtDeltaBinary))) return (status); if (TimeGmtDeltaBinary[0] || TimeGmtDeltaBinary[1]) return (status); /* time must have been zero, make it one, one-hundreth of a second */ TimeGmtDeltaBinary[0] = -100000; TimeGmtDeltaBinary[1] = -1; return (SS$_NORMAL); } /*****************************************************************************/ /* The GMT is generated by calculating using an offset using 'TimeGmtDeltaOffset' and boolean 'TimeAheadOfGmt'. Adjust either to or from GMT. */ int TimeAdjustGMT ( BOOL ToGmTime, unsigned long *BinTimePtr ) { int status; unsigned long AdjustedTime [2]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "TimeAdjustGMT() ToGmTime: %d\n", ToGmTime); if ((ToGmTime && TimeAheadOfGmt) || (!ToGmTime && !TimeAheadOfGmt)) { /* to GMT from local and ahead of GMT, or to local from GMT and behind */ status = lib$sub_times (BinTimePtr, &TimeGmtDeltaBinary, &AdjustedTime); if (Debug) fprintf (stdout, "lib$sub_times() %%X%08.08X\n", status); } else { /* to GMT from local and behind GMT, or to local from GMT and ahead */ status = lib$add_times (BinTimePtr, &TimeGmtDeltaBinary, &AdjustedTime); if (Debug) fprintf (stdout, "lib$add_times() %%X%08.08X\n", status); } if (Debug) { unsigned short Length; char String [64]; $DESCRIPTOR (AdjustedTimeFaoDsc, "AdjustedTime: |!%D|\n"); $DESCRIPTOR (StringDsc, String); sys$fao (&AdjustedTimeFaoDsc, &Length, &StringDsc, &AdjustedTime); String[Length] = '\0'; fputs (String, stdout); } BinTimePtr[0] = AdjustedTime[0]; BinTimePtr[1] = AdjustedTime[1]; return (status); } /****************************************************************************/ /* Return an integer reflecting the major and minor version of VMS (e.g. 60, 61, 62, 70, 71, 72, etc.) */ #ifdef ODS_EXTENDED int GetVmsVersion () { static char SyiVersion [16]; static struct { short int buf_len; short int item; void *buf_addr; unsigned short *ret_len; } SyiItems [] = { { 8, SYI$_VERSION, &SyiVersion, 0 }, { 0,0,0,0 } }; int status, version; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "GetVmsVersion()\n"); if (VMSnok (status = sys$getsyiw (0, 0, 0, &SyiItems, 0, 0, 0))) exit (status); SyiVersion[8] = '\0'; version = ((SyiVersion[1]-48) * 10) + (SyiVersion[3]-48); if (Debug) fprintf (stdout, "|%s| %d\n", SyiVersion, version); return (version); } #endif /* ODS_EXTENDED */ /****************************************************************************/ /* 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. */ BOOL 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); } /*****************************************************************************/