/* HTML Form Kit, * These C routines help you to create HTML FORMs, * with all the usual kinds of input elements, * and to process those forms in CGI programs. * The element-creation routines check against the previous form, * so they can create PERSISTENT elements; * in which user settings are retained from one form to the next, * except of course as they are changed by the user. *

* This program source includes some strings containing HTML, * which will probably be rendered * rather than shown * in their proper form when you view this page, * as well as strings likely to be mistaken * for HTML tags, * and thus not shown at all. * Use the View/Source * feature of your viewer for an accurate display, * or just save the file and use your favorite editor. *

* If you would like to port this code to Java, * go right ahead. *

* Morris Hirsch * <mhirsch@ipdinc.com> */ static char rcsid[] = "$Id$"; #include #include #include /*---------------------------------------------------------------* * These functions were adapted from the * utilities in the NCSA's httpd sources *---------------------------------------------------------------*/ char *c2x (char *from, char *to) { sprintf (to, "%%%02x", (int) * from); return (to); } char *escape_url (char *str) { char *e, *p, *s, c[4]; if (!(str)) return (char *) 0; if (!(e = (char *) malloc (sizeof (char) * ((strlen (str) * 4) + 1)))) return (char *) 0; s = e; for (p = str; *p; *p++) { *s = '\0'; if (isalnum (*p)) *(s++) = *p; else if (*p == ' ') *(s++) = '+'; else { strcat (s, c2x (p, c)); s += 3; } } *s = '\0'; return (e); } char x2c (char *what) { register char digit; if (!(what) || !(*what)) return (char) 0; digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); digit *= 16; digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); return (digit); } void unescape_url (char *url) { register int x, y; if (!(url) || !(*url)) return; for (x = 0, y = 0; url[y]; ++x, ++y) { if ((url[x] = url[y]) == '%') { url[x] = x2c (&url[y + 1]); y += 2; } } url[x] = '\0'; } void plustospace (char *str) { register int x; if (!(str) || !(*str)) return; for (x = 0; str[x]; x++) if (str[x] == '+') str[x] = ' '; } int wordlen (char *from, char separator) { int result = 0; while ((from) && (*from) && (separator != *from)) { result++; from++; } return (result); } void decode_data (char *data) { register int x, y; plustospace (data); for (x = 0, y = 0; data[y]; ++x, ++y) { if ((data[x] = data[y]) == '%') { data[x] = x2c (&data[y + 1]); y += 2; } } data[x] = '\0'; } int ind (char *s, char c) { register int x; for (x = 0; s[x]; x++) if (s[x] == c) return x; return - 1; } void spacetoplus (char *str) { register int x; for (x = 0; str[x]; x++) if (str[x] == ' ') str[x] = '+'; } /* Duplicate string up to either terminal '&' or NULL. Copy ONLY this portion, and we provide terminal NULL on the copy. */ char *web_ndup (char *str) { int nchp; char *dup; if (!(str)) return (char *) 0; nchp = wordlen (str, '&'); if (!(dup = (char *) malloc (sizeof (char) * (nchp + 1)))) return (char *) 0; strncpy (dup, str, nchp); dup[nchp] = '\0'; return dup; } /* Decode the user input. See: * http://hoohoo.ncsa.uiuc.edu/cgi/overview.html * for more information about CGI scripts. * Note! you can re-read getenv but not stdin! */ char *get_cgi_query_string (void) { char *request_method = getenv ("REQUEST_METHOD"); char *query_string = getenv ("QUERY_STRING"); char *content_length = getenv ("CONTENT_LENGTH"); static char *content = NULL; int c_len, ii, pair_count = 0; char *ptr; /* already set just return it */ if ((content)) return content; if (!request_method) { puts ("No REQUEST_METHOD set -- must be GET or POST"); exit (0); } if (!strcmp (request_method, "GET")) { if (!query_string) { puts ("No QUERY_STRING set"); exit (0); } content = strdup (query_string); c_len = strlen (content); } else if (!strcmp (request_method, "POST")) { if (!content_length) { puts ("No CONTENT_LENGTH was set for POST method"); exit (0); } c_len = atoi (content_length); content = (char *) malloc (c_len + 1); if (c_len != fread (content, sizeof (char), c_len, stdin)) { printf ("Error reading form content\n"); exit (0); } *(content + c_len) = '\0'; } else { printf ("Unknown REQUEST_METHOD: %s -- must be GET or POST\n", request_method); exit (0); } return content; } /*---------------------------------------------------------------* * These functions manage the name=value pairs *---------------------------------------------------------------*/ /* Shift left as needed to cover any leading white space, and provide early NULL to mask any trailing white space. This works directly in the calling text string; you may call it with a strdup to preserve original. If that is not an issue, you may call it (void) and just use the side-effect. */ static char *remove_surrounding_spaces (char *text) { char *get = text; char *put = text; char *nsp = text; while ((get) && (*get) && isspace (*get)) get++; while ((get) && (*get)) { if (!isspace (*get)) nsp = get; *(put++) = *(get++); } if ((nsp)) *(++nsp) = '\0'; return text; } typedef struct { char *name; char *value; int used; } name_value_t; name_value_t *cgi_vector = NULL; /* Create a name=value vector from the cgi query string, for easier access to the value for any given name. */ name_value_t *create_cgi_vector (char *content) { name_value_t *cgi_vector = NULL; int c_len, ii; char *ptr; if (!(content) || !(*content)) return NULL; c_len = strlen (content); cgi_vector = (name_value_t *)malloc (ii = (sizeof (name_value_t) * c_len / 4)); memset (cgi_vector, 0, ii); for (ii = 0, ptr = strdup (content); (ptr) && (*ptr); ii++) { cgi_vector[ii].name = ptr; if ((ptr = strchr (ptr, '&'))) { *ptr = '\0'; ptr++; } if ((cgi_vector[ii].value = strchr (cgi_vector[ii].name, '='))) { *(cgi_vector[ii].value++) = '\0'; plustospace (cgi_vector[ii].value); unescape_url (cgi_vector[ii].value); } } return cgi_vector; } /* Create a name=value vector similar to the cgi vector, from name=value lines in a file. This is intended for configuration or preference settings. Blank lines and lines starting with '!' or '#' are skipped. Caller provides the open file and should later close it. Note! fgets() puts newline at the end of each line so we chop it. Folks tend to put spaces around an equals sign so we remove those. */ name_value_t *create_file_vector (FILE *stream) { name_value_t *new_vector = NULL; int ii, lines = 0; char line_buffer[1000]; char *eqptr; char *nlptr; if (!(stream)) return NULL; rewind (stream); while ((fgets (line_buffer, 999, stream))) lines++; new_vector = (name_value_t *)malloc (ii = (sizeof (name_value_t) * (1+ lines))); memset (new_vector, 0, ii); rewind (stream); lines = 0; while ((fgets (line_buffer, 999, stream))) { if ((nlptr = strchr (line_buffer, '\n'))) *nlptr = '\0'; if ((line_buffer[0]) && ('!' != line_buffer[0]) && ('#' != line_buffer[0]) && ((eqptr = strchr (line_buffer, '=')))) { *(eqptr++) = '\0'; new_vector[lines].name = remove_surrounding_spaces (strdup (line_buffer)); new_vector[lines].value = remove_surrounding_spaces (strdup (eqptr)); } } return new_vector; } /* Returns pointer to the master value not a copy; caller may NULL it to "kill" the entry after it is used, also the flag notes entry has been used. Names ending with asterisk mean prefix match. */ char *get_vector_value (char *name, name_value_t *vector) { char *ptr; char *value = NULL; int ii; int prefix_len = 0; if (!(name) || !(*name) || !(vector)) return NULL; if ((ptr = strrchr (name, '*'))) prefix_len = ptr - name; for (ii = 0; (vector[ii].name); ii++) if (((prefix_len > 0) && !strncmp (name, vector[ii].name, prefix_len)) || !strcmp (name, vector[ii].name)) { vector[ii].used++; return vector[ii].value; } return NULL; } /* Returns vector of pointers to the master values not copies; caller may NULL them to "kill" the entries used, also the flag notes entry has been used. Names ending with asterisk mean prefix match. */ char **get_vector_multi_values (char *name, name_value_t *vector) { char *ptr; char **values = NULL; int ii, instances = 0; int prefix_len = 0; if (!(name) || !(*name) || !(vector)) return NULL; /* match anything starting with this prefix */ if ((ptr = strrchr (name, '*'))) prefix_len = ptr - name; for (ii = 0; (vector[ii].name); ii++) if (((prefix_len > 0) && !strncmp (name, vector[ii].name, prefix_len)) || !strcmp (name, vector[ii].name)) instances++; if (!instances) return NULL; if (!(values = (char **)malloc (sizeof (char *) * (instances +1)))) return NULL; memset (values, 0, (sizeof (char *) * (instances +1))); for (instances = 0, ii = 0; (vector[ii].name); ii++) if (((prefix_len > 0) && !strncmp (name, vector[ii].name, prefix_len)) || !strcmp (name, vector[ii].name)) { vector[ii].used++; values [instances++] = vector[ii].value; } return values; } /* Call repeatedly for all name=value pairs, or all used less than some number of times, starting with index of zero, then using the returned next-index, until next-index returned is zero. */ int next_vector_entry (int next, int limit, char **name, char **value, name_value_t *vector) { int ii; if (!(vector) || !(name) || !(value)) return 0; for (ii = next; (vector[ii].name); ii++) { if (!(limit) || (limit > vector[ii].used)) { *name = vector[ii].name; *value = vector[ii].value; return ii+1; } } return 0; } /* Convenience wrappers that imply cgi query as data source. Names ending with asterisk mean prefix match. */ char *get_cgi_value (char *name) { if (!(cgi_vector)) cgi_vector = create_cgi_vector (get_cgi_query_string ()); return get_vector_value (name, cgi_vector); } char **get_cgi_multi_values (char *name) { if (!(cgi_vector)) cgi_vector = create_cgi_vector (get_cgi_query_string ()); return get_vector_multi_values (name, cgi_vector); } int next_cgi_entry (int next, int limit, char **name, char **value) { if (!(cgi_vector)) cgi_vector = create_cgi_vector (get_cgi_query_string ()); return next_vector_entry (next, limit, name, value, cgi_vector); } /*---------------------------------------------------------------* * These functions emit html for various FORM elements. * They default to the prior value if found in last submitted FORM, * and return the new (set or default) value. *---------------------------------------------------------------*/ /* Pass some name=value as a HIDDEN element of a FORM. The user cannot see it or change it. It will be returned as name=value by the new FORM. */ char *print_html_hidden (FILE * stream, char *name, char *value) { if (!(name) || !(*name)) return NULL; if (!(value)) value = get_cgi_value (name); if (!(value) || !(*value)) return NULL; fprintf (stream, "\n", name, value); return value; } /* Pass along name=value pairs as HIDDEN elements in the new FORM, if they appeared (or were HIDDEN) in the submitted FORM, but are not yet mentioned in the new one. (Use this call after elements have (or haven't) been created.) Useful when some FORM in a sequence does not want to show values, but wants to carry them forward so a later FORM can show them. Pass either all if no name is provided, or any exact name matches, or all prefix matches if name ends with an asterisk. */ void print_html_unused_as_hidden (FILE * stream, char *name) { char *cgi_name; char *cgi_value; char *ptr; int next = 0; int prefix_len = 0; /* match anything starting with this prefix */ if ((name) && (*name) && (ptr = strrchr (name, '*'))) prefix_len = ptr - name; while ((next = next_cgi_entry (next, 1, &cgi_name, &cgi_value))) if ((!(name) || !(*name)) || ((prefix_len > 0) && !strncmp (name, cgi_name, prefix_len)) || !strcmp (name, cgi_name)) (void) print_html_hidden (stream, cgi_name, cgi_value); } /* Emit HTML for an option menu: The name amd values array must be provided, so that name=value may be returned for the selected item. If both values and labels arrays are provided, the label is displayed, the VALUE=value option is used internally, and will be returned as "name=value" by the FORM. If no labels array is provided, the value is used as the visible label, and still will be returned as "name=value" by the FORM. At most one item will be marked SELECTED, if the current value is provided and matches a value or label. */ char *print_html_menu (FILE * stream, char *name, char *current, char **values, char **labels) { int ii; if (!(name) || !(*(name))) return NULL; if (!(current)) current = get_cgi_value (name); if (!(values) || !(*(values))) return NULL; fprintf (stream, "", stream); return current; } /* Emit HTML for a listbox: Either MULTIPLE or SIZE=nn makes SELECT a listbox instead of optionmenu. There is no MULTIPLE=nn way to control max selections -- sorry. Must have one or the other option else just an optionmenu. No use for labels with the listbox. */ char **print_html_list_box (FILE * stream, char *name, char **currents, char **values, int multiple, int size) { int ii, jj; int selected; if (!(name) || !(*(name))) return NULL; if (!(currents)) currents = get_cgi_multi_values (name); if (!(values) || !(*(values))) return NULL; fprintf (stream, "", stream); return currents; } /* Emit HTML for a group of radio buttons: The radio-button-group MUST have a name. A values array must be provided. If both values and labels arrays are provided, the label is displayed next to the button. At most one item will be marked CHECKED, if the current value is provided and matches a value. */ char *print_html_radio (FILE * stream, char *name, char *current, char **values, char **labels) { int ii; if (!(name) || !(*(name))) return NULL; if (!(current)) current = get_cgi_value (name); if (!(values) || !(*(values))) return NULL; for (ii = 0; (values[ii]); ii++) fprintf (stream, "%s", name, values[ii], ((current) && !strcmp (values[ii], current)) ? "CHECKED" : "", ((labels) && (labels[ii])) ? labels[ii] : ""); return current; } /* Emit HTML for a single toggle / check button: The button MUST have a name, but value and label are optional. Shows "[] Bold" and reports "font_bold=active" print_html_toggle (stdout, "font_bold", NULL, "active", "Bold"); Shows "[] Italic" and reports "font_italic=on" print_html_toggle (stdout, "font_italic", NULL, NULL, "Italic"); Shows "[]" w/out any label and reports "font_underline=on" print_html_toggle (stdout, "font_underline", NULL, NULL, NULL); An item will be marked CHECKED, if the current value is provided and matches value. If a label is provided it is displayed after the button. */ char *print_html_toggle (FILE * stream, char *name, char *current, char *value, char *label) { if (!(name) || !(*(name))) return NULL; fprintf (stream, "", stream); if ((label) && (*(label))) fprintf (stream, " %s", label); return current; } /* Emit HTML for a single toggle / check button, that shares a name with others, each having a unique value. The buttons need not be adjacent; if they are they form a button-box, similar to a radio-box but allowing multiple selections. Both name and value are required, label is optional. Mark this CHECKED if currents has a name=value pair that matches. */ char *print_html_shared (FILE * stream, char *name, char **currents, char *value, char *label) { int jj; char * selected = NULL; if (!(name) || !(*(name))) return NULL; if (!(value) || !(*(value))) return NULL; fprintf (stream, "", stream); if ((label) && (*(label))) fprintf (stream, " %s", label); return selected; } /* Emit HTML for a SUBMIT button: Both name and value are optional: This shows "Push Me" and reports "N_V_submit=Push+Me" print_html_submit (stdout, "N_V_submit", "Push Me"); This shows "Submit Query" and reports "no_value_submit=Submit+Query" print_html_submit (stdout, "no_value_submit", NULL); This shows "Who Am I" and nothing appears in submitted string print_html_submit (stdout, NULL, "Who Am I?"); This shows "Submit Query" and nothing appears in submitted string print_html_submit (stdout, NULL, NULL); -> A FORM may have more than one SUBMIT button. */ void print_html_submit (FILE * stream, char *name, char *value) { fputs ("\n", stream); } /* Submit a form by clicking in an image; reported as name.x=XX and name.y=YY coordinates if name is provided, otherwise x=XX and y=YY coordinates. */ void print_html_image_submit (FILE * stream, char *name, char *src, char *alt, char *label) { if (!(src) || !(*src)) return; fprintf (stream, "%s\n", label); else fputs (">\n", stream); } /* Emit HTML for a single-line text input: also has a MAXLENGTH attribute we have not used. */ char *print_html_text_input (FILE * stream, char *name, char *value, int size) { if (!(name) || !(*(name))) return NULL; if (!(value)) value = get_cgi_value (name); if (!(value) || !(*(value))) value = ""; fprintf (stream, "\n", name, value, size); return value; } /* Emit HTML for a single-line Password (no echo) text input. * WARNING! Although the user input is not visible on screen, * it is transmitted as typed with no encrytption. */ void print_html_password_input (FILE * stream, char *name, int size) { if (!(name) || !(*(name))) return; fprintf (stream, "\n", name, size); } /* Emit HTML for a multi-line text input: */ char *print_html_text_area (FILE * stream, char *name, char *value, int rows, int cols) { if (!(name) || !(*(name))) return NULL; if (!(value)) value = get_cgi_value (name); if (!(value) || !(*(value))) value = ""; if (rows < 1) rows = 1; if (cols < 3) cols = 3; fprintf (stream, "", name, rows, cols, value); return value; } /* Note that the temporary file must be in or under "DOCUMENT_ROOT" or it will not be served at all, and it must have the ".html" extension, or it will be shown as text rather than rendered as html. Also note that YOU must arrange for the file to be removed. The SRC file URLs are based from DOCUMENT_ROOT (here /usr/www/htdocs) thus "/info/inquery.html" means "/usr/www/htdocs/info/inquery.html" The gateway will not serve (rather than execute) files under cgi root, and scripts should not create temporaries there anyway. Scripts may even have trouble creating temporaries under doc root, but at least here we are able to do so... */ FILE *open_html_temp_file (char *prefix, char *exname, int *dlen) { char *droot = NULL; char dirname[80]; char *fname = NULL; FILE *stream = NULL; if ((droot = getenv ("DOCUMENT_ROOT"))) { strcpy (dirname, droot); *dlen = strlen (dirname); strcat (dirname, "/tmp"); if ((fname = tempnam (dirname, prefix))) { strcpy (exname, fname); strcat (exname, ".html"); stream = fopen (exname, "w"); } } return stream; } /********* * Compile with -DTESTING for the main / demo -- install it under cgi-bin *********/ #ifdef TESTING void draw_demo_form (FILE *stream, char *target) { char *sizes[] = { "xs", "sm", "med", "lg", "xl", NULL }; char *Size_Labels[] = { "Extra Small", "Small", "Medium", "Large", "Extra Large", NULL }; char *tmpch; int next = 0; char *name; char *value; char seq_nr[8]; if ((target) && (*target)) fprintf (stream, "

\n", getenv ("SCRIPT_NAME"), target); else fprintf (stream, "\n", getenv ("SCRIPT_NAME")); fprintf (stream, "

This output generated by %s

\n", rcsid); while ((next = next_cgi_entry (next, 0, &name, &value))) fprintf (stream, "(%s)=(%s)\n", name, value); /* How to pass along a hidden sequence number */ if ((tmpch = get_cgi_value ("seq_nr"))) sprintf (seq_nr, "%d", 1+ atoi (tmpch)); else strcpy (seq_nr, "1"); print_html_hidden (stream, "seq_nr", seq_nr); fputs ("
  • Ways to enter text:", stream); /* Anything still unused as HIDDEN */ print_html_unused_as_hidden (stream, NULL); fputs ("
  • ", stream); } void main (int argc, char **argv) { char exname[80]; FILE *stream; int dlen = 0; char *target = NULL; if ((target = get_cgi_value ("target"))) printf ("Content-type: text/html\nWindow-target:%s\n\n", target); else puts ("Content-type: text/html\n\n"); puts ("Test Form"); #ifdef FRAMES /* Determine somehow if this is the first call, here we look for ABSENCE of a known element of the form. -> First time issue the FRAMES layout, and refer to a temporary file in which we put the html. -> Later we just emit the new html directly, just as if no frames were in use. It will update in the frame. */ /* Any FRAME created with a NAME=name also needs SRC=url, otherwise later updates targeted to it will fail, instead creating a new pop-up window of that name. Clearly a Netscape bug! They have been told... */ if (!(get_cgi_value ("seq_nr"))) { if ((stream = open_html_temp_file ("hfk_", exname, &dlen))) { fputs ("Test Form\n", stream); fputs ("\n", stream); draw_demo_form (stream, NULL); fputs ("\n", stream); fclose (stream); puts (""); printf (" \n", exname+dlen); puts (" "); puts (""); exit (0); } puts ("ERROR CREATING TEMP FILE"); exit (0); } #endif puts (""); draw_demo_form (stdout, NULL); puts (""); } #endif