/* * This program provides form-based access to a PH/QI server. The * hostname of the ph server is specified as the path info. * * If no form data is available, we retrieve the field descriptions for * the target ph database and generate an HTML form that allows the user * to make a query. If you plan to use this form often, you should save * it a file and link to it rather than regenerate it every time (also * allows you to customize the form). * * If form data is present in the request, we parse it and construct a ph * query command based on the input supplied, which is sent to the ph server. * The result of the query is formatted as HTML and returned to the client. * * The DECthreads HTTP server's cgilib/scriptlib routines are used to * interact with the server script environment. * Socket routines are used to make the communicate with the ph server. * * Author: David Jones. * Date: 18-MAR-1995 * Revised: 22-MAR-1995 Redo of generate_form(), now driven by * template in character string array. * Revised: 31-MAR-1995 Miscellaneous * Revised: 4-APR-1995 Fix infinte loop in $(NAMEFLD) search. * Revised: 4-MAY-1995 Add Bruce Tanner's hack for url field * attribute. */ #include #include #include #include #include #include #include #include #include #include #define QI_PORT 105 #include "cgilib.h" #include "scriptlib.h" void error_abort(); static int open_remote ( char *, int ), load_fields (int, char *); static int build_query ( int, char *, int, char *, int, char *, int ); static char *next_line ( int, int *length ); struct field_info { struct field_info *next; char *name, *attributes, *description; int id; int lookup, indexed; int max_size; int defreturn, explicit_return, reserved; int public, url; char name_buf[16]; /* Avoid extra malloc if we can */ char desc_buf[32]; char attr_buf[80]; /* save on malloc if possible */ }; static struct field_info *fields; static char *extra_info; struct formgen_context { char *host, *seek; struct field_info *fld; int port, cur_line, cur_off, rep_line, rep_off; int public_override; int field_select; }; static int interpret_key ( char *key, char *buffer, int *length, struct formgen_context *ctx ); /******************************************************************************/ /* Main program, command line arguments: * argv[1] Method in HTTP request. * argv[2] Ident portion of URL requested. * argv[3] HTTP protocol version. */ int main ( int argc, char **argv ) { int status, i, length, port, qi, content_length, clen; char *query, *form, *path_info, *cp, *content_length_str; char host[256], message[256], command[4096]; /* * Setup CGI emulation and extract hostname/port from path info. */ status = cgi_init ( argc, argv ); if ( (status&1) == 0 ) exit ( status ); path_info = cgi_info ( "PATH_INFO" ); extra_info = ""; if ( !path_info ) error_abort ( "500 CGI bug", "Missing PATH_INFO (ph server host)" ); port = QI_PORT; cp = (*path_info == '/') ? path_info+1 : path_info; for ( i = 0; i < sizeof(host)-1; i++ ) { host[i] = *cp++; if ( !host[i] ) break; /* end of string */ else if ( host[i] == '/' ) { /* * Interpret data following host as port number */ extra_info = malloc ( strlen(&host[i+1]) + 1 ); strcpy ( extra_info, &host[i+1] ); break; } } if ( i == 0 ) { error_abort ( "500 missing host", "Ph server host name must be supplied as path info." ); strcpy ( host, cgi_info("SERVER_NAME") ); } else host[i] = '\0'; /* * Connect to PH server and get its fields. */ qi = open_remote ( host, port ); if ( qi < 0 ) return 0; status = load_fields ( qi, message ); if ( (status&1) == 0 ) error_abort ( "500 server error", message ); /* * Determine if we have post data or GET query data */ content_length_str = cgi_info ( "CONTENT_LENGTH" ); content_length = atoi ( content_length_str ); query = (char *) 0; if ( content_length <= 0 ) { /* See if query string present */ query = cgi_info ( "QUERY_STRING" ); if ( query ) { if ( !*query ) query = (char *) 0; } } if ( content_length <= 0 && !query ) { /* * No input data generate form only and return. */ int generate_form(); cgi_printf ( "content-type: text/html\n\n\n" ); return generate_form ( 0, qi, host, port ); } /* * Read form data or synthesize form data from query string * (allows testing via fetch_http.exe). */ if ( query ) { /* * Users doing GET, synthesize form data. */ form = malloc ( strlen(query) + 30 ); if ( !form ) error_abort ( "500 malloc error", "Error reading form" ); sprintf(form, "%s", query ); content_length = strlen ( form ); /* printf("form string: %s\n", form ); */ } else { /* * Decode form data to make query. */ form = malloc ( content_length+1 ); if ( !form ) error_abort ( "500 malloc error", "Error reading form" ); for ( i = 0; i < content_length; i += length ) { length = cgi_read ( &form[i], content_length - i ); if ( length < 0 ) break; } } /* * Convert form to PH command and send. */ cgi_printf ( "content-type: text/html\n\n\n" ); cgi_printf ( "ph results\n\n\n"); clen = build_query ( qi, form, content_length, command, sizeof(command), host, port ); if ( clen > 0 ) { command[clen++] = '\n'; length = write ( qi, command, clen ); if ( length == clen ) { void display_result(); display_result ( qi, command ); } else { cgi_printf ( "Error, write to ph server failed\n"); } cgi_printf ( "\n\n" ); } return 1; } /*****************************************************************************/ static int parse_field_info ( struct field_info *blk, char *msg ) { int i; char *info, *max_spec; /* * initialize all fields to default values. */ blk->indexed = blk->lookup = blk->reserved = blk->max_size = 0; blk->defreturn = blk->explicit_return = blk->public = blk->url = 0; /* * Search for keywords in info string and set values. Attributes * are converted to all lower-case for comparison. */ info = blk->attributes; for ( i = 0; info[i]; i++ ) info[i] = _tolower(info[i]); if ( strstr ( info, "indexed " ) ) blk->indexed = 1; if ( strstr ( info, "default " ) ) blk->defreturn = 1; if ( strstr ( info, "public " ) ) blk->public = 1; if ( strstr ( info, "lookup " ) ) blk->lookup = 1; if ( strstr ( info, "url " ) ) blk->url = 1; max_spec = strstr ( info, "max " ); if ( max_spec ) { sscanf ( &max_spec[4], "%d", &blk->max_size ); } else blk->max_size = 32; return 1; } /*****************************************************************************/ /* Query PH server for its field descriptions and build global list. */ int load_fields ( int qi, char *message ) { int i, length, code, prev_code, alen; char *bufp, *status_code, *index, *name; struct field_info *last, *new; /* * Ask server for field list. */ length = write ( qi, "fields\n", 7 ); if ( length != 7 ) { strcpy ( message, "Write error to ph server\n"); return 0; } last = fields = (struct field_info *) 0; /* * Read results and make form fields. */ prev_code = -2555; for ( bufp = "-"; *bufp == '-'; ) { bufp = next_line ( qi, &length ); if ( length < 0 ) { strcpy ( message, "ERROR: contact with ph server lost"); return 0; } /* * Check status, ignore any non-success. */ status_code = (*bufp == '-') ? &bufp[1] : bufp; if ( *status_code != '2' ) continue; /* * Parse fields withing returned line into status, ndx, name, data. */ index = name = (char *) 0; for ( i = 0; i < length; i++ ) if ( bufp[i] == ':' ) { bufp[i] = '\0'; if ( !index ) { index = &bufp[i+1]; code = atoi ( index ); } else if ( !name ) { name = &bufp[i+1]; } else { i++; break; } } /* * Add data to field structure. */ if ( name ) { if ( code == prev_code ) { /* * Remaining data is description, save in curren info block. */ alen = strlen(&bufp[i]) + 1; if ( alen > sizeof(last->desc_buf) ) { if ( !(new->description = malloc ( alen )) ) { strcpy(message,"malloc error"); return 0; } } strcpy ( last->description, &bufp[i] ); } else { /* * Initialize new block, append to end of list to preserv order */ new = malloc ( sizeof(struct field_info) ); if ( !new ) { strcpy(message,"malloc error"); return 0; } if ( last ) last->next = new; else fields = new; last = new; new->next = (struct field_info *) 0; new->name = new->name_buf; alen = strlen(name) + 1; if ( alen > sizeof(new->name_buf) ) { if ( !(new->name = malloc ( alen )) ) { strcpy(message,"malloc error"); return 0; } } strcpy ( new->name, name ); new->attributes = new->attr_buf; alen = strlen(&bufp[i]) + 2; if ( alen > sizeof(new->attr_buf) ) { if ( !(new->attributes = malloc ( alen )) ) { strcpy(message,"malloc error"); return 0; } } strcpy ( new->attributes, &bufp[i] ); strcpy ( &new->attributes[alen-2], " " ); new->description = new->desc_buf; new->desc_buf[0] = '\0'; new->id = atoi ( index ); /* * Scan for keywords. */ parse_field_info ( new, message ); } prev_code = code; } else break; } return 1; } /*****************************************************************************/ /* Query PH server and generate HTML form for client may use to query the * server for information. The form fields are given names of the form q_nn * for query fields and r=nn for return fields where nn is the index number * for the field. */ int generate_form ( int full_flag, int qi, char * host, int port ) { int i, j, length, code, out, prev_code; char *bufp, *key, *line, *status_code, *index, *name; char buffer[2048]; struct formgen_context ctx; static char *template[] = { "\nPh server on $(HOST)\n\n", "

Ph query, form for $(HOST)

\n", "To search the database for a name, fill in one or more of the fields in", "the form below in activate the 'make query' button, At least one of", "the entered fields must be flagged as indexed.", "
", " ", "", "

", "
Search parameters (* indicates indexed field):
", "
", "$(NAMEFLD)
\n
$(FDESC)$(FNDX)
", "
Last: ", " First:
", "$(QFIELDS)
$(FDESC)$(FNDX)
", "
\n$(NEXTFLD)", "
", "

", "

Output format:
", "
Returned data option:
", "$(RFIELDS) $(FDESC)
", "$(NEXTFLD)
", "
", "

", "", (char *) 0 }; out = 0; ctx.host = host; ctx.seek = (char *) 0; ctx.port = port; ctx.fld = (struct field_info *) 0; ctx.public_override = full_flag; /* * Parse the strings in the template array to produce HTML document * to send to client. First line is skipped for 'full' lists. */ out = 0; buffer[out] = '\0'; for ( i = full_flag ? 1 : 0; template[i]; i++ ) { /* * Search the current string for substitution, flagged by $( */ for ( line=template[i], j = 0; line[j]; j++ ) { if ( (line[j] == '$') && (line[j+1] == '(') ) { /* * Command detected, flush output buffer and find closing ')' * that delimits the command. */ buffer[out] = '\0'; if ( out > 0 ) cgi_printf("%s", buffer); out = 0; for ( key = &line[j]; line[j+1] && (line[j] != ')'); j++ ); /* * Save context, interpet command and restore updated context. */ ctx.cur_line = i; ctx.cur_off = j; interpret_key ( key, buffer, &length, &ctx ); i = ctx.cur_line; j = ctx.cur_off; line = template[i]; out = length; if ( ctx.seek ) { /* * command wants us to skip (forward) to indicated token. * Start at current position. */ int slen = strlen ( ctx.seek ); for ( ; template[i]; i++ ) { for ( line = template[i]; line[j]; j++ ) { if ( line[j] == '$' ) if ( 0 == strncmp(ctx.seek, &line[j], slen) ) { if ( j == 0 ) j = strlen(template[--i])-1; else --j; line = template[i]; ctx.seek = (char *) 0; break; } } if ( !ctx.seek ) break; j = 0; } if ( ctx.seek ) cgi_printf("Seek fail on %s\n", ctx.seek ); } } else { /* * Non-command text, add to output buffer. */ buffer[out++] = line[j]; if ( out > (sizeof(buffer)-3) ) { buffer[out] = '\0'; cgi_printf ( "%s", buffer ); out = 0; } } } buffer[out++] = '\n'; buffer[out] = '\0'; } if ( out > 0 ) cgi_printf ( "%s", buffer ); return 1; } /*****************************************************************************/ static int interpret_key ( char *key, char *buffer, int *length, struct formgen_context *ctx ) { struct field_info *fld; if ( fld = ctx->fld ) { /* * Most substitutions only recongined inside of loops. */ int error = 0; if ( 0 == strncmp (key, "$(FID)", 6) ) { sprintf ( buffer, "%d", fld->id ); } else if ( (0 == strncmp (key, "$(FDESC)", 8)) ) { sprintf ( buffer, "%s%s%s", fld->description, ctx->public_override ? " " : "", ctx->public_override ? fld->attributes : "" ); } else if ( (0 == strncmp (key, "$(FDEF)", 7)) ) { strcpy ( buffer, fld->defreturn ? " checked" : "" ); } else if ( (0 == strncmp (key, "$(FNDX)", 7)) ) { strcpy ( buffer, fld->indexed ? "*" : "" ); } else if ( (0 == strncmp (key, "$(FSIZE)", 8)) ) { sprintf ( buffer, " size=%d maxlength=%d", fld->max_size > 60 ? 60 : fld->max_size, fld->max_size); } else if ( (0 == strncmp (key, "$(FSIZE2)", 9)) ) { sprintf ( buffer, " maxlength=%d", fld->max_size ); } else { error = 1; } if ( !error ) { *length = strlen(buffer); return 1; } } /* */ buffer[0] = '\0'; if ( 0 == strncmp (key, "$(NEXTFLD)", 10 ) ) { if ( !ctx->fld ) fld = fields; else fld = ctx->fld->next; switch ( ctx->field_select ) { case 0: /* 'Query' fields, public and lookup attributes */ for ( ; fld; fld = fld->next ) if (fld->public && (fld->lookup==1)) break; break; case 1: /* 'Query' fields, accept lookup attribute */ for ( ; fld; fld = fld->next ) if ( fld->lookup == 1 ) break; break; case 2: /* 'Return' fields, public only */ for ( ; fld; fld = fld->next ) if ( fld->public ) break; break; case 3: /* all fields */ break; } if ( fld ) { ctx->cur_line = ctx->rep_line; ctx->cur_off = ctx->rep_off; } ctx->fld = fld; } else if ( (0 == strncmp (key, "$(QFIELDS)", 10)) || (0 == strncmp (key, "$(RFIELDS)", 10)) ) { /* * Begin interation sequence. */ ctx->rep_line = ctx->cur_line; ctx->rep_off = ctx->cur_off; ctx->fld = (struct field_info *) 0; ctx->seek = "$(NEXTFLD)"; ctx->field_select = (key[2] == 'Q') ? 0 : 2; if ( ctx->public_override ) ctx->field_select++; } else if ( 0 == strncmp (key, "$(NAMEFLD)", 10) ) { /* * Special, locate name field. Flag lookup so QFIELDS will skip it. */ for ( fld = fields; fld; fld = fld->next ) if ( strcmp (fld->name, "name" ) == 0 ) { if ( fld->lookup ) fld->lookup = 2; break; } ctx->fld = fld; } else if ( 0 == strncmp (key, "$(HOST)", 7) ) { strcpy ( buffer, ctx->host ); } else if ( 0 == strncmp (key, "$(SCRIPT_PATH)", 14) ) { sprintf ( buffer, "%s%s/%d", cgi_info("SCRIPT_NAME"), cgi_info("PATH_INFO"), ctx->port ); } else { /* * No match, dump key to buffer so client sees it for debugging. */ int out = 0; while ( *key && (*key != ')') ) { buffer[out++] = (*key++); if ( out > sizeof(buffer)-2 ) { buffer[out] = '\0'; cgi_printf("%s", buffer); out= 0; } } buffer[out++] = ')'; buffer[out] = '\0'; } *length = strlen ( buffer ); return 1; } /*****************************************************************************/ void error_abort ( char *status_line, char *message ) { cgi_printf ( "content-type: text/plain\nstatus: %s\n\n%s\n", status_line, message ); exit(1); } /*****************************************************************************/ /* Read line of data and return pointer to it. */ static char linebuf[8192]; static int linestate, linefilled, lineused; static char *next_line ( int socket, int *length ) { char cur; int i, start; /* * Scan buffer for next line beginning where we left off. */ #ifdef DEBUG printf("next_line state: %d, used: %d, filled %d\n", linestate, lineused, linefilled ); #endif for ( start = lineused; linestate >= 0; ) { /* * search for end-of-line, trimming CR if it precedes the LF. */ for ( i = start; i < linefilled; i++ ) { cur = linebuf[i]; if ( cur == '\n' ) { /* Found EOL */ *length = i - start - linestate; lineused = i + 1; linebuf[i-linestate] = '\0'; linestate = 0; return &linebuf[start]; } else if ( cur == '\r' ) { linestate = 1; } else if ( linestate ) linestate = 0; } /* * Getting here means no EOL found. Scoot unused portion to beginning of * buffer. */ if ( start > 0 ) { /* No more room in buffer */ for ( lineused = 0; start < linefilled; ) linebuf[lineused++] = linebuf[start++]; start = 0; } /* * Read more data into buffer */ i = read ( socket, &linebuf[lineused], sizeof(linebuf)-lineused-1 ); #ifdef DEBUG printf("read %d into buffer at position %d\n", i, lineused ); #endif if ( i <= 0 ) { *length = lineused; linestate = 0; return linebuf; } linefilled = lineused + i; } /* * Getting here means connection broken. */ *length = -1; linebuf[0] = '\0'; return linebuf; } /*****************************************************************************/ /* Create socket and connect to remote host. */ static int open_remote ( char *remhost, int port ) { struct sockaddr local, remote; struct hostent *hostinfo; int i, j, rem_port, sd, status; char *alt_port; /* */ linestate = -1; linefilled = 0; lineused = 0; rem_port = port; printf("remote host: %s, port: %d\n", remhost, port ); alt_port = strchr ( remhost, ':' ); if ( alt_port ) { *alt_port++ = '\0'; rem_port = atoi ( alt_port ); } hostinfo = gethostbyname ( remhost ); if ( !hostinfo ) { error_abort ( "503 Connect error", "Could not find host address" ); return -1; } /* * Attempt connect to remote host. */ sd = socket ( AF_INET, SOCK_STREAM, 0 ); if ( sd < 0 ) { fprintf(stderr,"Error creating socket: %s\n", strerror(errno) ); return sd; } local.sa_family = AF_INET; for ( j = 0; j < sizeof(local.sa_data); j++ ) local.sa_data[j] = '\0'; local.sa_data[0] = local.sa_data[1] = 0; status = bind ( sd, &local, sizeof ( local ) ); if ( status < 0 ) { error_abort ( "503 Connect error", "Could not bind socket" ); return -1; } remote.sa_family = hostinfo->h_addrtype; for ( j = 0; j < hostinfo->h_length; j++ ) { remote.sa_data[j+2] = hostinfo->h_addr_list[0][j]; } remote.sa_data[0] = rem_port >> 8; remote.sa_data[1] = (rem_port&255); status = connect ( sd, &remote, sizeof(remote) ); if ( status != 0 ) { error_abort ( "503 Connect error", "Could not connect to host" ); return -1; } linestate = 0; /* no CR pending */ linefilled = 0; lineused = 0; return sd; } /*************************************************************************/ /* Interpret contents for form data string and construct ph query command. * the fields array and return_type variable are also set. */ int build_query ( int qi, char *fdata, int content_length, char *command, int command_max, char *host, int port ) { int length, i, j, start, finish, flen, ndx, clen; int return_type, has_indexed; struct field_info *fld; return_type = 0; has_indexed = 0; length = content_length; if ( fdata[length-1] != '&' ) fdata[length++] = '&'; /* force termination*/ start = finish = clen = 0; for ( i = 0; i < length; i++ ) if ( !fdata[i] || (fdata[i] == '&') ) { /* * Value parsed. Unescape characters and look for first '=' * to delimit field name from value. */ flen = i - start; finish = start + flen; for ( j = start; j < finish; j++ ) if ( fdata[j] == '+' ) fdata[j] = ' '; net_unescape_string ( &fdata[start], &flen ); finish = start + flen; fdata[finish] = '\0'; #ifdef DEBUG cgi_printf("field: %s
\n", &fdata[start] ); #endif for ( j = start; j < finish; j++ ) if ( fdata[j] == '=' ) { /* * fdata[start..j-1] is field name, [j+1..finish-1] is value. */ if ( (fdata[start+1] == '_') && ((fdata[start] == 'r') || (fdata[start] == 'q')) ) { /* * decode fields number and lookup field info. */ sscanf ( &fdata[start+2], "%d=", &ndx ); for (fld = fields; fld; fld = fld->next) if ( ndx==fld->id ) { if ( (j+1) >= finish ) break; /* ignore nulls */ if ( fdata[start] == 'q' ) { /* * Append field to query line. */ if ( fld->lookup ) { if ( fld->indexed ) has_indexed = 1; if ( clen == 0 ) { strcpy ( command, "query " ); clen = 6; } else command[clen++] = ' '; sprintf(&command[clen], "%s=\"%s\"", fld->name, &fdata[j+1] ); clen += strlen ( &command[clen] ); } else { cgi_printf("Warning: non-lookup field ignored
\n"); } } else if ( fdata[start]== 'r' ) { fld->explicit_return = 1; } break; } } else if ( strncmp(&fdata[start],"return=",7)==0 ) { if ( strcmp(&fdata[start+7],"all") == 0 ) return_type = 1; else if ( strcmp(&fdata[start+7],"selected") == 0 ) return_type = 2; else if ( strncmp(&fdata[start+7],"full",4) == 0 ) { /* Client asking for full form. */ generate_form ( 1, qi, host, port ); return 0; } } } start = i + 1; } if ( (clen == 0) || !has_indexed ) { cgi_printf ( "Error, at least one indexed field value must be specified
\n"); return 0; } /* * Append return fields. */ if ( return_type == 1 ) strcpy ( &command[clen], " return all" ); else if ( return_type == 2 ) { strcpy ( &command[clen]," return" ); clen += 7; for ( fld = fields; fld; fld = fld->next ) if ( fld->explicit_return ) { command[clen++] = ' '; strcpy ( &command[clen], fld->name ); clen += strlen ( &command[clen] ); } } cgi_printf("ph command: '%s'
\n", command, clen ); return clen; } /****************************************************************************/ /* Read ph server response to query and format in html. */ void display_result ( int qi ) { int length, i, j, prev_ndx, ndx; char *bufp, *rcode, *ndx_str, *fname, *fvalue, buffer[1024]; struct field_info *fld; /* * Read lines until non-negative status. */ prev_ndx = -100; for ( bufp = "-"; (*bufp == '-') || (*bufp == '1'); ) { /* * Get next status line. */ bufp = next_line ( qi, &length ); if ( length < 0 ) { cgi_printf ( "ERROR: contact with ph server lost" ); break; } /* * Parse line. */ rcode = (*bufp == '-') ? &bufp[1] : bufp; ndx_str = fname = (char *) 0; for ( i = 0; i < length; i++ ) if ( bufp[i] == ':' ) { bufp[i] = '\0'; if ( !ndx_str ) { fname = ndx_str = &bufp[i+1]; } else { fname = &bufp[i+1]; break; } } if ( ndx_str ) { ndx = atoi ( ndx_str ); if ( prev_ndx != ndx ) { if ( prev_ndx != -100 ) cgi_printf ( "\n" ); if ( ndx == 0 ) { cgi_printf ( "
Information/status
\n" ); } else { cgi_printf ( "
Entry %d:
\n", ndx ); } prev_ndx = ndx ; } } else { cgi_printf ( "%s
\n", rcode ); continue; } if ( (*rcode >= '2') && (*rcode <= '5') && (fname != ndx_str) ) { while ( *fname == ' ' ) fname++; /* trim leading spaces */ for ( fvalue = fname; *fvalue; fvalue++ ) if (*fvalue == ':') { *fvalue++ = '\0'; break; } else if ( *fvalue == ' ' ) *fvalue = '\0'; /* trim trailing */ if ( *fname ) { for ( fld = fields; fld; fld = fld->next ) { if ( 0 == strcmp ( fld->name, fname ) ) { if ( fld->description ) fname = fld->description; break; } } if ( fld->url ) { int ns; for ( ns = 0; isspace(fvalue[ns]); ns++ ); cgi_printf ( "
%s

\n", &fvalue[ns], fname ); } else cgi_printf ( "
%s
%s
\n", fname, fvalue ); } else cgi_printf ( "%s
\n", fvalue ); } else cgi_printf("%s
\n", fname ? fname : rcode ); } if ( prev_ndx != -100 ) cgi_printf ( "
\n" ); }