/*****************************************************************************/ /* CGI[HTTP]Dmon.c Pronounced: see-gee-eye-deh-mon A CGI executable, browser viewable, HTTPDmon, using the underlying HTTPDmon executing in a PTD subprocess with the ANSI screen sequences converted into HTML. This is not a general but rather HTTPDmon-specific screen scraper. Of course, the infrastructure could be adapted to other purposes. Must be subject to WASD_CONFIG_AUTH authorisation! There are various ways this utility might be invoked. The obvious is... $ COPY WASD_EXE:CGIDMON.EXE CGI_EXE:CGIDMON.EXE $ INSTALL REPLACE CGI-BIN:[000000]CGIDMON /PRIVILEGE=(SYSPRV,SYSLCK,WORLD) Of course, you could also have an HTTPmon iteself script... $ COPY WASD_EXE:CGIDMON.EXE CGI_EXE:HTTPDMON.EXE $ INSTALL REPLACE CGI-BIN:[000000]HTTPDMON /PRIVILEGE=(SYSPRV,SYSLCK,WORLD) In fact, it will adapt itself to any other script name you care to use. HOW IT WORKS ------------ This was really just an exercise in screen-scraping the output of a "regular" command-line application onto a browser HTML page. The script uses JavaScript and the HTML DOM to build and populate that page. The script has two modes of operation. 1) build the browser page with display element 2) populate the display element with output from the CLI application The /build/ mode provides a browser page with the basic HTML/CSS/JavaScript resources. Inside that page an XMLHttpRequest() initiates the execution of another instance of the script this time with a query-string. The presence of the query-string initiates the /populate/ mode where the script uses the pseudo-terminal driver (PTD) to create a terminal session and then spawn a subprocess attached to that terminal. In that subprocess the CLI application (in this case HTTPDmon) is invoked and its output to the pseudo-terminal processed (scraped) to be HTML-escaped and otherwise munged for representative display in the display element. This munged output is transmitted back to the browser XMLHttpRequest() in these chunks. Each chunk is parsed from the total output and displayed as that chunk. To prevent the munged output becoming too bulky in the browser the /populate/ XMLHttpRequest() is re-initiated every 30 minutes (at ~1700 per screen then 30 minutes is approximately 1.5Mbytes). REQUIRED PRIVILEGES ------------------- Same as required for HTTPDmon, so... $ INSTALL REPLACE CGI-BIN:[000000]CGIDMON /PRIVILEGE=(SYSPRV,SYSLCK,WORLD) COPYRIGHT --------- Copyright (C) 2021 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version. http://www.gnu.org/licenses/gpl.txt VERSION LOG ----------- 28-MAY-2021 MGD initial */ /*****************************************************************************/ #define SOFTWAREVN "v1.0.0" #define SOFTWARENM "CGIDMON" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " " SOFTWAREVN " AXP" #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " " SOFTWAREVN " IA64" #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " " SOFTWAREVN " X86" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DC$_TERM 6 #ifndef UNT64PTR /* mainly to allow easy use of the __unaligned directive */ #define UINTPTR __unaligned unsigned int* #define ULONGPTR __unaligned unsigned long* #define USHORTPTR __unaligned unsigned short* #define UINT64PTR __unaligned uint64* #define INT64PTR __unaligned int64* #endif /* have up to 24 bits to play with */ #define ATTR_RSET (0x0001 << 8) /* reset all */ #define ATTR_BOLD (0x0002 << 8) /* bold */ #define ATTR_BOL0 (0x0004 << 8) /* bold off */ #define ATTR_ULIN (0x0008 << 8) /* underline */ #define ATTR_ULI0 (0x0010 << 8) /* underline off */ #define ATTR_BLNK (0x0020 << 8) /* blink */ #define ATTR_BLN0 (0x0040 << 8) /* blink off */ #define ATTR_REVS (0x0080 << 8) /* inverse */ #define ATTR_REV0 (0x0100 << 8) /* inverse off */ #define ATTR_INVS (0x0200 << 8) /* invisible */ #define ATTR_INV0 (0x0400 << 8) /* invisible off */ #define ATTR_DDH0 (0x0800 << 8) /* DECDHL top */ #define ATTR_DDH1 (0x1000 << 8) /* DECDHL bot */ #define ATTR_DDWL (0x2000 << 8) /* DECDWL double width */ #define ATTR_DSWL (0x4000 << 8) /* DECSWL single width */ #define ATTR_UCSI (0x8000 << 8) /* unknown CSI */ #define THIS_CHAR(ch) (ch & 0x000000ff) /* gets the character */ #define THIS_ATTR(ch) (ch & 0xffffff00) /* gets the attributes */ #if 1 int PtdDevice = TT$_VT100, PtdPageLength = 40, PtdPageWidth = 80; #else int PtdDevice = TT$_LA100, PtdPageLength = 48, PtdPageWidth = 132; #endif int dbug = 0, LibSpawnStatus, PtdReadStatus, PtdWriteStatus; ulong LibSpawnPid, ScriptJpiPid; char *CgiQueryString, *CgiRemoteUser, *CgiScriptName, *CgiServerSoftware, *dbugStream, *PtdReadBuffer, *PtdWriteBuffer; char ScriptName [64], ScriptPidString [16], PtdDevName [64]; static uint EfnRead; static ushort PtdChan; #define PTD_READ_SIZE 18 * 512 #define PTD_WRITE_SIZE 2 * 512 #define PTD_BUFFER_PAGES 20 static void PtdDelete (); static void PtdPoof (int); static void PtdRead (); static char* PtdScrapeOutput (char*, int); int PtdSpawnSub (); static void PtdSpawnAst (void*); static int PtdWrite (char*); static char* HtmlRead (ulong*, int, int); static char* ScrapeRead (char*, int); static int ScreenSentinal (char*); /**************/ /* JavaScript */ /**************/ char PageJavaScript [] = "\ \ var afterNetError = 60*1;\n\ var minsBeforeRefresh = 30;\n\ var retryInterval = 1000*2;\n\ var timeBegin = new Date();\n\ var bytesLoaded = 0;\n\ var prevBytesLoaded = 0;\n\ var networkError = false;\n\ var cntrlZ = false;\n\ var reload = \' ⟲ \';\n\ var borderlw = \'1px white dotted\';\n\ var borderlg = \'1px dimgray dotted\';\n\ var monHttpd;\n\ var monStatus;\n\ var xhr;\n\ \n\ function getMonData() {\n\ var url = window.location.href + \'?populate\';\n\ xhr = new XMLHttpRequest();\n\ xhr.open(\'GET\', url, true);\n\ networkError = false;\n\ bytesLoaded = prevBytesLoaded = 0;\n\ monHttpd = document.getElementById(\'HTTPDmon\');\n\ monStatus = document.getElementById(\'status\');\n\ updateStatus(\' loading…\');\n\ duraMon();\n\ \n\ xhr.onabort = function() {\n\ if (!cntrlZ) setTimeout(getMonData,1000);\n\ };\n\ \n\ xhr.onload = function() {\n\ if (!cntrlZ) setTimeout(getMonData,1000);\n\ };\n\ \n\ xhr.onerror = function(err) {\n\ networkError = true;\n\ if (bytesLoaded > 255) {\n\ monHttpd.style.borderLeft = borderlg;\n\ updateStatus(\' network error\');\n\ }\n\ setTimeout(getMonData,retryInterval);\n\ retryInterval *= 2;\n\ if (retryInterval > 1000*60) retryInterval = 1000*60;\n\ };\n\ \n\ xhr.onprogress = function(event) {\n\ bytesLoaded = event.loaded;\n\ if (bytesLoaded > 255) {\n\ monHttpd.innerHTML = xhr.response.substring(prevBytesLoaded);\n\ monHttpd.style.borderLeft = borderlw;\n\ updateStatus(\'\');\n\ retryInterval = 1000*2;\n\ }\n\ prevBytesLoaded = bytesLoaded;\n\ };\n\ \n\ xhr.send();\n\ }\n\ \n\ function updateStatus(string) {\n\ if (monStatus) monStatus.innerHTML = string;\n\ }\n\ \n\ function controlZ() {\n\ if (cntrlZ) {\n\ cntrlZ = false;\n\ timeBegin = new Date();\n\ window.location.reload();\n\ }\n\ else {\n\ cntrlZ = true;\n\ document.getElementById(\'cntrlz\').innerHTML = reload;\n\ monHttpd.style.borderLeft = borderlg;\n\ }\n\ if (xhr && xhr.readyState == 3) xhr.abort();\n\ bytesLoaded = prevBytesLoaded = 0;\n\ }\n\ \n\ function duraMon () {\n\ if (cntrlZ || networkError) return;\n\ setTimeout (duraMon, 1000*60);\n\ var timeNow = new Date();\n\ var mins = (timeNow.getTime() - timeBegin.getTime()) / 1000 / 60;\n\ mins = Math.floor(mins);\n\ if (mins < 1) return;\n\ if ((mins % minsBeforeRefresh) == 0) {\n\ if (xhr) xhr.abort();\n\ }\n\ if (mins < 60)\n\ var dura = mins + (mins > 1 ? \' mins\' : \' min\');\n\ else\n\ if (mins < 1440) {\n\ var hours = Math.floor(mins/60);\n\ var dura = hours + (hours > 1 ? \' hours\' : \' hour\');\n\ }\n\ else {\n\ var days = Math.floor(mins/60/24);\n\ var dura = days + (days > 1 ? \' days\' : \' day\');\n\ }\n\ document.getElementById(\'duraMon\').innerHTML = \' \' + dura;\n\ }\n"; /*******/ /* CSS */ /*******/ char PageCSS [] = "\ \ body { font-family:arial,verdana,helvetica,sans; font-size:11pt; \ background-color:white; color:black; margin:1em; }\n\ pre { font-family:monospace, monospace; width:80em; max-width:80em; \ white-space:pre; border-left: 1px white dotted; }\n\ .reverse { background-color:black; color:white; }\n\ .CGIDmon { display:inline-block; margin:0 0.5em 0 1em; letter-spacing:1px; }\n\ .duraMon { }\n\ .error { color:red; }\n\ .status { font-size:120%%; font-style:italic; }\n\ \ "; /*****************************************************************************/ /* */ int main (int argc, char *argv[]) { static unsigned long JpiPidItem = JPI$_PID; static unsigned long SyiNodeNameItem = SYI$_NODENAME; static char SyiNodeName [16]; static $DESCRIPTOR (SyiNodeNameDsc, SyiNodeName); int status; ushort slen; char *cptr, *sptr, *zptr;; /*********/ /* begin */ /*********/ lib$getjpi (&JpiPidItem, 0, 0, &ScriptJpiPid, 0, 0); sprintf (ScriptPidString, "%08.08X", ScriptJpiPid); lib$getsyi (&SyiNodeNameItem, 0, &SyiNodeNameDsc, &slen, 0, 0); SyiNodeName[slen] = '\0'; if (!(CgiServerSoftware = getenv ("WWW_SERVER_SOFTWARE"))) CgiServerSoftware = getenv ("SERVER_SOFTWARE"); if (CgiServerSoftware) { if (!(stdout = freopen ("SYS$OUTPUT", "w", stdout, "ctx=bin"))) exit (vaxc$errno); if (!(CgiRemoteUser = getenv ("WWW_REMOTE_USER"))) CgiRemoteUser = getenv ("REMOTE_USER"); if (CgiRemoteUser && *CgiRemoteUser == '\0') CgiRemoteUser = NULL; if (!(CgiQueryString = getenv ("WWW_QUERY_STRING"))) if (!(CgiQueryString = getenv ("QUERY_STRING"))) CgiQueryString = ""; if (!(CgiScriptName = getenv ("WWW_SCRIPT_NAME"))) CgiScriptName = getenv ("SCRIPT_NAME"); zptr = (sptr = ScriptName) + sizeof(ScriptName)-1; for (cptr = CgiScriptName; *cptr; cptr++); while (cptr > CgiScriptName && *cptr != '/') cptr--; for (cptr++; *cptr && *cptr != '.' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; if (!strcasecmp (ScriptName, "cgidmon")) strcpy (ScriptName, "CGIDmon"); else if (!strcasecmp (ScriptName, "httpdmon")) strcpy (ScriptName, "HTTPDmon"); if (*CgiQueryString) { /************/ /* populate */ /************/ if (!CgiRemoteUser) exit (SS$_AUTHFAIL); /* intended to be a /system logical nam */ dbugStream = getenv("CGIDMON_DBUG_STREAM"); fprintf (stdout, "Status: 200 OK\r\n\ Content-Type: text/%s\r\n\ Script-Control: X-stream-mode=1\r\n\ Script-Control: X-timeout-output=none\r\n\ Script-Control: X-content-encoding-gzip=0\r\n\ \r\n", dbugStream ? "plain" : "html"); fflush (stdout); status = PtdSpawnSub (); if (status & 1) sys$hiber(); exit (status); } else { /**************/ /* build page */ /**************/ fprintf (stdout, "Status: 200 OK\r\n\ Content-Type: text/html\r\n\ Script-Control: X-stream-mode\r\n\ \r\n\ \n\ \n\ \n\ \n\ %s %s::\n\ \n\ \n\ \n", SOFTWAREID, ScriptName, SyiNodeName, PageCSS, PageJavaScript); if (CgiRemoteUser) fprintf (stdout, "\n\ \n\
\n");
         else
            fprintf (stdout,
"\n\
\n\
authorization failure
\n\ \n"); fprintf (stdout, "%s\ \n\ \n\ \n\ \n\ \n", ScriptName); } } else { status = PtdSpawnSub (); if (status & 1) sys$hiber(); exit (status); } exit (SS$_NORMAL); } /*****************************************************************************/ /* Create a pseudo-terminal driver terminal session, spawn a process attached to that session, and write CLI commands to that process. */ int PtdSpawnSub () { static unsigned long DevNamItem = DVI$_DEVNAM; /* nowait nolognam noclisym nokeypad nocontrol */ static unsigned long flags = 0x6f; static ulong inadr [2]; static ulong chbuf [3]; static $DESCRIPTOR (PromptDsc, "CGIDMON: "); static $DESCRIPTOR (DevNameDsc, PtdDevName); static $DESCRIPTOR (PrcNamDsc, ""); int status; unsigned short slen; char *aptr, *cptr, *sptr, *zptr; char dclbuf [256], PrcNam [32]; /*********/ /* begin */ /*********/ status = lib$get_ef (&EfnRead); if (!(status & 1)) return (status); sptr = dclbuf; /* hmmm; the leading empty line suppresses some oddity */ for (cptr = "\nSET PROCESS /PRIVILEGE=(SYSPRV,SYSLCK,WORLD)"; *cptr; *sptr++ = *cptr++); for (cptr = "\n@WASD_FILE_DEV"; *cptr; *sptr++ = *cptr++); for (cptr = "\nMCR WASD_EXE:HTTPDMON /STATUS"; *cptr; *sptr++ = *cptr++); if (dbugStream) for (cptr = " /INTERVAL=10"; *cptr; *sptr++ = *cptr++); for (cptr = "\nEOJ"; *cptr; *sptr++ = *cptr++); *sptr = '\0'; /* if not trying to REstart the subprocess */ if (!inadr[0]) { status = sys$expreg (PTD_BUFFER_PAGES, &inadr, 0, 0); if (dbug) printf ("sys$expreg() %%X%08.08x\n", status); if (!(status & 1)) return (status); PtdReadBuffer = (char*)inadr[0]; PtdWriteBuffer = (char*)inadr[0] + PTD_READ_SIZE; /* set the terminal characteristics */ chbuf[0] = (PtdPageWidth << 16) | (PtdDevice << 8) | DC$_TERM; chbuf[1] = (PtdPageLength << 24) | TT$M_EIGHTBIT | TT$M_SCOPE | TT$M_WRAP | TT$M_MECHTAB | TT$M_LOWER | TT$M_TTSYNC /* | TT$M_NOECHO */ ; chbuf[2] = TT2$M_EDIT | TT2$M_DRCS | TT2$M_EDITING | TT2$M_HANGUP; } memset ((char*)inadr[0], 0, (char*)inadr[1] - (char*)inadr[0]); PtdReadStatus = LibSpawnPid = LibSpawnStatus = PtdWriteStatus = 0; status = ptd$create (&PtdChan, 0, chbuf, sizeof(chbuf), 0, 0, 0, &inadr); if (dbug) printf ("ptd$create() %%X%08.08x\n", status); if (!(status & 1)) return (status); status = lib$getdvi (&DevNamItem, &PtdChan, 0, 0, &DevNameDsc, &slen); PtdDevName[DevNameDsc.dsc$w_length = slen] = '\0'; if (dbug) printf ("lib$getdvi() %d %%X%08.08x |%s|\n", PtdChan, status, PtdDevName); if (!(status & 1)) return (status); sprintf (PrcNam, "HTTPDmon_%4.4X", ScriptJpiPid & 0xffff); PrcNamDsc.dsc$a_pointer = PrcNam; PrcNamDsc.dsc$w_length = strlen(PrcNam); status = lib$spawn (0, &DevNameDsc, &DevNameDsc, &flags, &PrcNamDsc, &LibSpawnPid, &LibSpawnStatus, 0, &PtdSpawnAst, 0, &PromptDsc, 0, 0); if (dbug) printf ("lib$spawn() %%X%08.08x\n", status); if (!(status & 1)) return (status); PtdRead (); for (int cnt = 5; cnt; cnt--) { if (LibSpawnStatus) return (LibSpawnStatus); sleep (1); if (PtdReadStatus & 1) break; } PtdWrite (dclbuf); return (status); } /*****************************************************************************/ /* Subprocess self-destruct. */ static void PtdPoof (int status) { ulong pid; /*********/ /* begin */ /*********/ PtdDelete (); if (!(pid = LibSpawnPid)) return; LibSpawnPid = 0; sys$forcex (&pid, 0, status); } /*****************************************************************************/ /* Delete the pseudo-terminal. */ static void PtdDelete () { /*********/ /* begin */ /*********/ if (dbug) printf ("ptd$delete() %d %%X%08.08X %%X%08.08X\n", PtdChan, PtdReadStatus, PtdWriteStatus); if (PtdChan) ptd$delete (PtdChan); PtdChan = 0; } /*****************************************************************************/ /* Subpocess completion AST. */ static void PtdSpawnAst (void *vptr) { ushort slen; char msg [256]; $DESCRIPTOR (msgDsc, msg); /*********/ /* begin */ /*********/ if (dbug) printf ("PtdSpawnAst() %%X%08.08X %d\n", LibSpawnStatus, PtdChan); if (PtdChan) ptd$delete (PtdChan); LibSpawnPid = 0; if (!(LibSpawnStatus & 1)) { sys$getmsg (LibSpawnStatus, &slen, &msgDsc, 1, 0); msg[slen] = '\0'; fprintf (stdout, "%s", msg); fflush (stdout); } sys$wake (0, 0); } /*****************************************************************************/ /* Expects a string containing newline separated, multiple DCL commands, and writes them successively to the subprocess' SYS$INPUT. */ static int PtdWrite (char *string) { static char *cmds, *next; int bcnt, status = 1; char *bptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ bptr = sptr = PtdWriteBuffer; if (PtdWriteStatus = *(ushort*)bptr) { bptr += sizeof(ushort); bcnt = *(ushort*)bptr; if (dbug) printf ("ptd$write() %d %%X%08.08X\n", bcnt, PtdWriteStatus); if (!(PtdWriteStatus & 1)) PtdPoof (PtdWriteStatus); } if (!cmds) cmds = next = strdup (string); if (!*next) { next = ""; free (cmds); cmds = NULL; return (SS$_NORMAL); } bptr = sptr = PtdWriteBuffer; *(ulong*)sptr = 0; cptr = (sptr += sizeof(ushort) + sizeof(ushort)); zptr = bptr + PTD_WRITE_SIZE; while (*next && *next != '\n' && sptr < zptr) *sptr++ = *next++; *sptr++ = '\r'; *sptr = '\0'; bcnt = sptr - cptr; if (*next) next++; if (dbug) printf ("ptd$write() %d |%s|\n", bcnt, cptr); status = ptd$write (PtdChan, PtdWrite, 0, bptr, bcnt, 0, 0); if (dbug) printf ("ptd$write(3) %%X%08.08X\n", status); if (!(status & 1)) PtdPoof (status); return (status); } /*****************************************************************************/ /* Read subprocess' SYS$OUTPUT records providing each to PtdScrapeOutput(). */ static void PtdRead () { int bcnt, status; char *bptr, *cptr, *sptr; /*********/ /* begin */ /*********/ bptr = PtdReadBuffer; if (dbug) printf ("PtdRead() %%X%08.08X %d\n", *(ushort*)bptr, *(ushort*)(bptr+2)); if (PtdReadStatus = *(USHORTPTR)bptr) { if (!(PtdReadStatus & 1)) PtdPoof (PtdReadStatus); bptr += sizeof(ushort); bcnt = *(USHORTPTR)bptr; bptr += sizeof(ushort); if (dbugStream) { fprintf (stdout, "PtdRead(%d)\n", bcnt); for (cptr = bptr; cptr < bptr + bcnt; cptr++) { fprintf (stdout, "%02.02x%c ", *cptr, *cptr >= 32 ? *cptr : '.'); if (*cptr == '\n') fputs ("\n", stdout); } fputs ("\n", stdout); } if (sptr = ScrapeRead (bptr, bcnt)) { static ulong secs, psecs; time (&secs); if (!psecs || secs - psecs) { psecs = secs; if (CgiServerSoftware) if (dbugStream) fprintf (stdout, "***** %d\n%s\n*****\n", strlen(sptr), sptr); else fputs (sptr, stdout); else fprintf (stdout, "***** %d\n%s\n*****\n", strlen(sptr), sptr); } } fflush (stdout); } if (dbug) printf ("PtdReadStatus: %%X%08.08X\n", PtdReadStatus); *(ULONGPTR)PtdReadBuffer = 0; status = ptd$read (EfnRead, PtdChan, PtdRead, 0, PtdReadBuffer, PTD_READ_SIZE-sizeof(ulong)-2); if (dbug) printf ("ptd$read() %%X%08.08X\n", status); if (!(status & 1)) PtdPoof (status); } /*****************************************************************************/ /* Process output from the pseudo-terminal session. This function creates a virtual screen /width/ chars wide and /page/ lines long. Each char in the virtual screen is 32 bits deep with the least significant 8 bits containing the ASCII character and the remaining 24 bits any attributes (e.g. bold, underline) received prior to the character. The virtual terminal has a virtual /cursor/ positioned by the /row/ and /col/ variables. */ static char* ScrapeRead (char *record, int length) { static int SentinalCount; static ulong *screen; # define CSAVE_MAX 16 static int csave = -1; static int colsave [CSAVE_MAX], rowsave [CSAVE_MAX]; int attrib, col, ch, ccnt, page, rcnt, row, size, tab, width; char *aptr, *cptr, *czptr, *sptr, *zptr; /*********/ /* begin */ /*********/ width = PtdPageWidth; page = PtdPageLength; /* ulong = <8-char><24-attrib>*/ if (!screen) screen = calloc (1, size = (page * width) * sizeof(ulong)); if (!screen) exit (vaxc$errno); if (dbugStream) { czptr = (cptr = record) + length; fprintf (stdout, "ScrapeRead(%d)\n", length); for (cptr = record; cptr < czptr; cptr++) { fprintf (stdout, "%02.02x%c ", *cptr, *cptr >= 32 ? *cptr : '.'); if (*cptr == '\n') fputs ("\n", stdout); } fputs ("\n", stdout); } if (length <= 1) return (NULL); if (ScreenSentinal (record)) { SentinalCount++; row = col = 0; /* clear screen */ for (rcnt = 0; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; if (dbugStream) fprintf (stdout, "SentinalCount: %d\n", SentinalCount); } if (!SentinalCount) return (NULL); aptr = NULL; czptr = (cptr = record) + length; while (cptr < czptr) { if (*cptr >= 32) { /* non-control character(s) */ while (*cptr >= 32 && cptr < czptr) { screen[(row*width)+col] |= *cptr++; if (col+1 < width) col++; } continue; } if (*(USHORTPTR)cptr == '\x1b[') /* CSI */ { cptr += 2; if (*(USHORTPTR)cptr == '0m') { /* reset to normal attributes */ cptr += 2; screen[(row*width)+col] |= ATTR_RSET; continue; } if (*(USHORTPTR)cptr == '1m') { /* bold */ cptr += 2; screen[(row*width)+col] |= ATTR_BOLD; continue; } if (*(USHORTPTR)cptr == '4m') { /* underline */ cptr += 2; screen[(row*width)+col] |= ATTR_ULIN; continue; } if (*(USHORTPTR)cptr == '5m') { /* blink */ cptr += 2; screen[(row*width)+col] |= ATTR_BLNK; continue; } if (*(USHORTPTR)cptr == '7m') { /* reverse */ cptr += 2; screen[(row*width)+col] |= ATTR_REVS; continue; } if (*(USHORTPTR)cptr == '8m') { /* reverse */ cptr += 2; screen[(row*width)+col] |= ATTR_INVS; continue; } if (*(USHORTPTR)cptr == '0J') { /* clear from current to end of screen */ cptr += 2; /* clear to end of line */ for (ccnt = col; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; /* then to end of screen */ for (rcnt = row+1; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; continue; } if (*(USHORTPTR)cptr == '1J') { /* clear start of screen to current */ cptr += 2; /* clear all rows up to the current */ for (rcnt = 0; rcnt < row; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; /* then clear from start of row up until the current col */ for (ccnt = 0; ccnt < col; ccnt++) screen[(rcnt*width)+ccnt] = 0; continue; } if (*(USHORTPTR)cptr == '2J') { /* clear screen */ cptr++; for (rcnt = 0; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; continue; } if (*(USHORTPTR)cptr == '0K') { /* clear to end of line */ cptr += 2; for (ccnt = col; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; continue; } if (*(USHORTPTR)cptr == '1K') { /* clear from start of line to current */ cptr += 2; for (ccnt = 0; ccnt < col; ccnt++) screen[(rcnt*width)+ccnt] = 0; continue; } if (*cptr == 'J') { /* clear screen */ cptr++; for (rcnt = 0; rcnt < page; rcnt++) for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; continue; } if (*cptr == 'K') { /* clear current line */ cptr++; for (ccnt = 0; ccnt < width; ccnt++) screen[(rcnt*width)+ccnt] = 0; continue; } if (*cptr == 'H') { /* home */ cptr++; row = col = 0; continue; } if (*(ULONGPTR)cptr == '0;0H' || *(ULONGPTR)cptr == '0;0f') { /* home */ cptr += 4; row = col = 0; continue; } if (isdigit(*cptr)) { rcnt = ccnt = atoi(cptr); for (aptr = cptr; isdigit(*aptr) && aptr < czptr; aptr++); if (*cptr == 'A') { /* move cursor up */ if (row - rcnt >= 0) row -= rcnt; continue; } if (*cptr == 'B') { /* move cursor down */ if (row + rcnt < page) row += rcnt; continue; } if (*cptr == 'C') { /* move cursor right */ if (col + ccnt < width) col += ccnt; continue; } if (*cptr == 'D') { /* move cursor right */ if (col - ccnt >= 0) col -= ccnt; continue; } if (*cptr == 'E') { /* moves cursor to beginning of lines down */ if (row + rcnt < page) { row += rcnt; col = 0; } continue; } if (*cptr == 'F') { /* moves cursor to beginning of lines up */ if (row + rcnt >= 0) { row += rcnt; col = 0; } } if (*cptr == 'G') { /* move cursor to col */ if (ccnt >= 0 && ccnt < width) col = ccnt; continue; } if (*cptr == 's') { /* save cursor */ if (csave < CSAVE_MAX) { csave++; rowsave[csave] = row; colsave[csave] = col; } continue; } if (*cptr == 'u') { /* restore cursor */ if (csave >= 0) { row = rowsave[csave]; col = colsave[csave]; csave--; } continue; } /* drop through */ } if (isdigit(*cptr)) { rcnt = atoi(cptr); for (aptr = cptr; isdigit(*aptr) && aptr < czptr; aptr++); if (*aptr == ';' && isdigit(*(aptr+1))) { ccnt = atoi(aptr+1); for (aptr++; isdigit(*aptr) && aptr < czptr; aptr++); if (*aptr == 'H' || *aptr == 'f') { /* move to row and col */ cptr = aptr + 1; if (rcnt < page) row = rcnt; if (ccnt < width) col = ccnt; continue; } } /* drop through */ } /* unknown CSI */ if (dbugStream) fprintf (stdout, "USCI %08.08x%08.08x\n", *(ULONGPTR)cptr, *(ULONGPTR)(cptr+4)); screen[(row*width)+col] |= ATTR_UCSI; cptr++; continue; } /* some (other) control char */ if (*cptr == '\r') { col = 0; cptr++; continue; } if (*cptr == '\n') { if (row+1 < page) row++; cptr++; continue; } if (*cptr == '\f') { if (row+1 < page) row++; cptr++; continue; } if (*cptr == '\v') { if (row+1 < page) row++; cptr++; continue; } if (*cptr == '\t') { for (tab = 8; tab > 0; tab--) { screen[(row*width)+col] = ' '; if (col+1 < width) col++; } cptr++; continue; } /* ignore this control char */ cptr++; if (row >= page || col >= width) { fprintf (stdout, "Status: 502\r\nContent-Type: text/plain\r\n\r\n\ BUGCHECK: terminal %dx%d screen %dx%d\n", width, page, col, row); exit (SS$_ABORT); } } aptr = HtmlRead (screen, width, page); return (aptr); } /*****************************************************************************/ /* */ static char* HtmlRead (ulong *screen, int width, int page) { static char ScreenBuffer[2][4096]; static int blink, bold, invisible, reverse, ucsi, under; static int BufferNumber, ScreenBufferLength, ScreenBufferSize = sizeof(ScreenBuffer[0]) - 16; uint att, ch, col, color, rcnt, row; char *aptr, *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (dbugStream) { fprintf (stdout, "HtmlRead() %dx%d\n", width, page); #if 0 for (row = 0; row < page; row++) { fprintf (stdout, "%02d: ", row); for (col = 0; col < width; col++) fprintf (stdout, "%08.08x ", screen[((row*width)+col)]); fputs ("\n", stdout); fflush (stdout); } fputs ("\n", stdout); #endif } bold = reverse = ucsi = under = 0; zptr = (sptr = aptr = ScreenBuffer[BufferNumber]) + ScreenBufferSize; for (row = 0; row < page; row++) { if (sptr >= zptr) break; if (THIS_CHAR(screen[((row*width)+0)])) { /* subsequent row so new line */ if (row) sptr += sprintf (sptr, "
"); } else { /* empty line, look ahead for a following non-empty */ for (rcnt = row+1; rcnt < page; rcnt++) if (THIS_CHAR(screen[((rcnt*width)+0)])) break; /* if found a non-empty this is a new line */ if (rcnt < page) sptr += sprintf (sptr, "
"); continue; } for (col = 0; col < width; col++) { if (sptr >= zptr) break; ch = screen[((row*width)+col)]; if (!ch) continue; att = THIS_ATTR(ch); ch = THIS_CHAR(ch); if (ucsi) { sptr += sprintf (sptr, ""); ucsi = 0; } if (att) { if (att & ATTR_UCSI) sptr += (ucsi = sprintf (sptr, "")); if (!bold && (att & ATTR_BOLD)) sptr += (bold = sprintf (sptr, "")); if (bold && (att & ATTR_BOL0)) { sptr += sprintf (sptr, ""); bold = 0; } if (!under && (att & ATTR_ULIN)) sptr += (under = sprintf (sptr, "")); if (under && (att & ATTR_ULI0)) { sptr += sprintf (sptr, ""); under = 0; } if (!under && (att & ATTR_BLNK)) sptr += (blink = sprintf (sptr, "")); if (under && (att & ATTR_BLN0)) { sptr += sprintf (sptr, ""); blink = 0; } if (!reverse && (att & ATTR_REVS)) sptr += (reverse = sprintf (sptr, "")); if (reverse && (att & ATTR_REV0)) { sptr += sprintf (sptr, ""); reverse = 0; } if (!invisible && (att & ATTR_INVS)) sptr += (reverse = sprintf (sptr, "")); if (invisible && (att & ATTR_INV0)) { sptr += sprintf (sptr, ""); invisible = 0; } if (att & ATTR_RSET) { if (bold) sptr += sprintf (sptr, ""); if (under) sptr += sprintf (sptr, ""); if (blink) sptr += sprintf (sptr, ""); if (reverse) sptr += sprintf (sptr, ""); if (invisible) sptr += sprintf (sptr, ""); blink = bold = invisible = reverse = under = 0; } } if (ch >= 32) { if (ch == '<') sptr += sprintf (sptr, "<"); else if (ch == '>') sptr += sprintf (sptr, ">"); else if (ch == '&') sptr += sprintf (sptr, "&"); else if (ch == '\"') sptr += sprintf (sptr, """); else if (ch == '\'') sptr += sprintf (sptr, "'"); else *sptr++ = ch; } } } *sptr = '\0'; if (sptr >= zptr) { fprintf (stdout, "Status: 502\r\nContent-Type: text/plain\r\n\r\n\ BUGCHECK: HTML buffer %d overflow\n", sizeof(ScreenBuffer[0])); exit (SS$_ABORT); } return (aptr); } /*****************************************************************************/ /* A unique combination of screen characters that signals a new screen. In this case: Return zero for not the sentinal, one if it is. */ static int ScreenSentinal (char *cptr) { /*********/ /* begin */ /*********/ if (memcmp (cptr, "\x1b[0;0H", 6)) return (0); cptr += 6; if (*cptr != ' ') return (0); while (*cptr == ' ') cptr++; if (memcmp (cptr, "\x1b[1m", 4)) return (0); return (1); } /*****************************************************************************/