ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/thttpd/cgi-src/ssi.c
Revision: 1.1
Committed: Mon Jun 18 21:11:58 2001 UTC (25 years, 3 months ago) by root
Content type: text/plain
Branch: MAIN
CVS Tags: mp_j, dp_j, cp_j, HEAD
Branch point for: connpatch, dirpatch, mmapppatch
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 /* ssi - server-side-includes CGI program
2     **
3     ** Copyright © 1995 by Jef Poskanzer <jef@acme.com>.
4     ** All rights reserved.
5     **
6     ** Redistribution and use in source and binary forms, with or without
7     ** modification, are permitted provided that the following conditions
8     ** are met:
9     ** 1. Redistributions of source code must retain the above copyright
10     ** notice, this list of conditions and the following disclaimer.
11     ** 2. Redistributions in binary form must reproduce the above copyright
12     ** notice, this list of conditions and the following disclaimer in the
13     ** documentation and/or other materials provided with the distribution.
14     **
15     ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16     ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17     ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18     ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19     ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20     ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21     ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22     ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23     ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24     ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25     ** SUCH DAMAGE.
26     */
27    
28     #include <stdio.h>
29     #include <stdlib.h>
30     #include <string.h>
31     #include <time.h>
32     #include <sys/types.h>
33     #include <sys/stat.h>
34    
35     #include "config.h"
36     #include "match.h"
37    
38    
39     #define ST_GROUND 0
40     #define ST_LESSTHAN 1
41     #define ST_BANG 2
42     #define ST_MINUS1 3
43     #define ST_MINUS2 4
44    
45    
46     static void read_file( char* vfilename, char* filename, FILE* fp );
47    
48    
49     static char* argv0;
50     static char* url;
51    
52     static char timefmt[100];
53     static int sizefmt;
54     #define SF_BYTES 0
55     #define SF_ABBREV 1
56     static struct stat sb;
57    
58    
59     static void
60     internal_error( char* reason )
61     {
62     char* title = "500 Internal Error";
63    
64     (void) printf( "\
65     <HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
66     <BODY><H2>%s</H2>\n\
67     Something unusual went wrong during a server-side-includes request:\n\
68     <BLOCKQUOTE>\n\
69     %s\n\
70     </BLOCKQUOTE>\n\
71     </BODY></HTML>\n", title, title, reason );
72     }
73    
74    
75     static void
76     not_found( char* filename )
77     {
78     char* title = "404 Not Found";
79    
80     (void) printf( "\
81     <HTML><HEAD><TITLE>%s</TITLE></HEAD>\n\
82     <BODY><H2>%s</H2>\n\
83     The requested server-side-includes filename, %s,\n\
84     does not seem to exist.\n\
85     </BODY></HTML>\n", title, title, filename );
86     }
87    
88    
89     static void
90     not_found2( char* directive, char* tag, char* filename2 )
91     {
92     char* title = "Not Found";
93    
94     (void) printf( "\
95     <HR><H2>%s</H2>\n\
96     The filename requested in a %s %s directive, %s,\n\
97     does not seem to exist.\n\
98     <HR>\n", title, directive, tag, filename2 );
99     }
100    
101    
102     static void
103     not_permitted( char* directive, char* tag, char* val )
104     {
105     char* title = "Not Permitted";
106    
107     (void) printf( "\
108     <HR><H2>%s</H2>\n\
109     The filename requested in the %s %s=%s directive\n\
110     may not be fetched.\n\
111     <HR>\n", title, directive, tag, val );
112     }
113    
114    
115     static void
116     unknown_directive( char* filename, char* directive )
117     {
118     char* title = "Unknown Directive";
119    
120     (void) printf( "\
121     <HR><H2>%s</H2>\n\
122     The requested server-side-includes filename, %s,\n\
123     tried to use an unknown directive, %s.\n\
124     <HR>\n", title, filename, directive );
125     }
126    
127    
128     static void
129     unknown_tag( char* filename, char* directive, char* tag )
130     {
131     char* title = "Unknown Tag";
132    
133     (void) printf( "\
134     <HR><H2>%s</H2>\n\
135     The requested server-side-includes filename, %s,\n\
136     tried to use the directive %s with an unknown tag, %s.\n\
137     <HR>\n", title, filename, directive, tag );
138     }
139    
140    
141     static void
142     unknown_value( char* filename, char* directive, char* tag, char* val )
143     {
144     char* title = "Unknown Value";
145    
146     (void) printf( "\
147     <HR><H2>%s</H2>\n\
148     The requested server-side-includes filename, %s,\n\
149     tried to use the directive %s %s with an unknown value, %s.\n\
150     <HR>\n", title, filename, directive, tag, val );
151     }
152    
153    
154     static int
155     get_filename( char* vfilename, char* filename, char* directive, char* tag, char* val, char* fn, int fnsize )
156     {
157     int vl, fl;
158     char* cp;
159    
160     /* Used for the various commands that accept a file name.
161     ** These commands accept two tags:
162     ** virtual
163     ** Gives a virtual path to a document on the server.
164     ** file
165     ** Gives a pathname relative to the current directory. ../ cannot
166     ** be used in this pathname, nor can absolute paths be used.
167     */
168     vl = strlen( vfilename );
169     fl = strlen( filename );
170     if ( strcmp( tag, "virtual" ) == 0 )
171     {
172     if ( strstr( val, "../" ) != (char*) 0 )
173     {
174     not_permitted( directive, tag, val );
175     return -1;
176     }
177     /* Figure out root using difference between vfilename and filename. */
178     if ( vl > fl ||
179     strcmp( vfilename, &filename[fl - vl] ) != 0 )
180     return -1;
181     if ( fl - vl + strlen( val ) >= fnsize )
182     return -1;
183     (void) strncpy( fn, filename, fl - vl );
184     (void) strcpy( &fn[fl - vl], val );
185     }
186     else if ( strcmp( tag, "file" ) == 0 )
187     {
188     if ( val[0] == '/' || strstr( val, "../" ) != (char*) 0 )
189     {
190     not_permitted( directive, tag, val );
191     return -1;
192     }
193     if ( fl + 1 + strlen( val ) >= fnsize )
194     return -1;
195     (void) strcpy( fn, filename );
196     cp = strrchr( fn, '/' );
197     if ( cp == (char*) 0 )
198     {
199     cp = &fn[strlen( fn )];
200     *cp = '/';
201     }
202     (void) strcpy( ++cp, val );
203     }
204     else
205     {
206     unknown_tag( filename, directive, tag );
207     return -1;
208     }
209     return 0;
210     }
211    
212    
213     static int
214     check_filename( char* filename )
215     {
216     static int inited = 0;
217     static char* cgi_pattern;
218     int fnl;
219     char* cp;
220     char* dirname;
221     char* authname;
222     struct stat sb;
223     int r;
224    
225     if ( ! inited )
226     {
227     /* Get the cgi pattern. */
228     cgi_pattern = getenv( "CGI_PATTERN" );
229     #ifdef CGI_PATTERN
230     if ( cgi_pattern == (char*) 0 )
231     cgi_pattern = CGI_PATTERN;
232     #endif /* CGI_PATTERN */
233     inited = 1;
234     }
235    
236     /* ../ is not permitted. */
237     if ( strstr( filename, "../" ) != (char*) 0 )
238     return 0;
239    
240     #ifdef AUTH_FILE
241     /* Ensure that we are not reading a basic auth password file. */
242     fnl = strlen(filename);
243     if ( strcmp( filename, AUTH_FILE ) == 0 ||
244     ( fnl >= sizeof(AUTH_FILE) &&
245     strcmp( &filename[fnl - sizeof(AUTH_FILE) + 1], AUTH_FILE ) == 0 &&
246     filename[fnl - sizeof(AUTH_FILE)] == '/' ) )
247     return 0;
248    
249     /* Check for an auth file in the same directory. We can't do an actual
250     ** auth password check here because CGI programs are not given the
251     ** authorization header, for security reasons. So instead we just
252     ** prohibit access to all auth-protected files.
253     */
254     dirname = strdup( filename );
255     if ( dirname == (char*) 0 )
256     return 0; /* out of memory */
257     cp = strrchr( dirname, '/' );
258     if ( cp == (char*) 0 )
259     (void) strcpy( dirname, "." );
260     else
261     *cp = '\0';
262     authname = malloc( strlen( dirname ) + 1 + sizeof(AUTH_FILE) );
263     if ( authname == (char*) 0 )
264     return 0; /* out of memory */
265     (void) sprintf( authname, "%s/%s", dirname, AUTH_FILE );
266     r = stat( authname, &sb );
267     free( dirname );
268     free( authname );
269     if ( r == 0 )
270     return 0;
271     #endif /* AUTH_FILE */
272    
273     /* Ensure that we are not reading a CGI file. */
274     if ( cgi_pattern != (char*) 0 && match( cgi_pattern, filename ) )
275     return 0;
276    
277     return 1;
278     }
279    
280    
281     static void
282     show_time( time_t t, int gmt )
283     {
284     struct tm* tmP;
285     char tbuf[500];
286    
287     if ( gmt )
288     tmP = gmtime( &t );
289     else
290     tmP = localtime( &t );
291     if ( strftime( tbuf, sizeof(tbuf), timefmt, tmP ) > 0 )
292     (void) fputs( tbuf, stdout );
293     }
294    
295    
296     static void
297     show_size( off_t size )
298     {
299     switch ( sizefmt )
300     {
301     case SF_BYTES:
302     (void) printf( "%ld", (long) size ); /* spec says should have commas */
303     break;
304     case SF_ABBREV:
305     if ( size < 1024 )
306     (void) printf( "%ld", (long) size );
307     else if ( size < 1024 )
308     (void) printf( "%ldK", (long) size / 1024L );
309     else if ( size < 1024*1024 )
310     (void) printf( "%ldM", (long) size / (1024L*1024L) );
311     else
312     (void) printf( "%ldG", (long) size / (1024L*1024L*1024L) );
313     break;
314     }
315     }
316    
317    
318     static void
319     do_config( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
320     {
321     /* The config directive controls various aspects of the file parsing.
322     ** There are two valid tags:
323     ** timefmt
324     ** Gives the server a new format to use when providing dates. This
325     ** is a string compatible with the strftime library call.
326     ** sizefmt
327     ** Determines the formatting to be used when displaying the size of
328     ** a file. Valid choices are bytes, for a formatted byte count
329     ** (formatted as 1,234,567), or abbrev for an abbreviated version
330     ** displaying the number of kilobytes or megabytes the file occupies.
331     */
332    
333     if ( strcmp( tag, "timefmt" ) == 0 )
334     {
335     (void) strncpy( timefmt, val, sizeof(timefmt) - 1 );
336     timefmt[sizeof(timefmt) - 1] = '\0';
337     }
338     else if ( strcmp( tag, "sizefmt" ) == 0 )
339     {
340     if ( strcmp( val, "bytes" ) == 0 )
341     sizefmt = SF_BYTES;
342     else if ( strcmp( val, "abbrev" ) == 0 )
343     sizefmt = SF_ABBREV;
344     else
345     unknown_value( filename, directive, tag, val );
346     }
347     else
348     unknown_tag( filename, directive, tag );
349     }
350    
351    
352     static void
353     do_include( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
354     {
355     char vfilename2[1000];
356     char filename2[1000];
357     FILE* fp2;
358    
359     /* Inserts the text of another document into the parsed document. */
360    
361     if ( get_filename(
362     vfilename, filename, directive, tag, val, filename2,
363     sizeof(filename2) ) < 0 )
364     return;
365    
366     if ( ! check_filename( filename2 ) )
367     {
368     not_permitted( directive, tag, filename2 );
369     return;
370     }
371    
372     fp2 = fopen( filename2, "r" );
373     if ( fp2 == (FILE*) 0 )
374     {
375     not_found2( directive, tag, filename2 );
376     return;
377     }
378    
379     if ( strcmp( tag, "virtual" ) == 0 )
380     {
381     if ( strlen( val ) < sizeof( vfilename2 ) )
382     (void) strcpy( vfilename2, val );
383     else
384     (void) strcpy( vfilename2, filename2 ); /* same size, has to fit */
385     }
386     else
387     {
388     if ( strlen( vfilename ) + 1 + strlen( val ) < sizeof(vfilename2) )
389     {
390     char* cp;
391     (void) strcpy( vfilename2, vfilename );
392     cp = strrchr( vfilename2, '/' );
393     if ( cp == (char*) 0 )
394     {
395     cp = &vfilename2[strlen( vfilename2 )];
396     *cp = '/';
397     }
398     (void) strcpy( ++cp, val );
399     }
400     else
401     (void) strcpy( vfilename2, filename2 ); /* same size, has to fit */
402     }
403    
404     read_file( vfilename2, filename2, fp2 );
405     (void) fclose( fp2 );
406     }
407    
408    
409     static void
410     do_echo( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
411     {
412     char* cp;
413     time_t t;
414    
415     /* Prints the value of one of the include variables. Any dates are
416     ** printed subject to the currently configured timefmt. The only valid
417     ** tag is var, whose value is the name of the variable you wish to echo.
418     */
419    
420     if ( strcmp( tag, "var" ) != 0 )
421     unknown_tag( filename, directive, tag );
422     else
423     {
424     if ( strcmp( val, "DOCUMENT_NAME" ) == 0 )
425     {
426     /* The current filename. */
427     (void) fputs( filename, stdout );
428     }
429     else if ( strcmp( val, "DOCUMENT_URI" ) == 0 )
430     {
431     /* The virtual path to this file (such as /~robm/foo.shtml). */
432     (void) fputs( vfilename, stdout );
433     }
434     else if ( strcmp( val, "QUERY_STRING_UNESCAPED" ) == 0 )
435     {
436     /* The unescaped version of any search query the client sent. */
437     cp = getenv( "QUERY_STRING" );
438     if ( cp != (char*) 0 )
439     (void) fputs( cp, stdout );
440     }
441     else if ( strcmp( val, "DATE_LOCAL" ) == 0 )
442     {
443     /* The current date, local time zone. */
444     t = time( (time_t*) 0 );
445     show_time( t, 0 );
446     }
447     else if ( strcmp( val, "DATE_GMT" ) == 0 )
448     {
449     /* Same as DATE_LOCAL but in Greenwich mean time. */
450     t = time( (time_t*) 0 );
451     show_time( t, 1 );
452     }
453     else if ( strcmp( val, "LAST_MODIFIED" ) == 0 )
454     {
455     /* The last modification date of the current document. */
456     if ( fstat( fileno( fp ), &sb ) >= 0 )
457     show_time( sb.st_mtime, 0 );
458     }
459     else
460     {
461     /* Try an environment variable. */
462     cp = getenv( val );
463     if ( cp == (char*) 0 )
464     unknown_value( filename, directive, tag, val );
465     else
466     (void) fputs( cp, stdout );
467     }
468     }
469     }
470    
471    
472     static void
473     do_fsize( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
474     {
475     char filename2[1000];
476    
477     /* Prints the size of the specified file. */
478    
479     if ( get_filename(
480     vfilename, filename, directive, tag, val, filename2,
481     sizeof(filename2) ) < 0 )
482     return;
483     if ( stat( filename2, &sb ) < 0 )
484     {
485     not_found2( directive, tag, filename2 );
486     return;
487     }
488     show_size( sb.st_size );
489     }
490    
491    
492     static void
493     do_flastmod( char* vfilename, char* filename, FILE* fp, char* directive, char* tag, char* val )
494     {
495     char filename2[1000];
496    
497     /* Prints the last modification date of the specified file. */
498    
499     if ( get_filename(
500     vfilename, filename, directive, tag, val, filename2,
501     sizeof(filename2) ) < 0 )
502     return;
503     if ( stat( filename2, &sb ) < 0 )
504     {
505     not_found2( directive, tag, filename2 );
506     return;
507     }
508     show_time( sb.st_mtime, 0 );
509     }
510    
511    
512     static void
513     parse( char* vfilename, char* filename, FILE* fp, char* str )
514     {
515     char* directive;
516     char* cp;
517     int ntags;
518     char* tags[200];
519     int dirn;
520     #define DI_CONFIG 0
521     #define DI_INCLUDE 1
522     #define DI_ECHO 2
523     #define DI_FSIZE 3
524     #define DI_FLASTMOD 4
525     int i;
526     char* val;
527    
528     directive = str;
529     directive += strspn( directive, " \t\n\r" );
530    
531     ntags = 0;
532     cp = directive;
533     for (;;)
534     {
535     cp = strpbrk( cp, " \t\n\r\"" );
536     if ( cp == (char*) 0 )
537     break;
538     if ( *cp == '"' )
539     {
540     cp = strpbrk( cp + 1, "\"" );
541     ++cp;
542     if ( *cp == '\0' )
543     break;
544     }
545     *cp++ = '\0';
546     cp += strspn( cp, " \t\n\r" );
547     if ( *cp == '\0' )
548     break;
549     if ( ntags < sizeof(tags)/sizeof(*tags) )
550     tags[ntags++] = cp;
551     }
552    
553     if ( strcmp( directive, "config" ) == 0 )
554     dirn = DI_CONFIG;
555     else if ( strcmp( directive, "include" ) == 0 )
556     dirn = DI_INCLUDE;
557     else if ( strcmp( directive, "echo" ) == 0 )
558     dirn = DI_ECHO;
559     else if ( strcmp( directive, "fsize" ) == 0 )
560     dirn = DI_FSIZE;
561     else if ( strcmp( directive, "flastmod" ) == 0 )
562     dirn = DI_FLASTMOD;
563     else
564     {
565     unknown_directive( filename, directive );
566     return;
567     }
568    
569     for ( i = 0; i < ntags; ++i )
570     {
571     if ( i > 0 )
572     putchar( ' ' );
573     val = strchr( tags[i], '=' );
574     if ( val == (char*) 0 )
575     val = "";
576     else
577     *val++ = '\0';
578     if ( *val == '"' && val[strlen( val ) - 1] == '"' )
579     {
580     val[strlen( val ) - 1] = '\0';
581     ++val;
582     }
583     switch( dirn )
584     {
585     case DI_CONFIG:
586     do_config( vfilename, filename, fp, directive, tags[i], val );
587     break;
588     case DI_INCLUDE:
589     do_include( vfilename, filename, fp, directive, tags[i], val );
590     break;
591     case DI_ECHO:
592     do_echo( vfilename, filename, fp, directive, tags[i], val );
593     break;
594     case DI_FSIZE:
595     do_fsize( vfilename, filename, fp, directive, tags[i], val );
596     break;
597     case DI_FLASTMOD:
598     do_flastmod( vfilename, filename, fp, directive, tags[i], val );
599     break;
600     }
601     }
602     }
603    
604    
605     static void
606     slurp( char* vfilename, char* filename, FILE* fp )
607     {
608     char buf[1000];
609     int i;
610     int state;
611     int ich;
612    
613     /* Now slurp in the rest of the comment from the input file. */
614     i = 0;
615     state = ST_GROUND;
616     while ( ( ich = getc( fp ) ) != EOF )
617     {
618     switch ( state )
619     {
620     case ST_GROUND:
621     if ( ich == '-' )
622     state = ST_MINUS1;
623     break;
624     case ST_MINUS1:
625     if ( ich == '-' )
626     state = ST_MINUS2;
627     else
628     state = ST_GROUND;
629     break;
630     case ST_MINUS2:
631     if ( ich == '>' )
632     {
633     buf[i - 2] = '\0';
634     parse( vfilename, filename, fp, buf );
635     return;
636     }
637     else if ( ich != '-' )
638     state = ST_GROUND;
639     break;
640     }
641     if ( i < sizeof(buf) - 1 )
642     buf[i++] = (char) ich;
643     }
644     }
645    
646    
647     static void
648     read_file( char* vfilename, char* filename, FILE* fp )
649     {
650     int ich;
651     int state;
652    
653     /* Copy it to output, while running a state-machine to look for
654     ** SSI directives.
655     */
656     state = ST_GROUND;
657     while ( ( ich = getc( fp ) ) != EOF )
658     {
659     switch ( state )
660     {
661     case ST_GROUND:
662     if ( ich == '<' )
663     { state = ST_LESSTHAN; continue; }
664     break;
665     case ST_LESSTHAN:
666     if ( ich == '!' )
667     { state = ST_BANG; continue; }
668     else
669     { state = ST_GROUND; putchar( '<' ); }
670     break;
671     case ST_BANG:
672     if ( ich == '-' )
673     { state = ST_MINUS1; continue; }
674     else
675     { state = ST_GROUND; (void) fputs ( "<!", stdout ); }
676     break;
677     case ST_MINUS1:
678     if ( ich == '-' )
679     { state = ST_MINUS2; continue; }
680     else
681     { state = ST_GROUND; (void) fputs ( "<!-", stdout ); }
682     break;
683     case ST_MINUS2:
684     if ( ich == '#' )
685     {
686     slurp( vfilename, filename, fp );
687     state = ST_GROUND;
688     continue;
689     }
690     else
691     { state = ST_GROUND; (void) fputs ( "<!--", stdout ); }
692     break;
693     }
694     putchar( (char) ich );
695     }
696     }
697    
698    
699     int
700     main( int argc, char** argv )
701     {
702     char* script_name;
703     char* path_info;
704     char* path_translated;
705     FILE* fp;
706    
707     argv0 = argv[0];
708    
709     /* Default formats. */
710     (void) strcpy( timefmt, "%a %b %e %T %Z %Y" );
711     sizefmt = SF_BYTES;
712    
713     /* The MIME type has to be text/html. */
714     (void) fputs( "Content-type: text/html\n\n", stdout );
715    
716     /* Get the name that we were run as. */
717     script_name = getenv( "SCRIPT_NAME" );
718     if ( script_name == (char*) 0 )
719     {
720     internal_error( "Couldn't get SCRIPT_NAME environment variable." );
721     exit( 1 );
722     }
723    
724     /* Append the PATH_INFO, if any, to get the full URL. */
725     path_info = getenv( "PATH_INFO" );
726     if ( path_info == (char*) 0 )
727     {
728     internal_error( "Couldn't get PATH_INFO environment variable." );
729     exit( 1 );
730     }
731     url = (char*) malloc( strlen( script_name ) + strlen( path_info ) + 1 );
732     if ( url == (char*) 0 )
733     {
734     internal_error( "Out of memory." );
735     exit( 1 );
736     }
737     (void) sprintf( url, "%s%s", script_name, path_info );
738    
739     /* Get the name of the file to parse. */
740     path_translated = getenv( "PATH_TRANSLATED" );
741     if ( path_translated == (char*) 0 )
742     {
743     internal_error( "Couldn't get PATH_TRANSLATED environment variable." );
744     exit( 1 );
745     }
746    
747     if ( ! check_filename( path_translated ) )
748     {
749     not_permitted( "initial", "PATH_TRANSLATED", path_translated );
750     exit( 1 );
751     }
752    
753     /* Open it. */
754     fp = fopen( path_translated, "r" );
755     if ( fp == (FILE*) 0 )
756     {
757     not_found( path_translated );
758     exit( 1 );
759     }
760    
761     /* Read and handle the file. */
762     read_file( path_info, path_translated, fp );
763    
764     (void) fclose( fp );
765     exit( 0 );
766     }