/* HTBrowse.c ** HYPERTEXT BROWSER FOR DUMB TERMINALS ** ** (c) COPRIGHT MIT 1995. ** Please first read the full copyright statement in the file COPYRIGH. ** @(#) $Id: HTBrowse.c,v 1.183 1999/06/24 19:41:04 frystyk Exp $ ** ** Authors: ** NP: Nicola Pellow Tech.Student CERN 1990-91 ** TBL: Tim Berners-Lee CERN (timbl@w3.org) ** JFG: Jean-Francois Groff, Cooperant CERN 1991-92 (jfg@dxcern.cern.ch) ** DR: Dudu Rashty +972-2-584848 ** MD: Mark Donszelmann, DELPHI CERN, (duns@vxdeop.cern.ch) ** HFN: Henrik Frystyk Nielsen, MIT, (frystyk@w3.org) ** ** History: ** ** 4 Dec 90: Written from scratch (NP) ** 11 Feb 91: Code written by TBL so that the browser could be linked with ** code produced by Bernd Pollermann, enabling access to the ** data on CERNVM. This involved changing the code to handle file ** numbers rather than file pointers. ** 18 Mar 91: The feature of history mechanism was included, enabling a ** record of previous nodes visited to be kept. ** 6 Apr 91: When a node is accessed, it is immediately read into a ** buffer, in an unformatted state, as soon as the connection is ** made, so that the server is freed as quickly as possible. ** The program now also uses the additional modules HTBufferFile.c ** and HTBufferFile.h. ** 17 Apr 91: Can be used on machines running ANSI C and ordinary C. ** 10 May 91: Formatted text is stored in a linked list buffer which allows ** scrolling and better page breaks in the middle of text. ** Code incorporated by Tim BL, to enable anonymous FTP. ** 21 May 91: Accepts various parameters on the command line. ** 19 Aug 91: Currently available in Unix, VAX/VMS and MVS environments. ** 21 Nov 91: Character grid uses new architecture. (TBL) ** added -w option, new commands, print, ** ... See Features.html for further details ** 16 Jan 92: Put in VIOLA-compatible options - see \017 characters. ** \017 and \016 bracket user-selectable input. ** 27 Feb 92: New handling of user input, enhanced command syntax. (JFG) ** 18 May 92: PS command see ifdef SLAVE_PRINTER (DR/TBL) ** 6 Oct 92: Painful recovery from someone(?)'s attept to pretty print.(TBL) ** Please see coding style guide before changing indentation etc! ** Mar 93: Force on HTFile's HTDirAccess and HTDirReadme flags. ** 3 Nov 93: (MD) Changed vms into VMS ** (MD) Assigne OUTPUT in main, not at initialize (VMS only) ** ** Compilation-time macro options ** ** REF_MARK Printf string to be used for printing anchor numbers ** END_MARK String to be used to denote the end of a document */ #include "WWWLib.h" #include "WWWMIME.h" #include "WWWCache.h" #include "WWWApp.h" #include "WWWTrans.h" #include "WWWInit.h" #include "GridText.h" /* Hypertext definition */ #include "HTBrowse.h" /* Things exported, short names */ #include "CSLApp.h" /* the PICApp library should provide everything the app needs */ #include "CSLL.h" #include "CSUser.h" #include "CSUsrLst.h" #ifndef W3C_VERSION #define W3C_VERSION "Unspecified" #endif #define APP_NAME "W3CLineMode" #define APP_VERSION W3C_VERSION /* Default page for "Manual" command */ #define MANUAL "http://www.w3.org/LineMode/User/QuickGuide.html" /* Default page for "help" command */ #define C_HELP "http://www.w3.org/LineMode/User/Commands.html" /* Default page for "-help" command line option */ #define L_HELP "http://www.w3.org/LineMode/User/CommandLine.html" #define DEFAULT_OUTPUT_FILE "www.out" #define DEFAULT_RULE_FILE "www.conf" #define DEFAULT_LOG_FILE "www.log" #define DEFAULT_MEMLOG "www.mem" #define DEFAULT_USERLIST_FILE "PICSusrs.html" #if 0 #define HT_MEMLOG #endif #define PROMPT "%s" #define REF_MARK "[%d]" #define END_MARK "[END]" #define SCREEN_WIDTH 79 /* Default width of the screen */ #define MIN_SCREEN_WIDTH 10 #define MAX_SCREEN_WIDTH 150 #define SCREEN_HEIGHT 24 /* Default number of lines */ #define MIN_SCREEN_HEIGHT 5 #define MAX_SCREEN_HEIGHT 200 #define MAX_HISTORY 20 /* Don't list more than this in list */ #define RESPONSE_LENGTH 1024 /* Maximum length of users response */ #define SHOW_MSG (WWWTRACE || HTAlert_interactive()) #define CHECK_INPUT(a, b) (!strncasecomp ((a), (b), strlen((b)))) #define DEFAULT_I_TIMEOUT 1000 /* Interactive timeout in millies */ #define DEFAULT_NI_TIMEOUT 10000 /* Non-interactive timeout in millies */ #define DEFAULT_FORMAT WWW_PRESENT #if defined(__svr4__) #define CATCH_SIG #endif /* HWL 18/7/94: applied patch from agl@glas2.glas.apc.org (Anton Tropashko) */ #ifdef CYRILLIC #include "a_stdio.h" struct ARc arc; #endif /* Screen size parameters */ PUBLIC int HTScreenWidth = SCREEN_WIDTH; /* By default */ PUBLIC int HTScreenHeight = -1; /* -1 = Undefined */ /* Anchor specific information */ PUBLIC BOOL display_anchors = YES; /* Show anchors in text? */ PUBLIC char * start_reference = NULL; /* Format for start anchor */ PUBLIC char * end_reference = REF_MARK; /* for end anchor */ PUBLIC char * reference_mark = "[%d] "; /* for reference lists */ PUBLIC char * end_mark = END_MARK; /* Format string for [End] */ typedef enum _LMFlags { LM_FILTER = 0x1, LM_REFS = 0x2, LM_REFORMAT = 0x4, LM_PREEMTIVE= 0x8 } LMFlags; struct _LineMode { HTRequest * console; /* For user input */ HTParentAnchor * anchor; HTList * active; /* List of acitve contexts */ HTList * presenters; HTHistory * history; /* History list */ char * cwd; /* Current dir URL */ char * rules; char * logfile; HTLog * log; char * outputfile; char * host; int trace; int timer; HTFormat format; /* Input format from console */ LMFlags flags; HTView * pView; char * userList; CSUser_t * pCSUser; BOOL noPICSPasswords; }; typedef enum _LMState { LM_UPDATE = 0x1, LM_NO_UPDATE= 0x2, LM_DONE = 0x4, LM_INACTIVE = 0x8, LM_IGNORE = 0x10 } LMState; typedef struct _Context { LMState state; HTRequest * request; LineMode * lm; HTParentAnchor * source; /* The source if we are using PUT or POST */ } Context; PRIVATE int scan_command (SOCKET s, void * param, HTEventType type); PRIVATE HTEvent * ConsoleEvent = NULL; #ifndef WWW_WIN_WINDOW PRIVATE FILE * OUTPUT = NULL; #endif PRIVATE InputParser_t parse_command; InputParser_t * PInputParser = &parse_command; /* Net callback handlers */ PRIVATE HTNetBefore MemoryCacheFilter; PRIVATE HTNetAfter terminate_handler; /* additions for browser update */ extern HText * HTMainText; extern HTParentAnchor * HTMainAnchor; extern void LMHText_beginElement (HText * text, int elem_num, const BOOL * present, const char ** value); extern void LMHText_endElement (HText * text, int elem_num); /* ------------------------------------------------------------------------- */ PUBLIC int HTWatch(int id, void * obj, const char * fmt, ...) { va_list pArgs; va_start(pArgs, fmt); fprintf(stderr, "id: %x obj: %p: ", id, obj); return vfprintf(stderr, fmt, pArgs); } /* Create a Context Object ** ----------------------- */ PRIVATE Context * Context_new (LineMode *lm, HTRequest *request, LMState state) { Context * me; if ((me = (Context *) HT_CALLOC(1, sizeof (Context))) == NULL) HT_OUTOFMEM("Context_new"); me->state = state; me->request = request; me->lm = lm; HTRequest_setContext(request, (void *) me); HTList_addObject(lm->active, (void *) me); return me; } /* Delete a Context Object ** ----------------------- */ PRIVATE BOOL Context_delete (Context * old) { HT_FREE(old); return YES; } PUBLIC LineMode * Context_getLineMode(HTRequest * request) { return ((Context *) HTRequest_context(request))->lm; } /* ** This function creates a new request object and adds it to the global ** list of active threads */ CSDisposition_callback PICSCallback; PRIVATE HTRequest * Thread_new (LineMode * lm, BOOL Interactive, LMState state) { HTRequest * newreq = HTRequest_new(); if (!lm) return NULL; Context_new(lm, newreq, state); if (Interactive) HTRequest_setConversion(newreq, lm->presenters, NO); if (lm->flags & LM_PREEMTIVE) HTRequest_setPreemptive(newreq, YES); HTRequest_addRqHd(newreq, HT_C_HOST); CSApp_registerReq(newreq, lm->pCSUser, PICSCallback, CSApp_callOnBad, (void *)lm); return newreq; } /* ** This function deletes all unused request objects */ PRIVATE void Thread_cleanup (LineMode * lm) { if (lm && lm->active) { HTList * cur = lm->active; Context * pres; while ((pres = (Context *) HTList_nextObject(cur))) { if (pres->state&LM_DONE && pres->state&LM_INACTIVE) { if ((HTList_removeObject(lm->active, pres)) == NO) HTTRACE(APP_TRACE, "NOT FOUND\n"); HTRequest_delete(pres->request); Context_delete(pres); cur = lm->active; } } } } /* ** This function deletes the whole list of active threads. */ PRIVATE void Thread_deleteAll (LineMode * lm) { if (lm && lm->active) { HTList * cur = lm->active; Context * pres; while ((pres = (Context *) HTList_nextObject(cur))) { if (pres->request) { HTRequest_delete(pres->request); Context_delete(pres); } } HTList_delete(lm->active); lm->active = NULL; } } /* Create a Line Mode Object ** ------------------------- */ PRIVATE LineMode * LineMode_new (void) { LineMode * me; if ((me = (LineMode *) HT_CALLOC(1, sizeof(LineMode))) == NULL) HT_OUTOFMEM("LineMode_new"); me->cwd = HTGetCurrentDirectoryURL(); me->active = HTList_new(); me->console = HTRequest_new(); Context_new(me, me->console, LM_UPDATE); me->trace = SHOW_ALL_TRACE; me->timer = -1; me->pCSUser = 0; if (!(me->pView = HTView_create("'nother Window", 25, 80, me))) return 0; return me; } /* Delete a Line Mode Object ** ------------------------- */ PRIVATE BOOL LineMode_delete (LineMode * lm) { if (lm) { Thread_deleteAll(lm); HTPresentation_deleteAll(lm->presenters); HTHistory_delete(lm->history); HT_FREE(lm->cwd); if (lm->log) HTLog_close(lm->log); if (lm->pCSUser) CSLoadedUser_remove(lm->pCSUser); HTView_destroy(lm->pView); #ifndef WWW_WIN_WINDOW if (OUTPUT && OUTPUT != stdout) fclose(OUTPUT); #endif HText_freeAll(); /* Free all loaded documents */ HT_FREE(lm); return YES; } return NO; } PUBLIC HTRequest * LineMode_getConsole(LineMode * pLm) { return pLm->console; } PUBLIC HTView * LineMode_getView(LineMode * pLm) { return pLm ? pLm->pView : 0; } PRIVATE BOOL LineMode_load(LineMode * lm, HTParentAnchor * anchor, char * url, BOOL preemptive, HTRequest ** pPReq) { char * fullURL; HTParentAnchor * pAnchor; *pPReq = Thread_new(lm, YES, LM_UPDATE); if (preemptive) HTRequest_setPreemptive(*pPReq, YES); if (anchor) return HTLoadRelative(url, anchor, *pPReq); fullURL = HTParse(url, lm->cwd, PARSE_ALL); pAnchor = (HTParentAnchor *) HTAnchor_findAddress(fullURL); HT_FREE(fullURL); return HTLoadAnchor((HTAnchor *) pAnchor, *pPReq); } PRIVATE void Cleanup (LineMode * me, int status) { if (HTAlert_interactive()) /* Terminate with a LF if not interactive */ OutputData(me->pView, "\n"); CSLoadedUser_deleteAll(); CSApp_unregisterApp(); LineMode_delete(me); HTProfile_delete(); #ifdef HT_MEMLOG HTMemLog_close(); #endif #ifdef VMS exit(status ? status : 1); #else exit(status ? status : 0); #endif } /* ** Get size of the output screen. Stolen from less. */ PRIVATE void scrsize (int * p_height, int * p_width) { #if defined(HAVE_IOCTL) && defined(HAVE_WINSIZE) && defined(TIOCGWINSZ) register char *s; /* int ioctl(); - henrik */ struct winsize w; if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_row > 0) *p_height = w.ws_row; else if ((s = getenv("LINES")) != NULL) *p_height = atoi(s); else *p_height = SCREEN_HEIGHT; if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_col > 0) *p_width = w.ws_col; else if ((s = getenv("COLUMNS")) != NULL) *p_width = atoi(s); else *p_width = 80; #else *p_height = SCREEN_HEIGHT; *p_width = SCREEN_WIDTH; #endif /* HAVE_IOCTL && HAVE_WINSIZE */ } #ifdef CATCH_SIG #include /* SetSignal ** This function sets up signal handlers. This might not be necessary to ** call if the application has its own handlers. */ PRIVATE void SetSignal (void) { /* On some systems (SYSV) it is necessary to catch the SIGPIPE signal ** when attemting to connect to a remote host where you normally should ** get `connection refused' back */ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { HTTRACE(APP_TRACE, "HTSignal.... Can't catch SIGPIPE\n"); } else { HTTRACE(APP_TRACE, "HTSignal.... Ignoring SIGPIPE\n"); } #ifdef HT_MEMLOG HTMemLog_flush(); #endif } #endif /* CATCH_SIG */ /* Print version information ** ------------------------- */ PRIVATE void VersionInfo (LineMode * lm) { HTView * pView = lm ? lm->pView : NULL; OutputData(pView, "\n\nW3C Reference Software\n\n"); OutputData(pView, "\tW3C Line Mode Browser version %s.\n", APP_VERSION); OutputData(pView, "\tW3C Reference Library version %s.\n\n",HTLib_version()); OutputData(pView, "Please send feedback to \n"); } /* Reference_List ** -------------- ** Print out a list of HyperText References from current document */ PRIVATE void Reference_List (LineMode * lm, BOOL titles) { int refs = HText_sourceAnchors(HTMainText); if (refs <= 0) { OutputData(lm->pView, "\n\nThere are no references from this document.\n\n"); } else { int cnt; OutputData(lm->pView, "\n*** References from this document ***\n"); for (cnt=1; cnt<=refs; cnt++) { HTLink * link = HTAnchor_mainLink((HTAnchor*)HText_childNumber(HTMainText,cnt)); HTAnchor * dest = HTLink_destination(link); HTParentAnchor * parent = HTAnchor_parent(dest); char * address = HTAnchor_address(dest); const char * title = titles ? HTAnchor_title(parent) : NULL; OutputData(lm->pView, reference_mark, cnt); OutputData(lm->pView, "%s%s\n", ((HTAnchor*)parent!=dest) && title ? "in " : "", (char *)(title ? title : address)); HT_FREE(address); } #ifndef WWW_WIN_WINDOW fflush(OUTPUT); #endif } } /* History_List ** ------------ ** Display a history list of nodes visited during the session. */ PRIVATE void History_List (LineMode * lm) { int current = HTHistory_position(lm->history); int max = HTHistory_count(lm->history); int cnt; OutputData(lm->pView, "\n Documents you have visited: "); if (max > MAX_HISTORY) { max = MAX_HISTORY; OutputData(lm->pView, "(truncated)\n"); } else OutputData(lm->pView, "\n"); for (cnt=1; cnt<=max; cnt++) { HTAnchor *anchor = HTHistory_list(lm->history, cnt); char *address = HTAnchor_address(anchor); HTParentAnchor *parent = HTAnchor_parent(anchor); const char *title = HTAnchor_title(parent); OutputData(lm->pView, "%s R %d\t%s%s\n", (cnt==current) ? "*" : " ", cnt, ((HTAnchor*)parent!=anchor) && title ? "in " : "", title ? title : address); HT_FREE(address); } OutputData(lm->pView, "\n"); } /* Prompt for answer and get text back. Reply text is either NULL on ** error or a dynamic string which the caller must HT_FREE. */ PRIVATE char * AskUser (HTRequest * request, const char * Msg, const char * deflt) { char buffer[200]; char *reply = NULL; HTPrint("%s ", Msg ? Msg : "UNKNOWN"); if (deflt) HTPrint("(RETURN for [%s]) ", deflt); #ifndef NO_STDIO if (!fgets(buffer, 200, stdin)) return NULL; /* NULL string on error, Henrik */ buffer[strlen(buffer)-1] = '\0'; /* Overwrite newline */ if (*buffer) StrAllocCopy(reply, buffer); else if (deflt) StrAllocCopy(reply, deflt); #endif return reply; } PRIVATE BOOL confirm (HTRequest * request, const char * Msg) { char response[4]; HTPrint("%s (y/n) ", Msg ? Msg : "UNKNOWN"); #ifndef NO_STDIO if (fgets(response, 4, stdin)) /* get reply, max 3 chars */ #endif { char *ptr = response; while (*ptr) { if (*ptr == '\n') { *ptr = '\0'; break; } *ptr = TOUPPER(*ptr); ptr++; } return (!strcmp(response, "YES") || !strcmp(response, "Y")) ? YES : NO; } return NO; } /* MakeCommandLine ** --------------- ** Generate the Prompt line and flush it to the user */ PRIVATE void MakeCommandLine (LineMode * lm, BOOL is_index) { /* First Line */ if (HTAnchor_hasChildren(HTMainAnchor)) { int refs = HText_sourceAnchors(HTMainText); if (refs>1) OutputData(lm->pView, "1-%d, ", refs); else OutputData(lm->pView, "1, "); } if (HText_canScrollUp(HTMainText)) { OutputData(lm->pView, "Top, "); OutputData(lm->pView, "Up, "); } if (HText_canScrollDown(HTMainText)) { OutputData(lm->pView, "BOttom, "); OutputData(lm->pView, "Down or for more,"); } /* Second Line */ OutputData(lm->pView, "\n"); if (HTHistory_canBacktrack(lm->history)) OutputData(lm->pView, "Back, "); if (HTHistory_canForward(lm->history)) OutputData(lm->pView, "Forward, "); if (is_index) OutputData(lm->pView, "FIND , "); OutputData(lm->pView, "Quit, or Help: "); fflush(stdout); /* For use to flush out the prompt */ return; } /* ** PUT the current document to the destination specified by the user ** Returns the result of the load function. */ PRIVATE int PutAnchor (LineMode * lm, HTRequest * request) { char * dest = AskUser(request, "Destination: ", NULL); /* ** If user has typed a destination then create a new request object and ** start the PUT operation. The destination is taken relative to the ** current location. */ if (dest) { HTRequest * new_request = Thread_new(lm, YES, LM_UPDATE); HTPutDocumentRelative(HTMainAnchor, dest, HTMainAnchor, new_request); HT_FREE(dest); } return YES; } /* ** Delete the current document. */ PRIVATE int DeleteAnchor (LineMode * lm, HTRequest * request) { int status = HT_INTERNAL; BOOL doit = confirm(request, "Delete the current document?"); if (doit) { HTRequest * new_request = Thread_new(lm, YES, LM_UPDATE); status = HTDeleteAnchor((HTAnchor *) HTMainAnchor, new_request); } return status; } /* ** This function puts up a stream to a file in order to save a document. This ** is activated by '>', '>>' or '>!' from the prompt line. */ PRIVATE BOOL SaveOutputStream (HTRequest * req, char * This, char * Next) { FILE *fp; char *fname; char *fmode; /* Checks if file exists. Can be overruled by using '>!' */ if (*(This+1) == '>') { /* Append to file */ fmode = "ab"; fname = *(This+2) ? (This+2) : Next; } else if (*(This+1) == '!') { fmode = "wb"; /* Overwrite file */ fname = *(This+2) ? (This+2) : Next; } else { /* File name follows */ fmode = "wb"; fname = *(This+1) ? (This+1) : Next; if (fname) { /* See if file exists */ if ((fp = fopen(fname, "r")) != NULL) { fclose(fp); if (!confirm(req, "File exists - overwrite?")) return NO; } } } if (!fname) /* No file name given */ return NO; if ((fp = fopen(fname, fmode)) == NULL) { if (SHOW_MSG) HTPrint("Can't access file (%s)\n", fname); return NO; } HTRequest_setOutputStream(req, HTFWriter_new(req, fp, NO)); if (SHOW_MSG) HTPrint("Saving to file `%s\'\n", fname); return (HTLoadAnchor((HTAnchor*) HTMainAnchor, req) != HT_WOULD_BLOCK); } CSError_t LoadedUsersCallback(CSUser_t * pCSUser, int index, void * pVoid) { LineMode * lm = (LineMode *)pVoid; OutputData(lm->pView, "%d: %s\n", index, CSUser_name(pCSUser)); return CSError_OK; } CSError_t UserListCallback(char * username, char * url, int index, void * pVoid) { LineMode * lm = (LineMode *)pVoid; OutputData(lm->pView, "%d: %s %s\n", index, username, url); return CSError_OK; } PRIVATE BOOL ShowPICSUsers(LineMode * lm) { OutputData(lm->pView, "Loaded users:\n"); CSLoadedUser_enum(&LoadedUsersCallback, (void *)lm); OutputData(lm->pView, "Listed users:\n"); CSUserList_enum(&UserListCallback, (void *)lm); return YES; } PRIVATE int PICS_userCallback(CSUser_t * pCSUser, void * pVoid) { LineMode * lm = (LineMode *)pVoid; char * userName; char * password = NULL; HTAlertCallback *cbf; int ret; HTAlertPar * reply; if (lm->noPICSPasswords) { lm->pCSUser = pCSUser; return 0; } if (!(cbf = HTAlert_find(HT_A_USER_PW))) return -1; reply = HTAlert_newReply(); userName = CSUser_name(pCSUser); if ((ret = (*cbf)(lm->console, HT_A_USER_PW, HT_MSG_NULL, userName, NULL, reply))) { userName = HTAlert_replyMessage(reply); password = HTAlert_replySecret(reply); } HTAlert_deleteReply(reply); if (!ret) { HTPrint("PICS set user *canceled*.\n"); return -1; } ret = -1; if (!userName) HTPrint("PICS cannot set to no name.\n"); else if (CSUser_checkPassword(pCSUser, password) == NO) HTPrint("PICS password was not valid.\n"); else { ret = 0; lm->pCSUser = pCSUser; } HT_FREE(userName); HT_FREE(password); return ret; } PRIVATE BOOL SetPICSUser(LineMode * lm, char * userName) { char * password = NULL; HTAlertCallback *cbf; BOOL ret; HTAlertPar * reply; if (!(cbf = HTAlert_find(HT_A_USER_PW))) return NO; reply = HTAlert_newReply(); if ((ret = (*cbf)(lm->console, HT_A_USER_PW, HT_MSG_NULL, userName, NULL, reply))) { userName = HTAlert_replyMessage(reply); password = HTAlert_replySecret(reply); } HTAlert_deleteReply(reply); if (!ret) { HTPrint("PICS set user *canceled*.\n"); return NO; } ret = NO; if (!userName) HTPrint("Canceled.\n"); else if (!(lm->pCSUser = CSApp_registerUserByName(userName, password))) { char * url; if ((url = CSUserList_findURL(userName)) == NULL) HTPrint("PICS user \"%s\" is unknown.\n", userName); else if (!CSLoadedUser_load(url, lm->cwd)) HTPrint("Can't load PICS user \"%s\".\n", userName); else if ((CSLoadedUser_find(userName)) == NO) HTPrint("PICS user \"%s\" not found in \"%s\".\n", userName, url); else if (!(lm->pCSUser = CSApp_registerUserByName(userName, password))) HTPrint("Failed to set PICS user to \"%s\".\n", userName); ret = YES; HT_FREE(url); } HT_FREE(userName); HT_FREE(password); return ret; } PRIVATE BOOL LoadPICSUser(LineMode * lm, char * url) { CSUser_t * pCSUser; if (!(pCSUser = CSLoadedUser_load(url, lm->cwd))) return NO; lm->pCSUser = pCSUser; return YES; } CSError_t PICSCallback(HTRequest* pReq, CSLabel_t * pCSLabel, CSUser_t * pCSUser, CSError_t disposition, void * pVoid) { LineMode * lm = (LineMode *)pVoid; char * mesg; switch (disposition) { case CSError_RATING_RANGE: { char * labelStr = CSLabel_getRatingStr(pCSLabel); char * userStr = CSUser_getRatingStr(pCSUser); char * anchorStr = HTAnchor_address((HTAnchor*)HTRequest_anchor(pReq)); OutputData(lm->pView, "PICS user %s is not allowed to see document: %s\n", CSUser_name(pCSUser), anchorStr); OutputData(lm->pView, " %s's \"%s\" rating for service %s (%s) did not include %s\n", CSUser_name(pCSUser), CSLabel_getRatingName(pCSLabel), CSLabel_getServiceName(pCSLabel), userStr, labelStr); HT_FREE(userStr); HT_FREE(labelStr); HT_FREE(anchorStr); } return disposition; case CSError_BUREAU_NONE: mesg="label bureau was not contacted"; break; case CSError_RATING_VALUE: mesg="value"; break; case CSError_RATING_MISSING: mesg="rating not found"; break; case CSError_SINGLELABEL_MISSING: mesg="no single-labels found"; break; case CSError_SERVICE_MISSING: mesg="service not available";break; case CSError_SERVICE_NONE: mesg="no services available for document"; break; case CSError_RATING_NONE: mesg="no ratings in a service"; break; case CSError_BAD_DATE: mesg="labels were out of date"; break; default:HTPrint("PICSCallback: odd error code: %d\n", disposition); return disposition; } OutputData(lm->pView, "PICS disallowed document: %s\n", mesg); return disposition; } /* ------------------------------------------------------------------------- */ /* EVENT FUNCTIONS */ /* ------------------------------------------------------------------------- */ /* parse_command ** ------------ ** Given the user's input, deal with it as necessary. ** Any Command which works returns from the routine. If nothing ** works then a search or error message down at the bottom. ** ** Returns HT_ERROR Error has occured or we quit ** HT_OK Call back was OK */ PRIVATE int parse_command (char* choice, SOCKET s, HTRequest *req, HTEventType type) { char * the_choice=NULL; /* preserved user command */ char * token=NULL; /* First word of command */ char * this_command; /* token and following */ char * next_word; /* Second word */ char * other_words; /* Second word and following */ BOOL is_index = HTAnchor_isIndex(HTMainAnchor); BOOL found = YES; BOOL OutSource = NO; /* Output source, YES/NO */ int status = YES; HTRequest * cur_req = NULL; Context * cur_context = NULL; LineMode * lm = NULL; cur_req = req; cur_context = (Context *) HTRequest_context(req); lm = cur_context->lm; StrAllocCopy (the_choice, choice); /* Remember it as is, */ if (*the_choice && the_choice[strlen(the_choice)-1] == '\n') /* final \n */ the_choice[strlen(the_choice)-1] = '\0'; token = strtok (choice, " \t\n\r"); /* Tokenize user input */ this_command = the_choice; if (token) { next_word = strtok (NULL, " \t\n\r"); other_words = the_choice + (next_word - choice); } else goto down; /* Empty input : scroll down */ /* Process Command */ loop: switch (TOUPPER(*token)) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int ref_num; sscanf(token,"%d",&ref_num); if (ref_num>0 && ref_num<=HText_sourceAnchors(HTMainText)) { HTChildAnchor *source = HText_childNumber(HTMainText, ref_num); if (source) { HTLink * link = HTAnchor_mainLink((HTAnchor *) source); HTAnchor * destination = HTLink_destination(link); req = Thread_new(lm, YES, LM_UPDATE); /* Continous browsing, so we want Referer field */ HTRequest_setParent(req, HTAnchor_parent((HTAnchor*)source)); HTLoadAnchor(destination, req); } else { status = NO; /* No anchor */ } } else { if (SHOW_MSG) HTPrint("Warning: Invalid Reference Number: (%d)\n", ref_num); } } break; case 'B': if (CHECK_INPUT("BACK", token)) { /* Return to previous node */ if (HTHistory_canBacktrack(lm->history)) { req = Thread_new(lm, YES, LM_NO_UPDATE); status = HTLoadAnchor(HTHistory_back(lm->history), req); } else { OutputData(lm->pView, "\nThis is the first document in history list\n"); } } else if (CHECK_INPUT("BOTTOM", token)) { /* Scroll to bottom */ HText_scrollBottom(HTMainText); } else if (CHECK_INPUT("BYE", token)) { /* Quit program? */ HTNet_killAll(); /* Kill all requests */ status = NO; } else found = NO; break; case 'C': #ifdef HAVE_CHDIR if (CHECK_INPUT("CD", token)) { /* Change working directory ? */ goto lcd; } else #endif /* HAVE_CHDIR */ if (CHECK_INPUT("CLEAR", token)) { /* Clear History list */ HTHistory_removeFrom(lm->history, 1); } else found = NO; break; case 'D': if (CHECK_INPUT("DELETE", token)) { /* DELETE */ status = DeleteAnchor(lm, req); } else if (CHECK_INPUT("DOWN", token)) { /* Scroll down one page */ down: if (HText_canScrollDown(HTMainText)) HText_scrollDown(HTMainText); } else found = NO; break; case 'E': if (CHECK_INPUT("EDIT", token)) { status = PutAnchor(lm, req); } else if (CHECK_INPUT("EXIT", token)) { /* Quit program? */ HTNet_killAll(); /* Kill all requests */ status = NO; } else found = NO; break; case 'F': /* Keyword search ? */ if (is_index && CHECK_INPUT("FIND", token)) { find: { if (next_word) { req = Thread_new(lm, YES, LM_UPDATE); status = HTSearchString(other_words, (HTAnchor *) HTMainAnchor, req); } } } else if (CHECK_INPUT("FLUSH", token)) { /* Flush file cache */ if (confirm(req, "Flush Persistent Cache?") == YES) HTCache_flushAll(); } else if (CHECK_INPUT("FORWARD", token)) { if (HTHistory_canForward(lm->history)) { req = Thread_new(lm, YES, LM_NO_UPDATE); status = HTLoadAnchor(HTHistory_forward(lm->history), req); } else { OutputData(lm->pView, "\nThis is the last document in history list.\n"); } } else found = NO; break; case 'G': if (CHECK_INPUT("GOTO", token)) { /* GOTO */ if (next_word) status = LineMode_load(lm, HTMainAnchor, next_word, NO, &req); } else found = NO; break; case '?': status = LineMode_load(lm, HTMainAnchor, C_HELP, YES, &req); break; case 'H': if (CHECK_INPUT("HELP", token)) { /* help menu, ..*/ status = LineMode_load(lm, HTMainAnchor, C_HELP, YES, &req); } else if (CHECK_INPUT("HOME", token)) { /* back HOME */ if (!HTHistory_canBacktrack(lm->history)) { HText_scrollTop(HTMainText); } else { req = Thread_new(lm, YES, LM_NO_UPDATE); status = HTLoadAnchor(HTHistory_find(lm->history, 1), req); } } else found = NO; break; case 'K': /* Keyword search ? */ if (is_index && CHECK_INPUT("KEYWORDS", token)) { goto find; } else found = NO; break; case 'L': if (CHECK_INPUT("LIST", token)) { /* List of references ? */ Reference_List(lm, !OutSource); } #ifdef HAVE_CHDIR else if (CHECK_INPUT ("LCD", token)) { /* Local change dir ? */ lcd: if (!next_word) { /* Missing argument */ OutputData(lm->pView, "\nName of the new local directory missing.\n"); } else if (chdir (next_word)) { /* failed : say why */ OutputData(lm->pView, "\n "); perror (next_word); } else { /* Success : display new local directory */ #ifdef HAVE_GETCWD OutputData(lm->pView, "\nLocal directory is now:\n %s\n", getcwd (choice, sizeof(choice))); #else #ifdef HAVE_GETWD OutputData(lm->pView, "\nLocal directory is now:\n %s\n", (char *) getwd (choice)); #else #error "This platform doesn't support getwd or getcwd" if (SHOW_MSG) HTPrint("This platform doesn't support getwd or getcwd\n"); #endif /* HAVE_GETWD */ #endif /* HAVE_GETCWD */ } } #endif /* HAVE_CHDIR */ else if (CHECK_INPUT("LUSER", token)) { /* List of references ? */ if (next_word) { LoadPICSUser(lm, next_word); } else { HTPrint("URL needed\n"); } } else found = NO; break; case 'M': if (CHECK_INPUT("MANUAL", token)) { /* Read User manual */ status = LineMode_load(lm, HTMainAnchor, MANUAL, YES, &req); } else found = NO; break; case 'O': if (CHECK_INPUT("OPTIONS", token)) { /* OPTIONS method */ req = Thread_new(lm, YES, LM_NO_UPDATE); status = HTOptionsAnchor((HTAnchor*) (HTMainAnchor ? HTMainAnchor:lm->anchor), req); } else found = NO; break; case 'P': if (CHECK_INPUT("POST", token)) { status = PutAnchor(lm, req); } #ifdef HAVE_SYSTEM else if (!lm->host && CHECK_INPUT("PRINT", token)) { char * address = HTAnchor_address((HTAnchor *) HTMainAnchor); char * command; char * tmplate = (char *) getenv("WWW_PRINT_COMMAND"); int result; if (!tmplate) tmplate = "www -n -na -p66 '%s' | lpr"; if ((command = (char *) HT_MALLOC(strlen(address)+strlen(tmplate)+20)) == NULL) HT_OUTOFMEM("command"); sprintf(command, tmplate, address); result = system(command); HT_FREE(address); HT_FREE(command); if (result) OutputData(lm->pView, " %s\n returns %d\n", command, result); } #endif /* this command prints the entire current text to the terminal's printer; at the end it displays the top of the text */ #ifdef SLAVE_PRINTER #define SLAVE_PRINTER_ON "\033\133\065\151" #define SLAVE_PRINTER_OFF "\033\133\064\151" else if (CHECK_INPUT("PS", token)) { OutputData(lm->pView, "%s",SLAVE_PRINTER_ON); OutputData(lm->pView, "\f"); /* Form feed for new page */ HText_scrollTop(HTMainText); while(HText_canScrollDown(HTMainText)) { HText_scrollDown(HTMainText); } OutputData(lm->pView, "\f"); /* Form feed for new page */ OutputData(lm->pView, "%s",SLAVE_PRINTER_OFF); HText_scrollTop(HTMainText); } #endif else if (CHECK_INPUT("PUT", token)) { status = PutAnchor(lm, req); } else found = NO; break; case 'Q': /* Quit program ? */ if (CHECK_INPUT("QUIT", token)) { /* JFG 9/7/92, following a complaint of 'q' mis-typed for '1'. JFG Then made optional because I hate it !!! TBL made it only affect remote logged on users. 921122 */ if (lm->host && (strcasecomp(token, "quit") != 0) ) { OutputData(lm->pView, "\n Please type \"quit\" in full to leave www.\n"); } else { HTNet_killAll(); /* Kill all requests */ status = NO; } } else found = NO; break; case 'R': if (CHECK_INPUT("RECALL", token)) { if (HTHistory_count(lm->history) <= 1) { OutputData(lm->pView, "\n No other documents to recall.\n"); } else { /* Previous node number exists, or does the user just */ /* require a list of nodes visited? */ if (next_word) { int cnt; if ((cnt = atoi(next_word)) > 0) { req = Thread_new(lm, YES, LM_NO_UPDATE); status = HTLoadAnchor(HTHistory_find(lm->history,cnt), req); } else { if (SHOW_MSG) HTPrint("Bad command (%s), for list of commands type help\n", this_command); } } else { History_List(lm); } } } else if (CHECK_INPUT("REFRESH", token)) { HText_setStale(HTMainText); /* Force refresh */ HText_refresh(HTMainText); /* Refresh screen */ } else if (CHECK_INPUT("REVALIDATE", token)) { req = Thread_new(lm, YES, LM_NO_UPDATE); /* ** Add a end-to-end validator to the request */ HTRequest_setReloadMode(req, HT_CACHE_END_VALIDATE); status = HTLoadAnchor((HTAnchor*) (HTMainAnchor ? HTMainAnchor : lm->anchor), req); } else if (CHECK_INPUT("RELOAD", token)) { req = Thread_new(lm, YES, LM_NO_UPDATE); /* ** Make sure that we do a complete flush of the cache */ HTRequest_setReloadMode(req, HT_CACHE_FLUSH); status = HTLoadAnchor((HTAnchor*) (HTMainAnchor ? HTMainAnchor : lm->anchor), req); } else found = NO; break; case 'S': /* TBL 921009 */ if (CHECK_INPUT("SOURCE", token)) { /* Apply to source */ if (next_word) { OutSource = YES; /* Load and print as source */ token = next_word; /* Move up one word */ next_word = strtok (NULL, " \t\n\r"); this_command = the_choice + (token - choice); other_words = the_choice + (next_word - choice); goto loop; /* Go treat as before */ } } else if (CHECK_INPUT("SET", token)) { /* config */ HTList * rules = HTRule_global(); HTRule_parseLine(rules, other_words); } else found = NO; break; case 'T': if (CHECK_INPUT("TOP", token)) { /* Return to top */ HText_scrollTop(HTMainText); } else found = NO; break; case 'U': if (CHECK_INPUT("UP", token)) { /* Scroll up one page */ HText_scrollUp(HTMainText); } else if (CHECK_INPUT("USER", token)) { if (next_word) { if (!strcasecomp(next_word, "?")) { ShowPICSUsers(lm); SetPICSUser(lm, 0); } else { SetPICSUser(lm, next_word); } } else { SetPICSUser(lm, 0); } } else found = NO; break; case 'V': if (CHECK_INPUT("VALIDATE", token)) { /* Cache validate */ req = Thread_new(lm, YES, LM_NO_UPDATE); /* ** Add a validator to the request */ HTRequest_setReloadMode(req, HT_CACHE_VALIDATE); status = HTLoadAnchor((HTAnchor*) (HTMainAnchor ? HTMainAnchor : lm->anchor), req); } else if (CHECK_INPUT("VERBOSE", token)) { /* Switch verbose mode */ WWWTRACE = WWWTRACE ? 0 : lm->trace; OutputData(lm->pView, "\n Verbose mode %s.\n", WWWTRACE ? "ON":"OFF"); } else if (CHECK_INPUT("VERSION", token)) { /* Version */ VersionInfo(lm); } else found = NO; break; case 'W': if (CHECK_INPUT("WHICH", token)) { /* Show title/URL of current page */ HTView * pView = lm ? lm->pView : NULL; char * current_address = HTAnchor_address((HTAnchor *) HTMainAnchor); const char * title = HTAnchor_title(HTMainAnchor); if (title) OutputData(pView, "\n\nYou are reading\n\t`%s\'\nwith address\n\t%s\n\n", title, current_address); } else found = NO; break; case 'Z': HText_setStale(HTMainText); /* Force refresh */ HText_refresh(HTMainText); /* Refresh screen */ HTNet_killAll(); /* Kill all requests */ break; case '>': if (!lm->host) { HText *curText = HTMainText; /* Remember current main vindow */ req = Thread_new(lm, NO, LM_NO_UPDATE); HTRequest_setReloadMode(req, HT_CACHE_FLUSH_MEM); if (OutSource) HTRequest_setOutputFormat(req, WWW_SOURCE); SaveOutputStream(req, token, next_word); HText_select(curText); } break; #ifdef GOT_PIPE case '|': if (!lm->host) { /* Local only!!!! */ char * address = HTAnchor_address((HTAnchor *) HTMainAnchor); char * command; int result; if ((command = (char *) HT_MALLOC(strlen(address) +strlen(this_command)+30)) == NULL) HT_OUTOFMEM("command"); sprintf(command, "www %s \"%s\" %s", OutSource ? "-source" : "-n -na -p", address,this_command); OutputData(lm->pView, "Command: %s\n", command); result = system(command); if (result) OutputData(lm->pView, " %s returns %d\n", command, result); HT_FREE(command); HT_FREE(address); } break; #endif #ifdef HAVE_SYSTEM case '!': if (!lm->host) { /* Local only! */ int result; if (SHOW_MSG) HTPrint("Executing `%s\'\n", this_command); result = system(strchr(this_command, '!') + 1); if (result) OutputData(lm->pView, " %s returns %d\n", strchr(this_command, '!') + 1, result); } break; #endif /* HAVE_SYSTEM */ default: found = NO; break; } /* Switch on 1st character */ if (!found) { if (is_index && *token) { /* No commands, search keywords */ next_word = other_words = this_command; found = YES; goto find; } else { if (SHOW_MSG) HTPrint("Bad command (%s), for list of commands type help\n", this_command); } } MakeCommandLine(lm, is_index); HT_FREE(the_choice); /* ** If we have created a new Request and is to update the history list then ** we can set the inactive bit on this request object. */ if (cur_req == req) cur_context->state |= LM_NO_UPDATE; else cur_context->state |= LM_INACTIVE; return (status==YES) ? HT_OK : HT_ERROR; } /* readConsole ** ----------- ** non-blocking read of the WIN32 console. EGP */ #ifdef WWW_WIN_CONSOLE PUBLIC BOOL readConsole(HANDLE conIn, char* buf, int len, int* pRed) { DWORD recordIndex, bufferIndex, toRead, red; PINPUT_RECORD pInput; /* grab the pending input records (keystrokes plus other garbage). */ GetNumberOfConsoleInputEvents(conIn, &toRead); if (len < (int)toRead) /* we'll get the rest on the next pass(es). */ toRead = len; if ((pInput = (PINPUT_RECORD) HT_MALLOC(toRead * sizeof(INPUT_RECORD))) == NULL) /* room for n input records */ return (FALSE); ReadConsoleInput(conIn, pInput, toRead, &red); for (recordIndex = bufferIndex = 0; recordIndex < red; recordIndex++) { /* grab all keydown events */ #if 1 KEY_EVENT_RECORD keyEvent = pInput[recordIndex].Event.KeyEvent; /* only used if EventType == KEY_EVENT */ if (pInput[recordIndex].EventType == KEY_EVENT && keyEvent.bKeyDown) { while (keyEvent.wRepeatCount && keyEvent.uChar.AsciiChar) { /* stuff the buffer with the keys */ buf[bufferIndex] = keyEvent.uChar.AsciiChar; if (buf[bufferIndex] == '\r') buf[bufferIndex] = '\n'; if (buf[bufferIndex] == '\b') OutputData(lm->pView, "\b "); OutputData(lm->pView, "%c", buf[bufferIndex]); bufferIndex++; keyEvent.wRepeatCount--; } } #else if (pInput[recordIndex].EventType == KEY_EVENT && pInput[recordIndex].Event.KeyEvent.bKeyDown) { while (pInput[recordIndex].Event.KeyEvent.wRepeatCount && pInput[recordIndex].Event.KeyEvent.uChar.AsciiChar) { /* stuff the buffer with the keys */ buf[bufferIndex] = pInput[recordIndex].Event.KeyEvent.uChar.AsciiChar; if (buf[bufferIndex] == '\r') buf[bufferIndex] = '\n'; if (buf[bufferIndex] == '\b') OutputData(lm->pView, "\b "); OutputData(lm->pView, "%c", buf[bufferIndex]); bufferIndex++; pInput[recordIndex].Event.KeyEvent.wRepeatCount--; } } #endif } HT_FREE(pInput); *pRed = bufferIndex; /* actual characters stuck into buffer */ return (TRUE); } #endif /* WWW_WIN_CONSOLE */ /* bufferInput ** ----------- ** Read available characters from buf into stat. buf maybe bigger or ** smaller than stat. */ PUBLIC int bufferInput (char* buf, int len, SOCKET s, HTRequest * req, HTEventType type) { static char stat[RESPONSE_LENGTH]; static int iStat = 0; static int ignoreNext = 0; int iBuf; for (iBuf = 0; iBuf < len; iBuf++) { switch (buf[iBuf]) { case '\r': case '\n': if (ignoreNext) ignoreNext = 0; else { int ret; stat[iStat] = 0; iStat = 0; if ((ret = (*PInputParser)(stat, s, req, type)) != HT_OK) return (ret); } break; case '\b': if (iStat) /* don't worry about ignoreNext as iStat will be 0*/ iStat--; break; default: if (!ignoreNext) stat[iStat++] = buf[iBuf]; } if (iStat == sizeof(stat)) { HTPrint("Read Console... BUFFER OVERRUN\n"); iStat = 0; ignoreNext = 1; } } return (HT_OK); } /* timeout_handler ** --------------- ** This function is registered to handle timeout in select eventloop */ PRIVATE int timeout_handler (SOCKET s, void * param, HTEventType type) { if (!HTAlert_interactive()) { HTRequest * req = (HTRequest *) param; Context * context = (Context *) HTRequest_context(req); LineMode * lm = context->lm; if (SHOW_MSG) HTPrint("Request timed out"); HTNet_killAll(); Cleanup(lm, -1); } if (HTNet_count() > 0) if (SHOW_MSG) HTPrint("."); return 0; } PRIVATE int scan_command (SOCKET s, void * param, HTEventType type) { HTRequest * req = (HTRequest *)param; /* buf happens to == eatText's buffer but not neccesary */ static char buf[RESPONSE_LENGTH]; #ifdef WWW_MSWINDOWS int red; int ret; #endif /* Handle any timeout here */ if (type == HTEvent_TIMEOUT) return timeout_handler (s, param, type); #ifdef WWW_MSWINDOWS while(1) { #ifdef WWW_WIN_CONSOLE if (!readConsole((HANDLE)s, buf, sizeof(buf), &red)) { HTTRACE(PROT_TRACE, "Read Console... READ ERROR\n"); return HT_ERROR; } #endif /* WWW_WIN_CONSOLE */ if (!red) return (HT_OK); ret = bufferInput(buf, red, s, req, type); if (ret != HT_OK) return (ret); } #else /* WWW_MSWINDOWS */ if (!fgets(buf, sizeof(buf), stdin)) /* Read User Input */ return HT_ERROR; /* Exit if EOF */ return ((*PInputParser)(buf, s, req, type)); #endif /* !WWW_MSWINOWS */ } /* terminate_handler ** ----------------- ** This function is registered to handle the result of the request */ PRIVATE int terminate_handler (HTRequest * request, HTResponse * response, void * param, int status) { Context * context = (Context *) HTRequest_context(request); LineMode * lm; BOOL is_index; if (!context) return HT_OK; lm = context->lm; if (context->state == LM_IGNORE) return HT_OK; if (CSApp_unregisterReq(request) == NO && lm->pCSUser) HTPrint("PICS request not found\n"); is_index = HTAnchor_isIndex(HTMainAnchor); if (status == HT_LOADED) { /* Should we output a command line? */ if (HTAlert_interactive()) { HText_setStale(HTMainText); MakeCommandLine(lm, is_index); } else { if (lm->flags & LM_REFS) Reference_List(lm, NO); Cleanup(lm, 0); } /* Record new history if we have not moved around in the old one */ if (context->state & LM_UPDATE) HTHistory_replace(lm->history, (HTAnchor *) HTMainAnchor); /* Now generate the new prompt line as a function of the result */ if (!HText_canScrollDown(HTMainText) && !HTAnchor_hasChildren(HTMainAnchor) && !is_index && (!HTHistory_canBacktrack(lm->history))) { return HT_OK; } } else { /* No page loaded so sit around and wait for a go command */ /* was MakeCommandLine(lm, is_index); */ /* ** stolen from above */ if (HTAlert_interactive()) { /* HText_setStale(HTMainText); */ MakeCommandLine(lm, is_index); } else { if (lm->flags & LM_REFS) Reference_List(lm, NO); Cleanup(lm, 0); } } context->state |= LM_DONE; Thread_cleanup(lm); if (!HTAlert_interactive()) Cleanup(lm, -1); return HT_OK; } /* ** Check the Memory Cache (History list) BEFORE filter ** --------------------------------------------------- ** Check if document is already loaded. The user can define whether ** the history list should follow normal expiration or work as a ** traditional history list where expired documents are not updated. ** We don't check for anything but existence proof of a document ** associated with the anchor as the definition is left to the application */ PRIVATE int MemoryCacheFilter (HTRequest * request, void * param, int mode) { HTReload validation = HTRequest_reloadMode(request); HTParentAnchor * anchor = HTRequest_anchor(request); void * document = HTAnchor_document(anchor); /* ** We only check the memory cache if it's a GET method */ if (HTRequest_method(request) != METHOD_GET) { HTTRACE(APP_TRACE, "Mem Cache... We only check GET methods\n"); return HT_OK; } /* ** If we are asked to flush the persistent cache then there is no reason ** to do anything here - we're flushing it anyway. Also if no document ** then just exit from this filter. */ if (!document || validation > HT_CACHE_FLUSH_MEM) { HTTRACE(APP_TRACE, "Mem Cache... No fresh document...\n"); return HT_OK; } /* ** If we have a document object associated with this anchor then we also ** have the object in the history list. Depending on what the user asked, ** we can add a cache validator */ if (document && validation != HT_CACHE_FLUSH_MEM) { HTParentAnchor * parent = HTRequest_anchor(request); HTChildAnchor * child = HTRequest_childAnchor(request); HText * document = HTAnchor_document(parent); HTTRACE(APP_TRACE, "Mem Cache... Document %p already in memory\n" _ document); /* ** Make sure that we have selected the HText object. This is normally ** done by the HText interface but must be repeated here. */ if (child && (HTAnchor *) child != (HTAnchor *) parent) HText_selectAnchor(document, child); else HText_select(document); return HT_LOADED; } return HT_OK; } /* ------------------------------------------------------------------------- */ /* MAIN PROGRAM */ /* ------------------------------------------------------------------------- */ int main (int argc, char ** argv) { int status = 0; int arg; /* Index into argv */ HTChunk * keywords = NULL; /* From command line */ int keycnt = 0; HTRequest * request = NULL; LineMode * lm; char * picsUser = NULL; #ifndef WWW_WIN_WINDOW OUTPUT = stdout; #endif /* Starts Mac GUSI socket library */ #ifdef GUSI GUSISetup(GUSIwithSIOUXSockets); GUSISetup(GUSIwithInternetSockets); #endif #ifdef __MWERKS__ /* STR */ InitGraf((Ptr) &qd.thePort); InitFonts(); InitWindows(); InitMenus(); TEInit(); InitDialogs(nil); InitCursor(); SIOUXSettings.asktosaveonclose = false; argc=ccommand(&argv); #endif /* HWL 18/7/94: patch from agl@glas2.glas.apc.org (Anton Tropashko) */ #ifdef CYRILLIC arc.locale=0; arc.encoding=0; arc.i_encoding=0; doinull(); #endif #ifdef HT_MEMLOG HTMemLog_open(DEFAULT_MEMLOG, 8192, YES); HTTraceData_setCallback(HTMemLog_callback); #endif /* Initiate W3C Reference Library with a client profile */ HTProfile_newClient(APP_NAME, APP_VERSION); /* It's confusing to have progress notofications in linemode browser */ HTAlert_deleteOpcode(HT_A_PROGRESS); /* Add the default HTML parser to the set of converters */ { HTList * converters = HTFormat_conversion(); HTMLInit(converters); } /* Create a new Line Mode object */ lm = LineMode_new(); request = Thread_new(lm, NO, LM_UPDATE); /* Scan command Line for parameters */ for (arg=1; arg filter */ if (argv[arg][1] == '\0') { lm->flags |= LM_FILTER; HTAlert_setInteractive(NO); /* non-interactive */ } else if (!strcmp(argv[arg], "-n")) { HTAlert_setInteractive(NO); /* from -- Initial represntation (only with filter) */ } else if (!strcmp(argv[arg], "-from")) { lm->format = (arg+1 < argc && *argv[arg+1] != '-') ? HTAtom_for(argv[++arg]) : WWW_HTML; HTAlert_setInteractive(NO); /* to -- Final representation */ } else if (!strcmp(argv[arg], "-to")) { HTFormat format = (arg+1 < argc && *argv[arg+1] != '-') ? HTAtom_for(argv[++arg]) : DEFAULT_FORMAT; HTRequest_setOutputFormat(request, format); HTAlert_setInteractive(NO); /* reformat html */ } else if (!strcmp(argv[arg], "-reformat")) { HTRequest_setOutputFormat(request, WWW_HTML); lm->flags |= LM_REFORMAT; HTAlert_setInteractive(NO); /* List References */ } else if (!strncmp(argv[arg], "-list", 5)) { lm->flags |= LM_REFS; HTAlert_setInteractive(NO); /* original output */ } else if (!strcmp(argv[arg], "-raw")) { HTRequest_setOutputFormat(request, WWW_RAW); HTAlert_setInteractive(NO); /* source please */ } else if (!strcmp(argv[arg], "-source")) { HTRequest_setOutputFormat(request, WWW_SOURCE); HTAlert_setInteractive(NO); /* HEAD method */ } else if (!strcasecomp(argv[arg], "-head")) { HTRequest_setMethod(request, METHOD_HEAD); HTRequest_setOutputFormat(request, WWW_MIME); HTAlert_setInteractive(NO); /* output filename */ } else if (!strcmp(argv[arg], "-o")) { lm->outputfile = (arg+1 < argc && *argv[arg+1] != '-') ? argv[++arg] : DEFAULT_OUTPUT_FILE; HTAlert_setInteractive(NO); /* print version and exit */ } else if (!strcmp(argv[arg], "-version")) { VersionInfo(lm); Cleanup(lm, 0); /* -? or -help: show the command line help page */ } else if (!strcmp(argv[arg],"-?") || !strcmp(argv[arg],"-help")) { lm->anchor = (HTParentAnchor *) HTAnchor_findAddress(L_HELP); keycnt = 1; #ifdef CYRILLIC /* HWL 18/7/94: applied patch from agl@glas2.glas.apc.org (Anton Tropashko) */ } else if (!strcmp(argv[arg], "-koi2alt")) { doia2k(); OutputData(lm->pView, "Ahak2a!"); #endif /* Page size */ } else if (!strncmp(argv[arg], "-p", 2)) { if (*(argv[arg]+2)) { if (sscanf(argv[arg]+2, "%d", &HTScreenHeight) < 1) HTScreenHeight = -1; else { if(HTScreenHeight < MIN_SCREEN_HEIGHT) HTScreenHeight = MIN_SCREEN_HEIGHT; if(HTScreenHeight > MAX_SCREEN_HEIGHT) HTScreenHeight = MAX_SCREEN_HEIGHT; } } else if (arg+1 < argc && *argv[arg+1] != '-') { if (sscanf(argv[++arg], "%d", &HTScreenHeight) < 1) HTScreenHeight = -1; else { if(HTScreenHeight < MIN_SCREEN_HEIGHT) HTScreenHeight = MIN_SCREEN_HEIGHT; if(HTScreenHeight > MAX_SCREEN_HEIGHT) HTScreenHeight = MAX_SCREEN_HEIGHT; } } /* Page width */ } else if (!strncmp(argv[arg], "-w", 2)) { if (*(argv[arg]+2)) { if (sscanf(argv[arg]+2, "%d", &HTScreenWidth) < 1) HTScreenWidth = SCREEN_WIDTH; } else if (arg+1 < argc && *argv[arg+1] != '-') { if (sscanf(argv[++arg], "%d", &HTScreenWidth) < 1) HTScreenWidth = SCREEN_WIDTH; } if(HTScreenWidth < MIN_SCREEN_WIDTH) HTScreenWidth = MIN_SCREEN_WIDTH; if(HTScreenWidth > MAX_SCREEN_WIDTH) HTScreenWidth = MAX_SCREEN_WIDTH; /* Telnet from */ } else if (!strcmp(argv[arg], "-h")) { if (arg+1 < argc && *argv[arg+1] != '-') { lm->host = argv[++arg]; /* Use host name */ HTLib_setSecure(YES); /* No easy access */ } /* log file */ } else if (!strcmp(argv[arg], "-l")) { lm->logfile = (arg+1 < argc && *argv[arg+1] != '-') ? argv[++arg] : DEFAULT_LOG_FILE; /* rule file */ } else if (!strcmp(argv[arg], "-r")) { lm->rules = (arg+1 < argc && *argv[arg+1] != '-') ? argv[++arg] : DEFAULT_RULE_FILE; /* timeout -- Change the default request timeout */ } else if (!strcmp(argv[arg], "-timeout")) { int timeout = (arg+1 < argc && *argv[arg+1] != '-') ? atoi(argv[++arg]) : -1; if (timeout > 0) lm->timer = timeout; /* PICS user */ } else if (!strcmp(argv[arg], "-u")) { picsUser = (arg+1 < argc && *argv[arg+1] != '-') ? argv[++arg] : "user"; } else if (!strcmp(argv[arg], "-unp")) { lm->noPICSPasswords = 1; #if 0 /* old version of user list file, still works when enabeled here */ /* PICS user list */ } else if (!strcmp(argv[arg], "-ul")) { picsUserList = (arg+1 < argc && *argv[arg+1] != '-') ? argv[++arg] : "users.url"; #endif /* PICS user list */ } else if (!strcmp(argv[arg], "-ul")) { lm->userList = (arg+1 < argc && *argv[arg+1] != '-') ? argv[++arg] : DEFAULT_USERLIST_FILE; /* PICS mode */ } else if (!strcmp(argv[arg], "-pics")) { lm->userList = DEFAULT_USERLIST_FILE; /* preemptive or non-preemptive access */ } else if (!strcmp(argv[arg], "-single")) { HTRequest_setPreemptive(request, YES); lm->flags |= LM_PREEMTIVE; /* Disconnected operation */ } else if (!strncmp(argv[arg], "-discon", 7)) { HTCacheMode_setDisconnected(HT_DISCONNECT_NORMAL); /* Disable the cache */ } else if (!strncmp(argv[arg], "-nocache", 8)) { HTCacheMode_setEnabled(NO); /* Handling of Expire (cache) */ } else if (!strncmp(argv[arg], "-x", 2)) { char *p = argv[arg]+2; for(;*p;p++) { switch (argv[arg][2]) { case 'i': HTCacheMode_setExpires(HT_EXPIRES_IGNORE); break; case 'n': HTCacheMode_setExpires(HT_EXPIRES_NOTIFY); break; case 'a': HTCacheMode_setExpires(HT_EXPIRES_AUTO); break; default: if (SHOW_MSG) HTPrint("Bad parameter (%s) for option -x\n", argv[arg]); break; } } /* Anchor format */ } else if (!strcmp(argv[arg], "-a")) { if (arg+1 < argc && *argv[arg+1] != '-') end_reference = argv[++arg]; /* New representation */ /* Anchor format */ } else if (!strcmp(argv[arg], "-ar")) { if (arg+1 < argc && *argv[arg+1] != '-') reference_mark = argv[++arg]; /* Change representation */ /* Anchor format */ } else if (!strcmp(argv[arg], "-as")) { if (arg+1 < argc && *argv[arg+1] != '-') start_reference = argv[++arg]; /* Change representation */ /* No anchors */ } else if (!strcmp(argv[arg], "-na")) { display_anchors = NO; #ifndef NO_DIR_OPTIONS } else if (!strncmp(argv[arg], "-d", 2)) { char *p = argv[arg]+2; for(;*p;p++) { switch (argv[arg][2]) { case 'r':HTFile_setDirReadme(HT_DIR_README_NONE); break; case 't':HTFile_setDirReadme(HT_DIR_README_TOP); break; case 'b':HTFile_setDirReadme(HT_DIR_README_BOTTOM);break; case 'n':HTFile_setDirAccess(HT_DIR_FORBID); break; case 's':HTFile_setDirAccess(HT_DIR_SELECTIVE); break; case 'y':HTFile_setDirAccess(HT_DIR_OK); break; default: if (SHOW_MSG) HTPrint("Bad parameter (%s) in -d option\n", argv[arg]); } } #endif #ifdef WWWTRACE /* trace flags */ } else if (!strncmp(argv[arg], "-v", 2)) { lm->trace = HTSetTraceMessageMask(argv[arg]+2); #endif } else { if (SHOW_MSG) HTPrint("Bad Argument (%s)\n", argv[arg]); } } else { /* If no leading `-' then check for main argument */ if (!keycnt) { char * ref = HTParse(argv[arg], lm->cwd, PARSE_ALL); lm->anchor = HTAnchor_parent(HTAnchor_findAddress(ref)); keycnt = 1; HT_FREE(ref); } else { /* Check for successive keyword arguments */ char *escaped = HTEscape(argv[arg], URL_XALPHAS); if (keycnt++ <= 1) keywords = HTChunk_new(128); else HTChunk_putc(keywords, ' '); HTChunk_puts(keywords, HTStrip(escaped)); HT_FREE(escaped); } } } #ifdef CATCH_SIG SetSignal(); #endif /* Set up HText interface callbacks */ if (!HText_registerCDCallback (LMHText_new, LMHText_delete)) { HTPrint("HText new/delete callback registration failed.\n"); exit (1); } if (!HText_registerTextCallback (LMHText_addText)) { HTPrint("HText addText callback registration failed.\n"); exit (2); } if (!HText_registerLinkCallback (LMHText_beginAnchor)) { HTPrint("HText link callback registration failed.\n"); exit (3); } if (!HText_registerElementCallback ( LMHText_beginElement, LMHText_endElement)) { HTPrint("HText element callback registration failed.\n"); exit (3); } /* Make home page address */ if (!lm->anchor) lm->anchor = HTHomeAnchor(); /* Do we need list of presenters? (external viewers) */ if (HTAlert_interactive()) { lm->presenters = HTList_new(); HTPresenterInit(lm->presenters); HTRequest_setConversion(request, lm->presenters, NO); } else { /* ** Add default content decoder. We insert a through line as it doesn't ** matter that we get an encoding that we don't know when not in ** interactive mode. In interactive mode, we ask the user. */ HTFormat_addCoding("*", HTIdentityCoding, HTIdentityCoding, 0.3); } if (HTScreenHeight == -1) { /* Default page size */ if (HTAlert_interactive()) scrsize(&HTScreenHeight, &HTScreenWidth); else HTScreenHeight = 999999; } /* Disable free directory browsing when using telnet host */ if (lm->host && HTFile_dirAccess() == HT_DIR_OK) HTFile_setDirAccess(HT_DIR_SELECTIVE); /* Open output file */ if (!HTAlert_interactive()) { #ifndef WWW_WIN_WINDOW if (lm->outputfile) { if ((OUTPUT = fopen(lm->outputfile, "wb")) == NULL) { if (SHOW_MSG) HTPrint("Can't open `%s'\\n", lm->outputfile); OUTPUT = stdout; } } HTRequest_setOutputStream(request, HTFWriter_new(request, OUTPUT, YES)); #endif /* ** To reformat HTML, just put it through a parser running ** into a regenerator tbl 940613 */ if (lm->flags & LM_REFORMAT) { HTStructured * html = HTMLGenerator(request, NULL, WWW_HTML, HTRequest_outputFormat(request), HTRequest_outputStream(request)); HTRequest_setOutputStream(request, SGML_new(HTML_dtd(), html)); } } /* Log file specifed? */ if (lm->logfile) { lm->log = HTLog_open(lm->logfile, YES, YES); if (lm->log) HTNet_addAfter(HTLogFilter, NULL, lm->log, HT_ALL, HT_FILTER_LATE); } /* Just convert formats */ if (lm->flags & LM_FILTER) { #ifdef STDIN_FILENO HTRequest_setAnchor(request, (HTAnchor *) lm->anchor); HTRequest_setPreemptive(request, YES); HTLoadSocket(STDIN_FILENO, request); #endif Cleanup(lm, 0); } /* Set timeout on sockets */ if (lm->timer < 0) { lm->timer = HTAlert_interactive() ? DEFAULT_I_TIMEOUT : DEFAULT_NI_TIMEOUT; } /* Set the DNS cache timeout */ HTDNS_setTimeout(3600); /* Rule file specified? */ if (lm->rules) { char * rules = HTParse(lm->rules, lm->cwd, PARSE_ALL); if (!HTLoadRules(rules)) if (SHOW_MSG) HTPrint("Can't access rules\n"); HT_FREE(rules); } /* Set up PICS machinary */ CSApp_registerApp(PICSCallback, CSApp_callOnBad, PICS_userCallback, (void *)lm); /* Add our own filter to update the history list */ HTNet_addBefore(MemoryCacheFilter, NULL, NULL, HT_FILTER_EARLY); HTNet_addAfter(terminate_handler, NULL, NULL, HT_ALL, HT_FILTER_LAST); /* Should we load a PICS user profile? */ if (picsUser && !LoadPICSUser(lm, picsUser)) HTPrint("Unable to load PICS user \"%s\".\n", picsUser); /* PICS user list specified? */ if (lm->userList) LoadPICSUser(lm, lm->userList); /* request was registered in Thread_new before command line parsing loaded the PICS user, so re-register to give it the correct user */ CSApp_unregisterReq(request); CSApp_registerReq(request, lm->pCSUser, PICSCallback, CSApp_callOnBad, (void *)lm); /* Start the request */ if (keywords) status = HTSearchAnchor(keywords, (HTAnchor *) lm->anchor, request); else status = HTLoadAnchor((HTAnchor *) lm->anchor, request); if (keywords) HTChunk_delete(keywords); if (status != YES) { if (SHOW_MSG) HTPrint("Couldn't load home page\n"); CSApp_unregisterDefaultUser(); CSApp_unregisterApp(); Cleanup(lm, -1); } /* Set up the rest if we are in interactive mode */ if (HTAlert_interactive()) { /* Start History manager */ lm->history = HTHistory_new(); /* ** Register STDIN as the user socket IF not STDIN is connected to ** /dev/null or other non-terminal devices */ ConsoleEvent = HTEvent_new(scan_command, lm->console, HT_PRIORITY_MAX, lm->timer); #ifdef STDIN_FILENO if (isatty(STDIN_FILENO)) { HTEventList_register(STDIN_FILENO, HTEvent_READ, ConsoleEvent); } #else HTEventList_register(GetStdHandle(STD_INPUT_HANDLE), HTEvent_READ, ConsoleEvent); #endif } /* Go into the event loop... */ HTEventList_loop(request); /* Only gets here if event loop fails */ Cleanup(lm, 0); return 0; }