6 * File Browser/Getter for World Wide Web Browsers.
12 * This is a Common Gateway Interface program which when accessed
13 * with an HTTP GET as SCRIPT_URL (see #defines below) will return
14 * a directory listing of files available on the host machine.
16 * The directory listing is in the shape of an HTML form with a
17 * "checkbox" button for each directory entry. The size of each file
18 * or an indication that it is a directory are given.
20 * A button for "mode select" is also given.
22 * The default mode is File Browse.
23 * A selected file is displayed, or a selected directory generates a
24 * new HTML form for that directory.
26 * The other mode is File Get.
27 * Selected files and/or directories are put in a tar archive and
28 * compressed back to the Web browser program.
31 * MJC: Martin Clayton (Starlink)
32 * {enter_new_authors_here}
38 * Added a direct "change directory to" text entry field.
39 * Changed note relating to Netscape to reflect change in Company name.
41 * Limited access to directories under /star tree.
42 * {enter_further_changes_here}
45 * Nothing checks that a file for browse is a text file.
46 * Default file protections are used, so a remote user can access
47 * any unprotected files on the system hosting this program.
48 * Some large HTML forms are slowly processed by the common
49 * Web browser Mosaic, this is best overcome by using a more advanced
50 * browser such as Netscape Communications' Netscape but remains a general
52 * The code may well be OSF/1 specific.
53 * {note_further_problems_here}
56 * {note_any_bugs_here}
61 /*Standard Include files:
66 #include <sys/types.h>
73 #define MAX_ENTRIES (10240) /* Maximum number of lines in cgi request. */
74 #define LIST_ENTRIES (10240) /* Maximum number of files in a Get request. */
75 #define TEXT_BUFSIZE (1024) /* Internal buffer size. */
76 #define ENT_WID (64) /* Maximum length of a file name. */
77 #define MODE_FLAG "spoingmode" /*Internal flag for Browse/Get mode. */
79 #define DEFAULT_DIR "/star" /* First directory to be listed. */
81 /*Where the program will be placed.
83 #define SCRIPT_URL "http://www.star.ucl.ac.uk/mjc-cgi/S.tar.Z"
85 /*Maintenance details.
87 #define AUTHOR_NAME "Martin Clayton"
88 #define AUTHOR_URL "<A HREF=\"mailto:mjc@starlink.ucl.ac.uk\">mjc@starlink.ucl.ac.uk</A>"
90 /*Structure to hold a cgi request list entry.
97 /*Function prototypes, local.
99 void html_file( char *a, char *b, int nf );
100 void html_directory( char *a, int nf );
101 void dir_dot_parse( char *s, char *d );
102 void tar_zed( entry *e, int m );
105 *Function prototypes, external.
107 char *makeword( char *line, char stop );
108 char *fmakeword( FILE *f, char stop, int *len );
109 void unescape_url( char *url );
110 void plustospace (char *str );
113 *Function prototyping for lclint checking
115 /*@nullterminated@*/char *getenv(/*@nullterminated@*/char *);
117 int atoi(/*@nullterminated@*/char *);
119 /*@nullterminated@*/char *strcpy(char *, /*@nullterminated@*/char *);
121 int strcmp(/*@nullterminated@*/ const char *s1, /*@nullterminated@*/ const char *s2);
123 /*@nullterminated@*/char *strcat(/*@nullterminated@*/char *dst, /*@nullterminated@*/const char *src);
125 int stat(/*@nullterminated@*/const char *path, struct stat *buf);
127 int strncmp(const char *s1, const char *s2, size_t n);
129 DIR *opendir(/*@nullterminated@*/const char *dirname);
131 char *strncpy(char *dst, const char *src, size_t n);
133 void qsort(void *base, size_t nel, size_t width,
134 int (*compar) (const void *, const void *));
136 struct tm *localtime(const time_t *clock);
138 void *memcpy(void *s1, const void *s2, size_t n);
140 int sprintf(char *s, /*@nullterminated@*/const char *format, /* args */ ...);
142 int system(const char *string);
146 /*****************************************************************************/
153 entry entries[MAX_ENTRIES];
155 int m; /*number of lines in request*/
157 char wdir[TEXT_BUFSIZE];
158 /*@nullterminated@*/ char adir[TEXT_BUFSIZE];
165 if ( !getenv( "CONTENT_LENGTH" ) ) {
168 /*Move request lines into "entry" array.
171 cl = atoi( getenv( "CONTENT_LENGTH" ) );
173 for ( x = 0; cl && ( !feof( stdin ) ); x++ ) {
175 entries[x].val = fmakeword( stdin, '&', &cl );
176 plustospace( entries[x].val );
177 unescape_url( entries[x].val );
178 entries[x].name = makeword( entries[x].val, '=' );
182 /*No request made. Initial inquiry from remote machine.
184 if ( ( m == -1 ) || ( !entries[0].name ) ) {
185 html_directory( "", 0 );
187 /*Decode the request.
191 /* Request for compressed archive data.
193 if ( !strcmp( entries[0].name, MODE_FLAG ) ) {
194 tar_zed( &entries[1], m - 1 );
198 } else if ( !strcmp( entries[0].name, "Explicit" ) ) {
200 /* Check for explicit directory or file name.
202 if ( strcmp( entries[0].val, "" ) ) {
203 strcpy( adir, entries[0].val );
205 } else if ( m > 0 ) {
207 /* Build required file name, first directory part.
209 strcpy( adir, entries[1].val );
211 /* Add in a / if not root directory.
213 if ( strcmp( adir, "/" ) ) {
217 /* Append the actual file name.
219 strcat( adir, entries[1].name );
222 strcpy( adir, DEFAULT_DIR );
225 /* Modify the file name if it is . or ..
227 dir_dot_parse( adir, wdir );
229 /* Get file information.
231 stat( wdir, &statbuf );
233 /* Build a directory listing.
235 if ( statbuf.st_mode & S_IFDIR) {
236 html_directory( wdir, m - 1 );
238 /* Display a file's contents.
241 html_file( wdir, entries[1].name, m - 1 );
248 exit ( EXIT_SUCCESS );
252 /*****************************************************************************/
259 * Build directory listing in HTML.
265 * html_directory( dirname, nf )
268 * dirname = char * (Given)
269 * Pointer to name of the directory to be listed.
271 * Total number of files in request.
274 * MJC: Martin Clayton (Starlink)
275 * {enter_new_authors_here}
281 * Added support for file last modification time to be displayed.
283 * Removed spurious <P> from second control button pair.
285 * Added gopher images to directory listings.
287 * Limited access to directories under /star tree.
288 * {enter_further_changes_here}
291 * {note_any_bugs_here}
297 char *dirname, /* Name of directory to be displayed. */
299 int nf /* Number of files in request. */
309 struct dirent *the_file;
314 char full_path[TEXT_BUFSIZE];
315 char adir[TEXT_BUFSIZE];
316 char ent_tab[LIST_ENTRIES][ENT_WID];
325 /*When not an empty string, use supplied file name.
327 if ( *dirname != '\0' ) {
328 strcpy( full_path, dirname );
330 /*Otherwise set to default directory.
333 strcpy( full_path, DEFAULT_DIR );
336 /*Make sure request is in /star tree, if not go to default directory.
338 if ( strncmp( full_path, DEFAULT_DIR, 5 ) ) {
340 strcpy( full_path, DEFAULT_DIR );
343 /*Try to open the directory.
345 the_directory = opendir( full_path );
347 /*Start response output.
353 www_head( "StarZ: File browser-getter." );
360 hheading( "StarZ: File browser-getter", 1 );
363 /*Print error message if unable to open directory.
365 if ( !the_directory ) {
366 printf( "Could not open %s directory.%c", full_path, NEW_LINE );
369 /*Otherwise start to build listing.
373 /* Currently, only one directory can be selected for listing.
374 * The first chosen is displayed, others are ignored except that
375 * the following message is displayed.
378 printf( "<B>Only first entry in list will be displayed.</B>%c",
382 /* Print warning that request is not in /star tree.
385 printf( "<B>Your request has been denied, default %s directory selected instead.</B>%c",
386 DEFAULT_DIR, NEW_LINE );
390 * URL of where script will reside.
392 printf( "<FORM action=\"%s\" method=\"POST\">%c",
393 SCRIPT_URL, NEW_LINE );
395 /* Submit and reset buttons. These are duplicated later.
397 printf( "<INPUT type=submit value=\" Submit Request \">%c", NEW_LINE );
398 printf( "<INPUT type=reset value=\" Reset \"><P>%c", NEW_LINE );
400 /* Browse or Get mode-select button.
402 printf( "<INPUT TYPE=\"checkbox\" NAME=\"%s\" ", MODE_FLAG );
403 printf( "VALUE=\"%s\"> <B>Get or Browse (default) mode</B><BR>%c",
404 full_path, NEW_LINE);
406 /* Text input field for entry of explicit file or directory name.
408 printf( "<INPUT SIZE=32 TYPE=TEXT NAME=\"Explicit\"> <B>Change Directory</B><BR>%c", NEW_LINE );
412 printf( "<H2>Directory of %s</H2>%c", full_path, NEW_LINE );
414 /* Build directory listing.
417 while ( ( the_file = readdir( the_directory ) ) &&
418 ( entries < LIST_ENTRIES ) ) {
420 /* Don't include . and .. in list for sort.
422 if ( !strcmp( the_file->d_name, "." ) ) {
425 } else if ( !strcmp( the_file->d_name, ".." ) ) {
428 /* Other file names added to list.
431 strncpy( ent_tab[entries], the_file->d_name, ENT_WID );
432 ent_tab[entries][ENT_WID - 1] = '\0';
437 /* Close the directory.
439 closedir( the_directory );
441 /* Sort the directory list.
443 qsort( (void *)(ent_tab), entries + 1, ENT_WID, strcmp );
445 /* Start output of the listing.
447 printf( "<PRE WIDTH=64>%c", NEW_LINE );
449 /* Current directory button.
451 printf( "<INPUT TYPE=\"checkbox\" NAME=\".\" VALUE=\"%s\">",
453 printf( " <-current directory->%c", NEW_LINE );
455 /* Parent directory button.
457 if ( strcmp( full_path, "/" ) ) {
458 printf( "<INPUT TYPE=\"checkbox\" NAME=\"..\" VALUE=\"%s\">",
460 printf( " <-parent directory%c", NEW_LINE );
465 for ( i = 1; i <= entries; i++ ) {
467 /* Start with button.
469 printf( "<INPUT TYPE=\"checkbox\" NAME=\"%s\" VALUE=\"%s\">",
470 ent_tab[i], full_path );
472 /* Get file information.
474 strcpy( adir, full_path );
475 if ( strcmp( adir, "/" ) ) {
478 strcat( adir, ent_tab[i] );
479 stat( adir, &statbuf );
481 /* Mark as a directory if that is the case.
483 if ( statbuf.st_mode & S_IFDIR) {
484 printf( " <IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-menu\">" );
485 printf( " %-32s", ent_tab[i] );
486 printf( "%9s", "directory" );
488 /* Otherwise print the size of the file.
491 if ( extension( ent_tab[i], "html" ) ) {
492 printf( " <IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-text\">" );
495 printf( " <IMG ALIGN=absbottom BORDER=0 SRC=\"internal-gopher-unknown\">" );
497 printf( " %-32s", ent_tab[i] );
498 printf( "%9d", (int)( statbuf.st_size ) );
501 /* Last modification date/time information.
503 (void)(memcpy( &ttime, localtime(&statbuf.st_mtime),
504 sizeof(struct tm)) );
505 (void)(strncpy( tbuf, asctime( &ttime ), 26 ) );
506 *( tbuf + 24 ) = '\0';
507 printf( " %s%c", tbuf, NEW_LINE );
511 /* End of the directory listing.
513 printf( "</PRE>%c", NEW_LINE );
515 /* Output a second set of control buttons.
517 printf( "<INPUT type=submit value=\" Submit Request \">%c",
519 printf( "<INPUT type=reset value=\" Reset \">%c", NEW_LINE );
523 printf( "</FORM>%c", NEW_LINE );
526 /*End main part of response
531 /*HTML Address field.
533 www_address( AUTHOR_NAME, AUTHOR_URL, NULL );
546 /*****************************************************************************/
553 * Finds simplest version of a file specifier with . or .. at end.
559 * dir_dot_parse( indir, outdir )
562 * indir = char * (Given)
563 * Pointer to the file specification to be checked.
564 * outdir = char * (Given and Returned)
565 * Pointer to space for parsed specification, must be big enough
566 * to hold converted file name.
569 * MJC: Martin Clayton (Starlink)
570 * {enter_new_authors_here}
575 * {enter_further_changes_here}
578 * {note_any_bugs_here}
599 /*Mark default return value as a blank string.
601 strcpy( outdir, "" );
602 if ( !strcmp( indir, "" ) ) {
606 /*Find the end of the supplied string.
614 /*Point to last non-null character in string.
616 if ( cptr > indir ) {
623 /*Does string end in a '.'?
625 if ( *cptr == '.' ) {
628 /* Current directory, remove /. part of file name.
630 if ( *cptr == '/' ) {
634 } else if ( *cptr == '.' ) {
637 /* Parent directory, remove /.. part of file name and
638 * name of the current directory, unless the current
639 * directory is root, in which case just remove the ..
641 if ( *cptr == '/' ) {
642 while ( cptr > indir ) {
644 if ( *cptr == '/' ) {
649 while ( cptr < eptr ) {
660 /*Copy the parsed file specifier to output buffer.
662 strcpy ( outdir, indir );
670 /*****************************************************************************/
677 * Display a text file in an HTML 'wrapper'.
683 * html_file( fname, sname, nf )
686 * fname = char * (Given)
687 * Pointer to full path of the file to be displayed.
688 * sname = char * (Given)
689 * Pointer to name of the file only.
691 * Total number of files in request.
694 * MJC: Martin Clayton (Starlink)
695 * {enter_new_authors_here}
701 * Added missing <BODY>.
702 * {enter_further_changes_here}
705 * {note_any_bugs_here}
711 char *fname, /* Full path of file. */
712 char *sname, /* Name only of file. */
714 int nf /* Total number of files in request. */
722 char thecommand[4 * TEXT_BUFSIZE];
723 char title[1024]; /* Workspace for page title. */
727 /*It's a gif wrap in simple HTML page.
729 if ( extension( fname, "gif" ) ) {
734 sprintf( title, "StarZ: %s", sname );
741 printf( "<IMG SRC=\"%s\"><P>%c", fname, NEW_LINE );
743 /* Attach an address field.
745 www_address( AUTHOR_NAME, AUTHOR_URL, NULL );
753 /*If the file appears NOT to be an HTML file, then wrap it in
754 *some appropriate HTML.
756 } else if ( !extension( fname, "html" ) ) {
761 sprintf( title, "StarZ: %s", sname );
766 /* Only the first file of a multiple-file display request is piped back.
769 hline( "<B>Only first entry in list will be displayed.</B>" );
772 /* Wrap the file in preformatted HTML style.
776 /* Flush stdout prior to calling cat to display file.
780 /* Build command line to display the file.
782 sprintf( thecommand, "cat %s\0x00", fname );
786 if ( system ( thecommand ) ) {
787 printf( "Could not access file %s%c", fname, NEW_LINE );
794 /* Attach an address field.
796 www_address( AUTHOR_NAME, AUTHOR_URL, NULL );
804 /*Other case is a file we expect to be in HTML format.
806 } else if ( extension( fname, "html" ) ) {
810 printf( "Content-type: text/html%c%c", NEW_LINE, NEW_LINE );
812 /* Only the first file of a multiple-file display request is piped back.
815 printf( "<B>Only first entry in list will be displayed.</B>%c",
819 /* Flush stdout prior to calling cat
823 /* Pipe out HTML file.
825 sprintf( thecommand, "cat %s\0x00", fname );
826 if ( system ( thecommand ) ) {
827 printf( "Could not access file %s%c", fname, NEW_LINE );
837 /*****************************************************************************/
844 * Compare the last part of a file specifier with a supplied extension.
850 * extension( fname, fext )
853 * fname = char * (Given)
854 * Pointer to name of the file to be checked.
855 * fext = char * (Given)
856 * Pointer to the extension to be tested for.
859 * Returns the result of a strcmp( ) call comparing the relevant
860 * part of the supplied text with the supplied extension.
863 * MJC: Martin Clayton (Starlink)
864 * {enter_new_authors_here}
869 * {enter_further_changes_here}
872 * {note_any_bugs_here}
878 char *fname, /* The file specification. */
879 char *fext /* A file extension, without ".". */
892 /*Find end of file name.
898 if ( cptr > fname ) {
902 /*Find last . in file name and point to character after it.
904 while ( ( *cptr != '.' ) && ( cptr > fname ) ) {
907 if ( cptr > fname ) {
911 /*Compare supplied extension with part of file name.
913 return ( !strcmp( cptr, fext) );
917 /*****************************************************************************/
924 * Pipe a compressed tar archive of the requested files to stdout.
933 * ent = entry * (Given)
934 * Pointer to first item in a list of "entry" structures.
936 * Total number of items in list.
939 * MJC: Martin Clayton (Starlink)
940 * {enter_new_authors_here}
945 * {enter_further_changes_here}
948 * {note_any_bugs_here}
954 entry *ent, /* Pointer to start of list. */
956 int n /* Number of items in list. */
964 char request_text[65536]; /* Buffer for command line. */
966 int i; /* Loop index. */
971 /*A null request was received, return message.
974 printf( "Content-type: text/html%c%c", NEW_LINE, NEW_LINE );
975 printf( "<B>Compressed tar file down-load request detected.</B><P>%c",
977 printf( "<B>Your request was empty!</B>%c", NEW_LINE );
979 /*Otherwise build command line to compress tar file to stdout.
983 /* ent[].val is the same foe all entries, the base directory for
984 * the archive build. Mark a "change directory" command for this.
986 strcpy( request_text, "cd " );
987 strcat( request_text, ent[1].val );
989 /* Add tar command with flags for pipe to stdout and follow soft links.
991 strcat( request_text, " ; tar -cfh - " );
993 /* Now add all the list entries.
995 for ( i = 1; i <= n; i++ ) {
996 strcat( request_text, ent[i].name );
997 strcat( request_text, " " );
1000 /* Finally, the pipe to compress with flag to send output to stdout.
1002 strcat( request_text, " | compress -c" );
1006 printf( "Content-Encoding: x-compress%c%c", NEW_LINE, NEW_LINE );
1012 /* Execute compress archive command line.
1014 if ( system( request_text ) ) {
1015 printf( "Content-type: text/html%c%c", NEW_LINE, NEW_LINE );
1016 printf( "<B>Transfer failed.</B><P>%c", NEW_LINE );