/* outer.c outer_brev version 1.0 came from outer.c version 2.5, JGOFS 1.5 release (as of 10 Jan 98). Renamed back to outer as of summer 06, approx vers outer_brev 2.1. Seemed appropriate to set version of newly-returned outer higher than 2.5... Incompatibilities 1) outer_brev variable names may not include spaces or percent signs. 2) Non-numeric data values (including "nd") cannot be selected with numeric operators 3) When clicking on a datum to use it as a selection for the next level, comparisons are done as strings instead of numbers 4) Rely on program exit to free dynamically allocated memory */ #define OUTER_VERSION "outer version 3.4a 10 Aug 2011" /* 10 Aug 11 v 3.4a WJS Change name of ooserver from "Plotting and ..." to "Download and ..." [Begin outer 3.4a] 11 Aug 10 v 3.4 WJS Bug fix: code to remove empty levels apparently incorrect from inception (outer 3.0). Same levels of original object could appear on different levels of output object. Also, there could be data at a level w/o vars at that level. Ick! "Things worked first time through", so manifestation seems to be datasets truncated after said "first time through" [Begin outer 3.4] 5 Feb 10 v 3.3 WJS Put extended URL into OOserver link. Should allow OOserver to use the correct INFOSERVER [Begin outer 3.3] 21 May 09 v 3.2 WJS Bug fix: do something reasonable w/display of attribute lists in "flat" output. While at it, check buffer overflows, parametrize some stuff, and even do things a bit more efficiently [needs outer.h 1.5c] 15 May 09 v 3.2 WJS Get rid of precison-dependent stuff Make a global variable local Change required function from iovalreal_ to iovaldouble_ [needs jgofs_selection_parser 2.5] [needs jgofs_selection_tester 2.5] [needs varnames_in_sel 1.0] [needs parse.h 2.2] [no longer uses parse.c] 10 May 09 v 3.2 WJS Need a version of errn that emulates util's errn 18 Apr 09 v 3.2 WJS Add projections for vars in selections. This may result in reading levels beyond what user asked for, so we need to alter concept of "max level" yet again. Sigh Parametrize attribute delimiters. Fix bug that would have added empty attr lists. Never happened because we always have width= ... but outer doesn't know that. 3 Apr 09 v 3.2 WJS Mods for altered parse (a few funcs were added to parse_functions) Move commented out old parsing code to parse 1 Apr 09 v 3.2 WJS test now returns "Unknown" if the selection vars have not been read in at the time the test is done. Compensate here (This addresses the "not doesn't work" bug) [Needs parse 2.2] [Begin outer 3.2] 14 Feb 09 v 3.1a WJS Change entry points to parse. Reflects precision-dependent mess we got into when we moved the code out of here. Not clear if we are totally out of the woods if inner and outer both use parse and are compiled w/different values of USE_DOUBLE. Present guess: ugly but OK (if basic iovalreal_/iovaldouble_ stuff is OK w/mismatched USE_DOUBLE!) [Begin outer 3.1a] 17 Jan 08 v 3.1 WJS Deal w/possibility that Apache has &/or will change // to / in PATH_ INFOs. This affects both remote object specs and the directory and info server specs (and who knows what else). Try not to confuse // after http: w/ // in remote object & * server specs [Begin outer 3.1] 20 Jul 07 v 3.0b WJS Add &object_info and &method_info in a test mode, turned off by default. NOTE THIS NEW STUFF BREAKS THE jgof_read*.pl methods, which expect &v0 or &c as the first lines. Move parse, test, and the wildcard stuff out of here Change the way outer distinguishes between selections and projections Old code looked for special characters. New code assumes selection if candidate string is not a varname. Among other consequences, a misspelled projection will show up w/a selection error of some kind. However, w/new code, we don't need the list of special characters, and therefore don't need to worry about them being in quotes, etc. [Begin outer 3.0b] 23 Mar 07 v 3.0a WJS Bug fix: out_err had buffer overflow in prepping output message which caused error_ to segv in the free(outname) after it printed the message! Error happened when I indented a portion of the message and forgot to include the indentation chars in the buffer size Change another strcpy to COPY_INTO_FIXED_LEN_BUFFER [Begin outer 3.0a] 26 Oct 06 v 3.0 WJS Bug fix: forgot to remove terminating \n when level removed 20 Oct 06 v 3.0 WJS If a level is totally projected out, remove it from output object Only implemented for non-flat, non-html output. Flat is OK anyway; html has big question regarding to what user-requested level means. Consider a 2 level object whose 2nd level is a 1 level object Consider the object specified by projecting out level 0 of this object. Old behavior was that projected object had 2 levels, 1st of which had nothing in it. New behavior is that projected object has 1 level. A note in case it seems that outer should preserve level structure: outer always truncated max level if it was projected out (eg, 2 level object w/only level 0 vars projected in only output level 0). Therefore, outer never preserved the structure Logically, THIS BREAKS THINGS, not at the code level but at the level of "how many levels in this object?" etc Get get_logical_value from library. Change some strcpy/strcats to library length-checking versions. 8 Sep 06 v 3.0 WJS Move older comments out. Start calling this outer again, instead of outer_brev [Begin outer 3.0] *** End of outer_brev *** 12 Jul 06 v 2.1 WJS BACKGROUND_COLOR; DISPLAY_DATA_URL. Per Bob by request from NEC project Results in adding & tags even if new stuff isn't spec'd 20 Oct 05 v 2.1 WJS Bug fix: didn't properly handle escaped chars 6 Oct 05 v 2.1 WJS Add "like" string comparison function Add upper-cased version of string comparison functions to do case-blind comparisons parse used to add a $ to selection strings as a non-null to indicate string end. Seems to me that this would conflict if we actually searched for a $, so switch it to something binary (and check input string just in case) [Needs outer.h 1.4] [Begin 2.1] [Comments pre-v2.1 removed to outer_revision.doc. WJS 8 Sep 06] */ /* =========================================== Subroutines required: =========================================== */ int ioopen_(); /* int ioopen_(s,nparams,ntotal) char s[][]; int *nparams,*ntotal; s[0..nparams-1]: parameter strings. Inner sets s[j][0]=0 for any strings which it processes; others will be processed by outer. Thus selection/projections would normally be ignored by inner. nparams: number of parameter strings ntotal (returned): total number of variable names */ int ioreadrec_(); /* int ioreadrec_(level) int *level; Read record at appropriate level. Return 0 if end at that level. Return 1 if ok. */ void ioclose_(); /* ioclose_() Close files Note: this routine should NOT call error_ */ void iovaldouble_(); /* iovaldouble_(vn,df) int *vn; double *df; Return numeric double precision value (df) for variable indexed by vn. -9999. is returned if the datum is not numeric */ void iovalstr_(); /* iovalstr_(vn,tmp) int *vn; char *tmp; Return string value (tmp) for variable indexed by vn. */ int iovarlevel_(); /* int iovarlevel_(vn) int *vn; Return level corresponding to variable indexed by vn. */ int ioattrout_(); /* ioattrout_(vn, str) int *vn; char *str; Output next attribute for variable indexed by vn. 0=none left. */ void ioname_(); /* ioname_(vn,s) int *vn; char *s; Return name (s) corresponding to variable number vn. */ int iowidth_(); /* int iowidth_(vn); int *vn; Return length of variable field indexed by vn. */ int iocommout_(); /* int iocommout_(s) char *s; Return next comment string. 0=none left. -1=next comment contains a URL */ #ifndef HAS_OUTER_INTEREST_ENTRY #define HAS_OUTER_INTEREST_ENTRY FALSE #endif #if HAS_OUTER_INTEREST_ENTRY #define OUTER_COMPILATION_OPTION_INTEREST "compiled to use ioexpress_outer_interest_ entry" int ioexpress_outer_interest_(); /* int ioexpress_outer_interest_(flag) int flag; Use flag (TRUE/FALSE as of v 2.0) to tell inner if we want next record from ioreadrec_ or if we have no more interest in this level Get back indicator (unused; TRUE/FALSE as of v 2.0) if inner can respond to a flag of FALSE by somehow ending its own input */ #else #define OUTER_COMPILATION_OPTION_INTEREST "compiled to ignore any ioexpress_outer_interest_ entry" #endif /* All #includes in outer.h */ #include "outer.h" #ifndef TEST_PROTOCOL_ADDITION #define TEST_PROTOCOL_ADDITION FALSE #endif Logical test_protocol_addition=TEST_PROTOCOL_ADDITION; /* jgofs.a functions... */ Logical get_logical_value(); /* HTDoConnect in turn requires some functions, presently found */ /* jgofs.a. Some are HT*.c modules; a couple are in jdb.c */ /* These functions needed only for testing for gifs, which is turned */ /* off by default. However, can't eliminate these defns based on */ /* compile-time TEST_GIFS, since user can spec tests at run time */ /* (only useful time, if functionality useful at all). */ /* If requiring these functions causes trouble (eg, no jgofs.a), */ /* should unconditionally set test_gifs to false. Suggest testing */ /* it first, and aborting if true (on grounds that somebody appears */ /* to want gifs tested, and we're not about to do it) */ int HTDoConnect(); int HTDoRead(); /* Interface to parse and test (now in jgofs.a library) */ /* Note: actual name of functions have _single or _double appended */ /* per compilation options. Work done in parse.h */ void jgofs_selection_parser(); Logical jgofs_selection_tester(); int varnames_in_sel(); /* Really a fancy call to parse */ /* outer_utils.c functions (in jgofs.a as of ~Apr 04) */ char *htmlescape(); char *trigram(); char *un_trigram(); int ntotal; int minlevel; int maxlevel; int reqlevel; int maxobjectlevel; int *outlevel; int brevflag=0; int htmlflag=0; int flatflag=0; int *level=NULL; int brevstart; int brevcount; int brevend; Logical *anydata; /* PATH_INFO stuff. Functions are in path_info_routines */ char *flat_PATH_INFO,*nextlevel_PATH_INFO,*level0_PATH_INFO; char *no_extURL_PATH_INFO,*full_list_PATH_INFO; char *propagate_brev_no_extURL_PATH_INFO; char *make_PATH_INFO_putenv_string(),*get_jgofs_env_datum(); int get_level(); static char *outnames=NULL; char *parsav; char *QUERY_STRING_no_selections; char *objsav; Logical dirlink,doclink,plotlink,lev0link,levnlink,flatlink; Logical quotenolink,toplinks,otheropts,oo_outer,brev_optimize_inner_calls; Logical flat_width_attr,flat_nonwidth_attrs; Logical test_gifs; Logical display_data_url; char *buttonimagesdir; char *background_color; Logical trailing_buttonimagesdir_slash; /* Format of "server" is node (optionally w/port), followed by cgi */ /* program name. Program name by itself can be used if we know */ /* that node is local. A node spec does not include a leading // */ /* (but we'll strip them off found) */ char *dirserver_w_dir; char *optionserver; char *infoserver_w_obj,*infoserver_extURL; char *dataserver,*localnodeport; char *equality_test; /* Links generate string or numeric test? */ char *attr_delims=ATTR_DELIM; /************************************************************************/ char *outer_return_vers() /* Dummy routine. Exists only to force .h file version string into */ /* this module. Note string must not be global or we'll have con- */ /* flicts if another routine similarly includes the version string */ { static char version[] = OUTER_VERSION"/"FULL_OUTERH_VERSION; return version; } void error_(s1,s2) char *s1,*s2; { char *prefix; if(htmlflag){ printf("

%s: %s

\n",s1,s2); } else { /* Prefix message w/ &x if it doesn't already start with that */ /* Not sure about blank following &x; it was in code before I */ /* started fooling. No problem w/putting it in - problem is */ /* whether or not a string starting w "&x" but not "&x " is */ /* to be considered a "properly formatted" error, and hence */ /* not requiring a preceding "&x ". As of _brev v 1.5a, */ /* decided "yes - &x proper; don't precede" */ prefix = (strncmp(s1,"&x",2) == 0) ? "" : "&x "; printf("%s%s: %s\n",prefix,s1,s2); } /* More dynamically allocated memory could be explicitly freed */ /* here, and probably should be so freed, but I'm lazy */ if (level) free(level); if (outnames) free(outnames); ioclose_(); exit(ERROR_EXIT_STATUS); } void out_err(s,t) char *s,*t; { char *ptr1,*ptr2; time_t timbuf; ptr1 = (char *) malloc (strlen(s) + 2 + strlen(t) + 1); if (ptr1 == NULL) error_(s,t); ptr2 = (char *) malloc (20 + 26 + 17 + strlen(OUTER_VERSION) + 2 + strlen(OUTER_COMPILATION_OPTION_INTEREST) + 2 + 1); if (ptr2 == NULL) error_(s,t); strcpy (ptr1,s); strcat (ptr1,": "); strcat (ptr1,t); strcpy (ptr2,"\n This msg issued "); time(&timbuf); strcat (ptr2,ctime(&timbuf)); /* ctime includes \n */ strcat (ptr2," This msg from "); strcat (ptr2,OUTER_VERSION); strcat (ptr2,"\n\t"); strcat (ptr2,OUTER_COMPILATION_OPTION_INTEREST); strcat (ptr2,"\n"); error_(ptr1,ptr2); return; } void out_errn(s,n,fmt) char *s,*fmt; int n; { char intbuf[25]; sprintf(intbuf,fmt,n); out_err(s,intbuf); return; } void out_errn_utils_style(s,n) char *s; int n; { out_errn(s,n,"%d"); return; } void malloc_err(s,n) char *s; int n; { /* Don't want to malloc an error buffer if we're having trouble */ /* malloc'ing in the first place */ /* Tried putting malloc in here, too, returning ptr when OK, but */ /* ran into BUSERR alignment problems. Don't know why... */ #define MAX_S 50 char idbuf[25 + MAX_S + 15 + 1] = "Could not get memory for "; int len; if ( (len = strlen(s)) > MAX_S ) len = MAX_S; strncat(idbuf,s,len); strcat(idbuf,". nbytes (hex) "); out_errn(idbuf,n,"%x"); return; } int varname_lookup(s) char *s; { int i; for (i=0; i0) { ptr = htmlescape(tmp); printf(comment_fmt,ptr); free(ptr); } else printf(comment_fmt,tmp); return; } void outvar() /* outvar outputs tabs as this is likely being fed into another program or being transferred across a network and therefore, we wish to minimize size of transfer to one character separating fields */ { int i,j; char tmp[OUTVARBUFSIZE],attr[TOTATTRSIZE]; for (i=0; i<=maxlevel; i++) { if (anydata[i]) { printf("&v%d\n",outlevel[i]); for (j=0; j= 0) { COPY_INTO_FIXED_LEN_BUFFER (tmp, outnames+j*VARNAMEBUFSIZE, "outvar name buf"); nattrs_output = 0; while (ioattrout_(&j,attr) != 0) { is_width = (strncmp(attr,"width=",6) == 0); print_it = (is_width && flat_width_attr) || ( ! is_width && flat_nonwidth_attrs); if (print_it) { nattrs_output++; if (nattrs_output == 1) ADD_INTO_FIXED_LEN_BUFFER (tmp,init_attr_delim,"init attr delim"); ADD_INTO_FIXED_LEN_BUFFER (tmp,attr,"attr in outvar name buf"); ADD_INTO_FIXED_LEN_BUFFER (tmp,attr_sep,"attr sep char"); } } if (nattrs_output > 0) { len = strlen(tmp); tmp[len-1] = attr_delims[1]; /* Replace last ; with closing ] */ } len = iowidth_(&j); printf("%-*s ",len,tmp); /* output var name in enough space + 2 */ } printf("\n"); } Logical outputflat(firsttime) Logical firsttime; { int j,len; char tmp[OUTBUFSIZE]; while (iocommout_(tmp)) ; for (j=0; j= 0) { iovalstr_(&j,tmp); len=iowidth_(&j); printf("%-*s ",len,tmp); } printf("\n"); minlevel=maxlevel; return 1; } void make_PATH_INFOs() { /* First "\0" to make_PATH_INFO_putenv_string says "don't start string */ /* with PATH_INFO=". First NULL says "Use PATH_INFO as default */ /* for unspec'd fields. For rest, NULL or USE_EXISTING_LEVEL */ /* means "use what's in PATH_INFO; otherwise, replace what's in */ /* PATH_INFO with what was supplied. */ /* May want to review comments about reqlevel vs maxlevel in main. */ /* Due to issues raised there, a user of the "Next level" button */ /* cannot get beyond a level that is "empty" because of */ /* projections */ full_list_PATH_INFO = make_PATH_INFO_putenv_string ("\0",NULL,NULL,"html",USE_EXISTING_LEVEL,NULL); /* Not sure why there's no extended URL in PATH_INFO for flat */ /* Not sure why level should be present either */ flat_PATH_INFO = make_PATH_INFO_putenv_string ("\0",NULL,NULL,"flat",USE_EXISTING_LEVEL,"\0"); nextlevel_PATH_INFO = make_PATH_INFO_putenv_string ("\0",NULL,NULL,NULL,reqlevel+1,NULL); level0_PATH_INFO = make_PATH_INFO_putenv_string ("\0",NULL,NULL,NULL,0,NULL); /* If/when we want to propagate .brev, change "html" to NULL below */ /* and get rid of propagate_brev in favor of no_extURL */ /* Note: as of outer 3.3, no_extURL_PATH_INFO is not, by itself, */ /* put into any links. It used to be in the link to the OOserver */ no_extURL_PATH_INFO = make_PATH_INFO_putenv_string ("\0",NULL,NULL,"html",USE_EXISTING_LEVEL,"\0"); propagate_brev_no_extURL_PATH_INFO = make_PATH_INFO_putenv_string ("\0",NULL,NULL,NULL,USE_EXISTING_LEVEL,"\0"); /* Make string for "Documentation" button. Although not a PATH_ */ /* INFO, I assume the {} section is logically an extended URL. */ /* Therefore, in the name of separating the formatting from the */ /* content, we build it here. The malloc & sprintf, below, */ /* should be replaced when we write a "create_extURL" routine */ /* since the = and , in "dir=whatever," are also extURL format- */ /* ting and not content. */ infoserver_extURL = NULL; if (*infoserver_w_obj == '\0') infoserver_extURL = infoserver_w_obj; else { infoserver_extURL = (char *)malloc (strlen(dirserver_w_dir) + strlen(dataserver) + strlen(no_extURL_PATH_INFO) + strlen(parsav) + strlen("{dir=,data=}?") + 1); if (infoserver_extURL != NULL) /* Assumes we prefer "dir=" to nothing if dirserver_w_dir empty */ sprintf(infoserver_extURL,"{dir=%s,data=%s%s}?%s", dirserver_w_dir, dataserver,propagate_brev_no_extURL_PATH_INFO,parsav); } if (full_list_PATH_INFO == NULL) out_err("Cannot make full_list_PATH_INFO",""); if (flat_PATH_INFO == NULL) out_err("Cannot make flat_PATH_INFO",""); if (nextlevel_PATH_INFO == NULL) out_err("Cannot make nextlevel_PATH_INFO",""); if (level0_PATH_INFO == NULL) out_err("Cannot make level0_PATH_INFO",""); if (no_extURL_PATH_INFO == NULL) out_err("Cannot make no_extURL_PATH_INFO",""); if (infoserver_extURL == NULL) out_err("Cannot make infoserver_extURL",""); return; } Logical check_gifs(gifs) char *gifs[]; /* If gif does not exist, replace its pointer with NULL */ /* Return TRUE if any gifs exist */ /* By default, this routine only looks at BUTTONIMAGESDIR. If empty */ /* no gifs exist; else all gifs exist. If TEST_GIFS specified, */ /* this routine will go out on net and look for each gif individ- */ /* ually. */ /* Put all tests in one module to try to do all testing w/one */ /* connect. Don't know how to do this, however. May not be poss- */ /* ible w/HTTP 1.0 routines (I assume HT routines in jdb.a are 1.0 */ { char *buttonhoststart,*buttonfilestart; int lenbuttonimagesdir; int handle,len,lenimg,lenbuttonfile,len_Host_buf; int i; Logical null_all_gifs,got_dir,got_a_gif; char *ptr,*HEAD_buf,*Host_buf,*htdatabuf; /* Buffer needs to be big enough to hold response to http HEAD */ /* request. 4096 chosen since that's what was in transfer, but */ /* I suspect that's for data, not just response to HEAD... */ #define HTDATARECSIZE 4096 got_dir = (*buttonimagesdir != '\0'); /* Assume we're not testing for gifs. In that case, turn off gifs */ /* if we weren't given a BUTTONIMAGESDIR */ null_all_gifs = ! got_dir; if ( got_dir && test_gifs ) { null_all_gifs = TRUE; /* .. in case we can't malloc various buffers */ lenbuttonimagesdir = strlen(buttonimagesdir); /* Set buttonhoststart to point after the protocol:// string */ /* Set buttonfilestart to point to the / that begins the file */ buttonhoststart = strstr(buttonimagesdir,"://") + 3; if ( (buttonfilestart = strchr(buttonhoststart,'/')) == NULL ) buttonfilestart = buttonimagesdir + lenbuttonimagesdir; trailing_buttonimagesdir_slash = (*(buttonimagesdir + lenbuttonimagesdir - 1) == '/'); /* 2nd/3rd line buffer is "Host: ", host:port, "\r\n\r\n" */ len_Host_buf = 6 + buttonfilestart - buttonhoststart + 4; Host_buf = (char *) malloc (len_Host_buf + 1); if (Host_buf != NULL) { strcpy (Host_buf,"Host: "); strncat (Host_buf, buttonhoststart, buttonfilestart - buttonhoststart); strcat (Host_buf, "\r\n\r\n"); lenimg = 0; for (i=0; i lenimg) lenimg = len; } /* 1st line buffer is "HEAD ",filename," HTTP 1.0\r\n" */ /* buttonimagesdir + lenbuttonimagesdir - buttonfilestart */ /* is the length of any directory portion for gif that was */ /* given in buttonimagesdir. */ lenbuttonfile = buttonimagesdir + lenbuttonimagesdir - buttonfilestart + lenimg + 2; /* +2 = we might need added /s */ HEAD_buf = (char *) malloc (5 + lenbuttonfile + 11 + 1); if (HEAD_buf != NULL) { if ( (htdatabuf = (char *)malloc(HTDATARECSIZE +1)) != NULL ) { /* From here, will be able to handle each gif individual- */ /* ly, so don't handle them collectively */ null_all_gifs = FALSE; for (i=0; i 0) { /* Assume status is 2nd token on returned line */ /* Also assume 2nd token fits within HTDATARECSIZE */ /* Also assume token seps are blank, tab. */ /* Also assume 200 is only good return. Aren't */ /* assumptions fun? */ ptr = strtok(htdatabuf,"\t "); if (ptr != NULL) ptr = strtok(NULL,"\t "); if (ptr == NULL) gifs[i] = NULL; /* Format bogus */ else if (strcmp("200",ptr) != 0) gifs[i] = NULL; /* Not OK (or format bogus) */ } else { gifs[i] = NULL; /* Read failed */ } /* Skip rest of reply to HEAD request */ close (handle); } else { gifs[i] = NULL; /* Connect failed */ } } free (htdatabuf); } free (HEAD_buf); } free (Host_buf); } } got_a_gif = FALSE; if (null_all_gifs) for (i=0; i",text); else { ptr = img; slash = ""; if (trailing_buttonimagesdir_slash) { if (*ptr == '/') ptr++; } else { if (*ptr != '/') slash = "/"; } printf ("\"%15s\"", text,buttonimagesdir,slash,ptr); } return; } void print_vert_bar(gif,set_new_line) Logical set_new_line; char *gif; /* Vertical bars are supposed to separate text links from all others */ /* Print one if: */ /* 1) This is not the first link on a line */ /* 2) The link we are about to output is text */ /* 3) The link we are about to output is image, but the link we */ /* output before was text */ /* This routine maintains state across calls... */ /* The set_new_line argument is used to set the "first link on a */ /* line" characteristic. No vertical bar is output in this case */ { static Logical first_link; static Logical printed_text_link; if (set_new_line) { first_link = TRUE; return; } if (first_link) first_link=FALSE; else if ( (gif == NULL) || printed_text_link) printf (" | "); printed_text_link = (gif == NULL); return; } void outvarhtml() /* routine to print the heading of all data objects if in HTML mode The directory button should always direct the browser back to the directory from which it came. Send link back to referring host for documentation - the .remoteobjects at that site has responsibility of correctly id'ing the INFOSERVER, using hyperlinks from a static obj.info file or Redirect to another host with HTTPD function (NCSA HTTPD 1.5.1 or gt) */ { char tmp[ATTRBUFSIZE]; Logical any_buttons_on_line_1,any_buttons_on_line_2; int j; char *ptr,*ptr1; char next_level[19]; /* Big enough for "Level 999 (of 999)\0" */ char *gifs[NGIFS]; gifs[DIR_GIF] = "dir.gif"; gifs[DOC_GIF] = "doc.gif"; gifs[PLOT_GIF] = "more.gif"; gifs[LEVEL0_GIF] = "level0.gif"; gifs[NOLEVEL0_GIF] = "nolevel0.gif"; gifs[LEVELN_GIF] = "leveln.gif"; gifs[NOLEVELN_GIF] = "noleveln.gif"; gifs[FLAT_GIF] = "flat.gif"; make_PATH_INFOs(); check_gifs(gifs); printf("%s -- Level %d\n",objsav,maxlevel); /* Format and position of tag from RCG Jul 06 (at which */ /* time he noted the absence of a tag from this program) */ /* Note that the leading # prohibits the use of literals like */ /* 'white'. However, validation code for background_color insists */ /* on exactly 6 hex digits in this version of outer (Jul 06) */ printf("\n"); if (display_data_url) { /* Make header look good by removing trigrams. Note that this */ /* is inconsistent; this version of outer only expects */ /* trigrams in selections & parsav could have significant % */ /* signs. If that gets to be a problem, we'll have to reparse */ /* or make an appropriate string while parsing... */ if ( (ptr = un_trigram(parsav,TRIGRAM_KEY,&j)) == NULL ) if (j<0) out_err("Could not malloc ptr to hold",parsav); else out_err("Bad trigram",parsav+j); printf("

%s --%s-- Level %d

\n",objsav,ptr,maxlevel); free(ptr); } /* No dirlink if we have no directory server */ dirlink = dirlink && (*dirserver_w_dir != '\0'); /* No doclink if we have no infoserver */ doclink = doclink && (*infoserver_extURL != '\0'); /* Put out toplinks. */ /* If there are no buttons corresponding to the links, all */ /* spec'd links go on 1 line. Separate these text links with */ /* vertical bars. */ /* If there ARE buttons, they go on their "correct */ /* lines; that is, if any of doclink, dirlink, or plotlink */ /* are spec'd, they go on line 1; if any of lev0link, lev1link, */ /* or flatlink are spec'd, they go on line 2 (if there was a */ /* line 1). */ /* A mixture of text links and button links are allowed. As */ /* for formatting, I tried... */ /* */ /* Logic: */ /* 1) Put out vertical bar, if needed */ /* 2) Open the anchor with "" */ /* 4) Call tag_toplink to associate text and, optionally, gif */ /* with anchor. tag_toplink issues the close anchor */ /* "<\a"> string, too */ /* 5) Do some bookkeeping about what kind of link we just did */ print_vert_bar (NULL,TRUE); any_buttons_on_line_1 = FALSE; if (dirlink) { print_vert_bar (gifs[DIR_GIF],FALSE); printf("", dirserver_w_dir); tag_toplink("Directory", gifs[DIR_GIF]); if (gifs[DIR_GIF] != NULL) any_buttons_on_line_1 = TRUE; } if (doclink) { print_vert_bar (gifs[DOC_GIF],FALSE); /* infoserver wants an object name (no protocol or level), */ /* and enough info to generate pointers to "our" dir and data */ printf("", infoserver_w_obj, infoserver_extURL); tag_toplink("Documentation", gifs[DOC_GIF]); if (gifs[DOC_GIF] != NULL) any_buttons_on_line_1 = TRUE; } if (plotlink) { print_vert_bar (gifs[PLOT_GIF],FALSE); /* optionserver wants a JGOFS object and its selections/proj- */ /* ections. I suspect it does not want protocol or level info, */ /* but historically those have been provided. It doesn't know */ /* how to handle extended URL's, so don't pass that either, but */ /* I suspect that will change, too. A "JGOFS object" is speci- */ /* fied by its node/port and its name (in PATH_INFO, along w/ */ /* protocol and level). optionserver requires the node/port to */ /* start with // (again for historical reasons, but we need some */ /* kind of separator anyway!). This 2nd // should probably be */ /* the symbol REMOTE_OBJECT_PREFIX, defined in utils.h, but */ /* since outer doesn't use utils, and since I'm too lazy to deal */ /* w/moving the symbol defn to core.h, leaving as is... */ /* The "I suspect that will change", from at least outer_brev */ /* 1.0 (Feb 98), now changes (outer 3.3) */ printf("", optionserver, localnodeport, full_list_PATH_INFO, parsav); tag_toplink("Download and Other Operations...", gifs[PLOT_GIF]); if (gifs[PLOT_GIF] != NULL) any_buttons_on_line_1 = TRUE; } /* If we are displaying buttons, and if we've displayed any so */ /* far, and if we have any left to display, "go to next line" */ /* I wonder if this should happen in a NOTOPLINKS environment... */ /* (See
comment, below) */ /* In order to find out about next line's buttons, we have to */ /* repeat a whole bunch of logic. Makes changing line structure, */ /* etc, a drag... */ if (any_buttons_on_line_1) { any_buttons_on_line_2 = (flatlink && (gifs[FLAT_GIF] != NULL)); if ( ! any_buttons_on_line_2 ) if (lev0link) if (maxlevel == 0) any_buttons_on_line_2 = (gifs[NOLEVEL0_GIF] != NULL); else any_buttons_on_line_2 = (gifs[LEVEL0_GIF] != NULL); if ( ! any_buttons_on_line_2 ) if (levnlink) if (maxlevel == maxobjectlevel) any_buttons_on_line_2 = (gifs[NOLEVELN_GIF] != NULL); else any_buttons_on_line_2 = (gifs[LEVELN_GIF] != NULL); if (any_buttons_on_line_2) { printf("

"); print_vert_bar (NULL,TRUE); } } if (lev0link) { if (maxlevel == 0) { ptr = " - At level 0 - "; ptr1 = gifs[NOLEVEL0_GIF]; print_vert_bar (ptr1,FALSE); printf(""); } else { ptr = "Level 0"; ptr1 = gifs[LEVEL0_GIF]; print_vert_bar (ptr1,FALSE); /* Level 0 button gets query string w/o selections/projections */ /* I still don't know why it doesn't keep any "original" se- */ /* lections/projections, but we certainly don't want any */ /* "clicked-on" selections. See comments up top for dis- */ /* cussion of preservation of inner's args */ /* QUERY_STRING_no_selections may be \0; has its own ? */ /* if needed */ printf ("", JGOFS_DATA_CGI_SPEC, level0_PATH_INFO, QUERY_STRING_no_selections); free(QUERY_STRING_no_selections); } tag_toplink(ptr, ptr1); } if (levnlink) { if (maxlevel < maxobjectlevel) { ptr = next_level; ptr1 = gifs[LEVELN_GIF]; print_vert_bar (ptr1,FALSE); printf ("", JGOFS_DATA_CGI_SPEC, nextlevel_PATH_INFO, parsav); sprintf(next_level,"Level %d (of %d)",maxlevel+1,maxobjectlevel); } else { ptr = " - At last level - "; ptr1 = gifs[NOLEVELN_GIF]; print_vert_bar (ptr1,FALSE); printf(""); } tag_toplink(ptr, ptr1); } if (flatlink) { print_vert_bar (gifs[FLAT_GIF],FALSE); printf ("", JGOFS_DATA_CGI_SPEC, flat_PATH_INFO, parsav); tag_toplink("Flat list", gifs[FLAT_GIF]); } /* If we are displaying buttons, "
" (whatever that is) */ /* I wonder if this should also happen in a NOTOPLINKS environment */ /* (See

comment, above) */ if (any_buttons_on_line_1 || any_buttons_on_line_2) printf("
"); printf("\n"); printf("

\n");

  print_comments_html();

    /* look at attributes to create array of widths - see ioattrout_ */
  for(j=0; j= 0)
      while(ioattrout_(&j,tmp))
        ;

  return;
}

void print_html_data_line(print_data_as_links,level_to_print)
Logical print_data_as_links;
int level_to_print;
/*  print_html_data_line scans the list of all variables, selecting	*/
/*  those at level level_to_print.  It prints the data corresponding	*/
/*  to each selected variable as either a text string, or a link to	*/
/*  the next level with the text string as a label.  The choice of	*/
/*  of which way to print is governed by print_data_as_links arg and	*/
/*  the quotenolink global variable.  The rules:			*/
/*    1) At most one level is displayed as links.			*/
/*    2) The candidate level is the last level being displayed		*/
/*    3) If that level is the last of the object, do not display as	*/
/*	as links							*/
/*    4) if quotenolink: regardless of level, if datum is enclosed	*/
/*	in "s, remove them and print the rest as text			*/
/*  Rule 4 is implemented here.  Rules 1, 2, & 3 are "put into"		*/
/*  print_data_as_links by outputhtml					*/
{
  int j,len_datum,field_width;
  char tmp[DATUMBUFSIZE];
  char *comma,*ptr;

  comma = (*parsav == '\0') ? "\0" : ",";

  for(j=0;j%-s%*s",
          JGOFS_DATA_CGI_SPEC, nextlevel_PATH_INFO, 
	  parsav, comma, outnames+j*VARNAMEBUFSIZE, equality_test, ptr,
          tmp,
	  field_width-len_datum+2, " "
	);
	free (ptr);
      }
      else printf("%-*s  ",field_width,tmp);
    }

  printf("\n");

  return;
}

void print_full_list_link (s)
char *s;
{
#define BREVLINKINDENT 16

  printf ("%*s%s\n",BREVLINKINDENT," ",s);
  printf ("%*sFull data listing\n",
    BREVLINKINDENT, " ", JGOFS_DATA_CGI_SPEC, full_list_PATH_INFO, parsav);

  return;
}

Logical outputhtml(firsttime)
Logical firsttime;
{
    /* linecount (only for abbreviated listing feature)			*/
  static int linecount;

  int i,j;

    /* minlevel != maxlevel for first record of each maxlevel file	*/
    /* in a multilevel display						*/
  if (minlevel brevend)  return FALSE;
    if (linecount < brevstart) {
	/*  Put link at beginning (only once!) as well as end in case	*/
	/*  data ends before brevstart is reached			*/
      if (linecount == 1)
	print_full_list_link("[ ... skipping up to first listing line]");
      return TRUE;
    }
  }

    /*  Print data as links unless we're at bottom level of object	*/
  print_html_data_line(  (maxlevel != maxobjectlevel), maxlevel  );

  return TRUE;
}

Logical reconciliation(assert,negate,assert_default)
char *assert,*negate;
Logical assert_default;
/*  Given 2 environment variables assert and negate, where one is	*/
/*  logically the negative of the other (eg; TOPLINKS and NOTOPLINKS),	*/
/*  reconcile their values (if present in any combination) into a	*/
/*  single consistent logical value					*/
/*    Negate is optional.  If not there, this routine validates assert,	*/
/*  if present, and returns it.  If no assert, return default		*/
{
  Logical assert_val,negate_val;
  char *ptr;

    /*  UNSPECIFIED back from get_logical_value really means "illegal	*/
    /*  string" if fed a non-null string.  However, to do that would	*/
    /*  introduce yet another non-TRUE, non-FALSE value for "Logical"	*/
    /*  variables.  Sigh.						*/
  if (  (ptr = getenv(assert)) == NULL  )  assert_val = UNSPECIFIED;
  else if (  (assert_val = get_logical_value(ptr)) == UNSPECIFIED  )
			      out_err("Invalid logical value of env var",ptr);

  if (negate == NULL) negate_val = UNSPECIFIED;
  else if (  (ptr = getenv(negate)) == NULL  )  negate_val = UNSPECIFIED;
  else if (  (negate_val = get_logical_value(ptr)) == UNSPECIFIED  )
			      out_err("Invalid logical value of env var",ptr);

    /*  Both true or both false is an error.  				*/
    /*  Note cannot just test (assert_val && negate_val) since they can	*/
    /*    have values other than TRUE or FALSE				*/
  if (  (  (assert_val == TRUE)  &&  (negate_val == TRUE) )  ||
	(  (assert_val == FALSE) &&  (negate_val == FALSE))     )
    out_err("Conflicting values of logical env vars",assert);

    /*  If we get here, anything user defined is consistent, so we can	*/
    /*  use either defined value, if any.				*/
    /*		assert defined and negate defined: use assert		*/
    /*		assert defined and negate undefined: use assert		*/
    /*		assert undefined and negate defined: use NOT negate	*/
    /*		assert undefined and negate undefined: use default	*/
  if (assert_val == UNSPECIFIED)
    assert_val = (negate_val == UNSPECIFIED) ? assert_default : !negate_val;

  return assert_val;
}

void get_outer_params()
  /*  Parameters for this invocation of outer come from the http-	*/
  /*  supplied environment variables PATH_INFO and QUERY_STRING and	*/
  /*  from other environment variables often set by serv from entries	*/
  /*  in the .objects file for the object we are about to process.	*/
  /*  The PATH_INFO environment variable carries lots of info.  See	*/
  /*  path_info_routines.c for lots of doc about contents and formatting */
  /*    Based on parameters, this routine also sets up an html or non-	*/
  /*  html environment.  Logically that should happen after this	*/
  /*  routine returns, but the environment is needed to allow pro-	*/
  /*  cessing of any errors that happen in this routine, so setup must	*/
  /*  happen as soon as possible					*/
{
  char *ptr,*ptr1;
  int i,j;

  if (  (parsav = getenv("QUERY_STRING")) == NULL  ) parsav = "\0";

  if (getenv("PATH_INFO") == NULL) {
    printf("Content-type: text/plain\n\n");
    return;
  }

/*  Get output protocol (html, flat, etc) from PATH_INFO env variable	*/

    /*  Note that in case of error getting protocol, error_ might	*/
    /*  not be able to get environment correct & a "Server 500"	*/
    /*  could happen							*/

  if (  (ptr = get_jgofs_env_datum("protocol",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));
  brevflag = (strcmp(ptr,"brev") == 0);
  htmlflag = (  (strcmp(ptr,"html") == 0) || brevflag  );
  if (htmlflag) printf("Content-type: text/html\n\n");
  else {
    printf("Content-type: text/plain\n\n"); 
    flatflag = (strcmp(ptr,"flat") == 0);
  }

  if (  !(htmlflag || flatflag)  ) return;

/*	We're displaying stuff.  Need lots of info			*/

    /*  Get misc logical environment variables				*/
  if (getenv("OO_OUTER") != NULL) {
    fprintf(stderr,"Warning: OO_OUTER env var encountered\n");
    fprintf(stderr,"\twhile processing PATH_INFO %s\n",getenv("PATH_INFO"));
    fprintf(stderr,"\tOO_OUTER no longer useful as of %s\n",OUTER_VERSION);
  }
  quotenolink = reconciliation("QUOTENOLINK",NULL,QUOTENOLINK);
  toplinks = reconciliation("TOPLINKS","NOTOPLINKS",TOPLINKS);
  dirlink = reconciliation("DIRLINK","NODIRLINK",DIRLINK);
  doclink = reconciliation("DOCLINK","NODOCLINK",DOCLINK);
  plotlink = reconciliation("PLOTLINK","NOPLOTLINK",PLOTLINK);
  lev0link = reconciliation("LEV0LINK","NOLEV0LINK",LEV0LINK);
  levnlink = reconciliation("LEVNLINK","NOLEVNLINK",LEVNLINK);
  flatlink = reconciliation("FLATLINK","NOFLATLINK",FLATLINK);
    /*  Note PLOTLINK in next line.  Assures that run-time PLOTLINK	*/
    /*  supersedes compile-time OTHEROPTS and vice versa.  See outer.h	*/
  otheropts = reconciliation("OTHEROPTS","NOOTHEROPTS",PLOTLINK);
  test_gifs = reconciliation("TEST_GIFS","NOTEST_GIFS",TEST_GIFS);
  display_data_url = 
     reconciliation("DISPLAY_DATA_URL","NODISPLAY_DATA_URL",DISPLAY_DATA_URL); 
  flat_width_attr = 
     reconciliation("FLAT_WIDTH_ATTR","NOFLAT_WIDTH_ATTR",FLAT_WIDTH_ATTR);
  flat_nonwidth_attrs = 
     reconciliation("FLAT_NONWIDTH_ATTRS","NOFLAT_NONWIDTH_ATTRS",
		     FLAT_NONWIDTH_ATTRS);


    /*  Set up for generating numeric or alpha selections when outer	*/
    /*  generates a "next-level-with-selection" link			*/
  if (  reconciliation ("GENERATE_ALPHA_SELECTIONS",
			"NOGENERATE_ALPHA_SELECTIONS",
			GENERATE_ALPHA_SELECTIONS)      ) {
      /*  Next line returns "%20eq%20"					*/
    equality_test = trigram(" eq "," ",TRIGRAM_KEY,NULL);
    if (equality_test == NULL)
      out_err("Cannot get memory for equality_test","");
  } else
    equality_test = "=";

    /*  If both plotlink and otheropts are specified, they must match	*/
    /*  If either is specified, use that val; else use TRUE (or TOPLINK?) */
  if (plotlink == UNSPECIFIED) 
    plotlink = (otheropts == UNSPECIFIED) ? TRUE : otheropts;
  else
    if ((otheropts != UNSPECIFIED) && (plotlink != otheropts))
      out_err("Conflicting values of PLOTLINK and OTHEROPTS","");

  if (doclink == UNSPECIFIED) doclink = toplinks;
  if (lev0link == UNSPECIFIED) lev0link = toplinks;
  if (levnlink == UNSPECIFIED) levnlink = toplinks;
  if (flatlink == UNSPECIFIED) flatlink = toplinks;

    /*  Get misc "address information" environment variables that	*/
    /*  may need to appear in links					*/

    /*  localnodeport is, logically, the node:port spec of the		*/
    /*  http server that's running us.  This is the info in		*/
    /*  SERVER_NAME and SERVER_PORT.  However, for hosts, we		*/
    /*  normally prefer to output our www alias on URLs we generate	*/
    /*  - that's what's in MYADDR.  Don't know why MYADDR should con-	*/
    /*  tain a port at all; ie, I don't know why SERVER_PORT should not	*/
    /*  always be used.  ?? Oh well...  For parallelism w/MYADDR, allow	*/
    /*  a MYPORT, too.  outer 2.5 and earlier did			*/
    /*  not propagate the port at all, so we don't propagate port 80 	*/
  if (  (localnodeport = getenv("MYADDR")) == NULL  )  
    if (   ( *(localnodeport = MYADDR) == '\0' )   )
      if (  (localnodeport = getenv("SERVER_NAME")) == NULL  )
	localnodeport = MYADDR;		/*  address of empty string	*/
  if (  (ptr = getenv("MYPORT")) == NULL  )  
    if (   ( *(ptr = MYPORT) == '\0' )   )
      if (  (ptr = getenv("SERVER_PORT")) == NULL  )
	ptr = MYPORT;			/*  address of empty string	*/
  if (*ptr == ':') ptr++;		/*  Just in case...		*/
  i = (int) strtol(ptr,&ptr1,10);
  if (*ptr1 == '\0') {
    if (  ! ((i == 0) || (i == 80))  )   {
	/*  Ready to add port if localnodeport didn't already have one.	*/
	/*   Assume colon is node/port separator.  If nothing after	*/
	/*   colon, pretend it isn't there				*/
      if (  (ptr1 = strchr(localnodeport,':')) != NULL)
	if (*(++ptr1) == '\0') {
	  *(ptr1-1) = '\0';
	  ptr1 = NULL;
	}
      if (ptr1 == NULL) {
	ptr1 = localnodeport;
	j = strlen(ptr1) + 1 + strlen(ptr) + 1;
	if (  (localnodeport = (char *)malloc(j)) == NULL )
				malloc_err("localnodeport",j);
	sprintf(localnodeport,"%s:%s", ptr1, ptr);
      }
    }
  } else
    out_err("Non-numeric port in MYPORT/SERVER_PORT",ptr);

  j = strlen(localnodeport) + strlen(JGOFS_DATA_CGI_SPEC) + 1;
  if (  (dataserver = (char *)malloc(j)) == NULL  )
				malloc_err("dataserver",j);
  strcpy(dataserver,localnodeport);
  strcat(dataserver,JGOFS_DATA_CGI_SPEC);

    /*  buttonimagesdir is expected to include the front part of a	*/
    /*  URL, not just a dir.  Stick prefix on if necessary		*/
  if (  (buttonimagesdir = getenv("BUTTONIMAGESDIR")) == NULL  )
					buttonimagesdir = BUTTONIMAGESDIR;
  if (buttonimagesdir == NULL) buttonimagesdir = "";
  if (*buttonimagesdir != '\0') {
    if (strstr(buttonimagesdir,"://") == NULL) {
      ptr = buttonimagesdir;
      j = strlen("file://localhost") + 1 + strlen(ptr) + 1;
      if (  (buttonimagesdir = (char *)malloc(j)) == NULL  )
				malloc_err("buttonimagesdir",j);
      strcpy(buttonimagesdir,"file://localhost");
      if (*ptr != '/') strcat (buttonimagesdir,"/");
      strcat (buttonimagesdir,ptr);
    }
  }

    /*  For now (Jul 06), background color must be exactly 6 hex digits	*/
  if (  (background_color = getenv("BACKGROUND_COLOR")) == NULL  )
					background_color = BACKGROUND_COLOR;
  if (background_color == NULL) background_color = "";
  if (*background_color != '\0') {
    if (strlen(background_color) != 6) 
      out_err("Length of BACKGROUND_COLOR string is not 6. string is",
			background_color);
    if (strspn(background_color,"0123456789abcdefABCDEF") != 6)
      out_err("BACKGROUND_COLOR string contains illegal hex digit. string is",
			background_color);
  }

  if (brevflag) {
    if (  (ptr = getenv("BREVSTART")) == NULL  ) brevstart=BREVSTART;
    else {
      brevstart = (int) strtol(ptr,&ptr1,10);
      if (*ptr1 != '\0') out_err("Non-numeric BREVSTART",ptr);
    }
    if (brevstart < 1) out_err("Illegal BREVSTART",ptr);
    if (  (ptr = getenv("BREVCOUNT")) == NULL  ) brevcount=BREVCOUNT;
    else {
      brevcount = (int) strtol(ptr,&ptr1,10);
      if (*ptr1 != '\0') out_err("Non-numeric BREVCOUNT",ptr);
    }
    if (brevcount < 1) out_err("Illegal BREVCOUNT",ptr);
    brevstart--;		/* start w/0 vs start w/1 adjustment	*/
    brevend = brevstart + brevcount;

      /*  Note: the fooling around to optimize is in principle unre-	*/
      /*  lated to .brev.  At some point, for example, we might want	*/
      /*  to have, say, a sampling mode whereby non-flat, non-html	*/
      /*  output is restricted to the first 20 data values (brev de-	*/
      /*  faults) at bottom level.  Such a mode should also be subject	*/
      /*  to consideration about whether the particular inner can cease	*/
      /*  to be called "in the middle of things".  Logic is here, now	*/
      /*  (v 2.0a), because, like the rest of this issue, it came to	*/
      /*  light w/.brev and at present is only a problem in that mode	*/
    brev_optimize_inner_calls =
      reconciliation ("BREV_OPTIMIZE_INNER_CALLS",NULL,BREV_OPTIMIZE_INNER_CALLS);
  }

    /* syntax of OPTIONSERVER (like all *SERVERs) is			*/
    /*		machine.domain/jg/script-or-binary			*/
    /* optionally preceded by //					*/

    /*  Very hard for there not to be an OPTIONSERVER in		*/
    /*  environment.  serv will put one there from the .objects		*/
    /*  file, or from a value compiled into it (usually from		*/
    /*  build-env).  Therefore, only time we use the constant		*/
    /*  OPTIONSERVER compiled into outer is when we're being run	*/
    /*  standalone.  In particular, you cannot build a method		*/
    /*  with "its own" OPTIONSERVER by compiling outer with that	*/
    /*  option server.  Use the .objects file instead...		*/
  if (  (optionserver = getenv("OPTIONSERVER")) == NULL  )
						optionserver = OPTIONSERVER;
    /* Make sure whatever we have does NOT start with // since 		*/
    /* printf puts them in.  Allow for possibility that // has become /	*/
    /* courtesy of Apache2.  Hopefully we did NOT use a single / for	*/
    /* something meaningful						*/
  if (*optionserver == '/') optionserver++;
  if (*optionserver == '/') optionserver++;

    /*  Get object name, level, dir routine that got us here, and	*/
    /*  info routine we should use.  All this may have been encoded	*/
    /*  into the PATH_INFO env variable, or may be in individual	*/
    /*  environment variables, or may be in compilation constants,	*/
    /*  or may be we just defaulted it in this module			*/
  if (  (reqlevel = get_level(NULL)) == LEVEL_ERROR  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));

  if (  (objsav = get_jgofs_env_datum("object",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));

    /*  If dir= is in extended URL, it probably includes a direct-	*/
    /*  ory spec.  A dirserver w/o a directory spec will operate	*/
    /*  on a default directory; hence it is also logically includes	*/
    /*  a directory spec.  Who knows what somebody would put in		*/
    /*  the environment?  We assume it's just a server, but it		*/
    /*  doesn't have to be						*/
  if (  (dirserver_w_dir = get_jgofs_env_datum("dir",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));
  if (*dirserver_w_dir == '\0') {
    free (dirserver_w_dir);
    if (  (dirserver_w_dir = getenv("DIRSERVER")) == NULL  )
      dirserver_w_dir = DIRSERVER;
  }
  if (*dirserver_w_dir == '/') dirserver_w_dir++;
  if (*dirserver_w_dir == '/') dirserver_w_dir++;

    /*  If info= is in extended URL, it includes objsav.  Too bad;	*/
    /*  would be cleaner to just get server here and add objsav		*/
    /*  when creating the "Documentation" link.  However, this way	*/
    /*  allows info to come from a source whose name is not related	*/
    /*  to the object name.  If that's the intent, this comment		*/
    /*  should be changed to say so					*/
    /*    Note that this string is subject to the Apache double slash	*/
    /*  issue.  These slashes could have been both in the infoserver	*/
    /*  spec and in the "objsav" portion of the string.  outer will	*/
    /*  care of the infoserver part.  The objsav portion becomes the	*/
    /*  infoserver's problem.  A second treatment by Apache after we	*/
    /*  leave outer won't make things worse, and might remove any	*/
    /*  mitigation we add, so info has to know what to do anyway	*/
  if (  (infoserver_w_obj = get_jgofs_env_datum("info",NULL)) == NULL  )
    out_err("Incorrectly formatted PATH_INFO",getenv("PATH_INFO"));
  if (*infoserver_w_obj == '\0') {
    free (infoserver_w_obj);
    if (  (ptr = getenv("INFOSERVER")) == NULL  ) ptr = INFOSERVER;
    j = strlen(ptr) + strlen(objsav) + 1;
    if (  (infoserver_w_obj = (char *) malloc(j)) == NULL  )
				malloc_err ("infoserver_w_obj",j);
    strcpy (infoserver_w_obj,ptr);
    strcat (infoserver_w_obj,objsav);
  }	
  if (*infoserver_w_obj == '/') infoserver_w_obj++;
  if (*infoserver_w_obj == '/') infoserver_w_obj++;

  return;
}

int main(argc,argv)

int argc;
char *argv[];
{
  int cl;
  int i,j,k,nparams,maxdatalevel,nobjectlevels;
  int any_projections = FALSE;
  char *stmp,*ptr;
  char **vars_in_selections,**char_array_ptr;
  Logical firsttime,want_more_data;
  Logical result_of_selection_test;
  char init_QUERY_STRING_item_char;
  struct parse_functions parse_functions;
  struct parse_data parse_data;

  reqlevel=LEVEL_NOT_SPECIFIED;		/* from path_info_routines.h	*/
  get_outer_params();
  if (reqlevel == LEVEL_NOT_SPECIFIED) reqlevel = 999;	/* 999 from orig code */
    /*  Make copy of query string.  Eventually remove selections &	*/
    /*  projections from it.  One malloc "+1" is for leading '?'; other	*/
    /*  is for trailing '\0'						*/
  i = strlen(parsav);
  if (  (QUERY_STRING_no_selections = (char *)malloc(i+1+1)) == NULL  )
					malloc_err("copy of query string",i);
  *QUERY_STRING_no_selections = QUERY_STRING_CHAR;
  strcpy (QUERY_STRING_no_selections + 1, parsav);

  nparams=argc-1;
  maxobjectlevel=ioopen_(argv+1,&nparams,&ntotal);
  nobjectlevels = maxobjectlevel + 1;

  i = ntotal*sizeof(int);
  if (  (level = (int *) malloc(i)) == NULL  )  malloc_err("level array",i);
  for (i=0;i= -(reqlevel+1))  )
						level[k] = -(level[k]+1);
	any_projections = TRUE;
      }
    }
    /*  If nothing in QUERY_STRING_no_selections, remove leading '?'	*/
    /*  Also remove trailing comma, if any				*/
  if ( *(QUERY_STRING_no_selections + 1) == '\0')
			  		*QUERY_STRING_no_selections = '\0';
  else {
    i = strlen(QUERY_STRING_no_selections) - 1;
    if ( *(QUERY_STRING_no_selections + i) == QUERY_STRING_ITEM_SEPARATOR )
      *(QUERY_STRING_no_selections + i) = '\0';
  }

  if ( ! any_projections )
    for (i=0; i= -reqlevel-1) level[i]= -level[i]-1;

/*  At this point, level, indexed by var num ranging over all input	*/
/*  vars, has non-negative entries for all "projected-in" variables	*/

  i = nobjectlevels*sizeof(Logical);
  if (  (anydata = (Logical *) malloc(i)) == NULL  )  
						malloc_err("anydata array",i);
  i = nobjectlevels*sizeof(int);
  if (  (outlevel = (int *) malloc(i)) == NULL  )  
						malloc_err("outlevel array",i);
  for (i=0; i= 0) {
      anydata[j] = TRUE;
      if (j > maxlevel)  maxlevel = j;
    }
  }
  j = 0;
  for (i=0; i<=maxlevel; i++) 
    if (anydata[i]) outlevel[i] = j++;

#ifdef DEBUG
  printf("maxlevel %d\n",maxlevel);
#endif

  if (reqlevel>maxlevel) reqlevel=maxlevel;

/*  There may be some ambiguity between reqlevel, maxlevel, and the	*/
/*	level in PATH_INFO.  maxlevel is the "deepest" level we will	*/
/*	display.  reqlevel starts out as what was in PATH_INFO.  It	*/
/*	is used to set an upper bound on maxlevel (the "bottom" level	*/
/*	of the object is also an upper bound indirectly via the calls	*/
/*	to iovarlevel_).  maxlevel can further be reduced by proj-	*/
/*	ections.  Finally, maxlevel is used as an upper bound on	*/
/*	reqlevel.  Thus, after everything is over, I think reqlevel	*/
/*	ends up equal to maxlevel.  Logically, however, if reqlevel	*/
/*	means "requested level", that's what's in the URL.  Everything	*/
/*	else is logically maxlevel.					*/
/*  reqlevel is used in the "Next level" button.  I think it would work	*/
/*	better if the above "if" were removed, in which case reqlevel	*/
/*	would represent the min of the user requested level & the	*/
/*	number of levels in the object					*/
/*  [Apr 09]  The above discussion ignores the problem of vars in	*/
/*	selections that are NOT in the projection list.  So did the	*/
/*	code.  We must read the selection vars regardless of their	*/
/*	level and/or projection state, or we cannot evaluate the	*/
/*	selections.  Add "maxdatalevel", the level we need to read	*/
/*	to accomplish this, by getting the vars in the selection list	*/
/*	and seeing if any of them are beyond maxlevel			*/
  maxdatalevel = maxlevel;
  if (vars_in_selections != NULL) {
    char_array_ptr = vars_in_selections;
    while (*char_array_ptr != NULL) {
      for (j=0; j= ntotal)
        out_err (
"Internal error: varname not found in list of vars after \
having been found previously.  Varname=",
		  *char_array_ptr
	        );
      i = iovarlevel_(&j);
      if (i > maxdatalevel) maxdatalevel = i;
      char_array_ptr++;
    }
  }

    /*  Next statement is logically the initializing "min=max+1"	*/
    /*  statement in a loop that calcs mininma ... almost.  minlevel	*/
    /*  gets set if we have a multilevel display, but NOT if we have	*/
    /*  single level.  In any case, there are minlevel->maxlevel loops	*/
    /*  in the output* subroutines, so minlevel is a "printing" level	*/
    /*  and logically belongs w/maxlevel (and not maxdatalevel, 	*/
    /*  maxobjectlevel, etc)						*/
  minlevel=maxlevel;

  cl=0;
  firsttime=TRUE;
  want_more_data=TRUE;

  if (htmlflag) outvarhtml();
  else if (flatflag) outvarflat();
  else outfirst_section();

  if (  (stmp = (char *)malloc(COMMENTLINEBUF)) == NULL  )
				malloc_err ("comment buffer",COMMENTLINEBUF);

    /*  want_more_data doesn't have to be set by the output*	*/
    /*  routines except that that's how .brev was spec'ed	*/
    /*  ioexpress_outer_interest_ is used to try to induce an	*/
    /*  EOF response from ioreadrec_.  If inner can't do this,	*/
    /*  we must keep reading and rely on the output* routines	*/
    /*  to ignore things themselves (maybe could recode that,	*/
    /*  but they seem to be doing fine).  Don't even try to	*/
    /*  optimize this way if somebody told us not to (eg, a	*/
    /*  general-purpose inner like defgb might support opti-	*/
    /*  mization in general but for a particular object, it	*/
    /*  shouldn't.  Most likely problem area would be a pipe to	*/
    /*  defgb that should not be closed until other end has	*/
    /*  finished)						*/
  while(1){
#if HAS_OUTER_INTEREST_ENTRY 
    if (brev_optimize_inner_calls) ioexpress_outer_interest_(want_more_data);
    else ioexpress_outer_interest_(TRUE);
#endif
    if (ioreadrec_(&cl))  {
#ifdef DEBUG
      printf("succ. read at lev %d\n",cl);
#endif

	/*  Result can come back TRUE, FALSE, or NOT_VALID, the	*/
	/*  last occurring if we haven't read enough levels to	*/
	/*  get all the selection vars.  In that case, we want	*/
	/*  to accept what we have pending the future read	*/
      result_of_selection_test = 
		jgofs_selection_tester(cl,&parse_data,&parse_functions);
      if (result_of_selection_test != FALSE) {
	if  (cl < maxdatalevel)  {
	  if  (cl < minlevel)  minlevel=cl;
	  cl++;
	} else {
	  if (htmlflag) want_more_data = outputhtml(firsttime);
	  else if (flatflag) want_more_data = outputflat(firsttime);
	  else want_more_data = output(firsttime);
	  firsttime = FALSE;
	    /*  We have read enough levels to find a record	*/
	    /*  that says "selected!"  We've printed, down to	*/
	    /*  the appropriate level, the first instance of	*/
	    /*  this record.  If maxlevel = maxdatalevel, then	*/
	    /*  the first instance is the only instance.  If	*/
	    /*  not, though, we don't want to repeat the upper	*/
	    /*  level values for each "selected!" we find.  Go	*/
	    /*  back to the level we're interested in printing	*/
	  cl = maxlevel;
	}
      }
    } else {
#ifdef DEBUG
      printf("failed read at lev %d\n",cl);
#endif
      if (cl == 0) break;
        /* flush comment buffer */
      while (iocommout_(stmp))
	;
      cl--;
      want_more_data=TRUE;
    }
  }

  ioclose_();

  if (htmlflag) printf ("
\n\n"); else if (!flatflag) printf("&e\n"); exit(0); }