/* lastupdated.c * ------------- * A simple CGI program to output the last modification date * of a specified file relative to the calling file. * * Copyright (C)2000 Daniel G. Delaney, Dionysia Software * * Written by: Daniel G. Delaney (Dionysos@Dionysia.org) * Version: 1.0.4 * Last updated: 3 January 2000 * Status: Freeware * This program may be freely used and distributed * as long as this copyright information is not modified. * If you make improvements, please contact the author * and suggest them for the official distribution. * * * COMPILING: * I wrote this on a machine running FreeBSD 3.1 and it compiled * just fine with the straight forward command: * * cc lastupdated.c -o lastupdated * * That will produce an executable named "lastupdated" which you should * place into your cgi-bin directory. * * I've also compiled it on an AIX machine with that command. * On HP-UX you have to use the compiler in extended ANSI mode, * thus: "cc -Ae lastupdated.c". I haven't tried it on any others yet. * * * USAGE: * lastupdated should be called in a server side include. If the program * is called with no file specified it will give the date of the file in * which the INCLUDE is found. File names are specified relative to the * file with the INCLUDE statement unless an extra '/' is included. So, * for example, to print the last modification date of a file called * "foobar.html" in the directory "/my/departent" from the * DocumentRoot of the server, the following include would be used: * * * * lastupdated can also take a QUERY_STRING to tell it how to format the * date information. All of the date information can be output, including * the day of the week and the time. The file name, server name and file * path can also be output. Any characters in the QUERY_STRING * will be output as-is except for the following substitutions: * * %WS = Day of the Week--Short (Fri) * %WL = Day of the Week--Short (Friday) * %WN = Day of the Week--Numeric (6) * %WP = Day of the Week--Padded Numeric (06) * %MS = Month--Short (Feb) * %ML = Month--Long (February) * %MN = Month--Numeric (2) * %MP = Month--Padded Numeric (01) * %DM = Day of the Month (4) * %DP = Day of the Month--Padded (04) * %Y2 = Year--2 digits (98) * %Y4 = Year--4 digits (1998) * %YD = Day of the Year (46) * %YP = Day of the Year--Padded (046) * %H1 = Hour--12 hour (3) * %P1 = Hour--Padded 12 Hour (03) * %H2 = Hour--24 hour (15) * %P2 = Hour--Padded 24 hour * %MM = Minutes * %PM = Minutes--Padded * %SS = Seconds * %PS = Seconds--Padded * %A1 = AM/PM--No periods, lowercase (am) * %A2 = AM/PM--No periods, uppercase (AM) * %A3 = AM/PM--Periods, lowercase (a.m.) * %A4 = AM/PM--Periods, uppercase (A.M.) * %TZ = Time Zone Abbreviation (EST) * %SB = File size in bytes * %SK = File size in kilobytes * %SM = File size in megabytes * %SG = File size in gigabytes * %SA = File size--automatically determine units * %SN = Server Name * %FN = File Name * %FP = Path to File from DocumentRoot * * If no query string is found, the date is output in ISO format, * %Y4-%MN-%DM (e.g., 1998-5-23). If you wanted to output to look * like "10 March 1998", the QUERY_STRING should contain * "%DM %ML %Y4". If you want the output to look like "Fri, * 10-Mar-98" the QUERY_STRING should contain "%WS, %DM-%MS-%Y2" * */ #include #include #include #include #include #include #include /* * Routine to convert a two-digit hex number into the character it represents */ char hex2char(char *number) { char character; character = (number[0] >= 'A' ? ((number[0] & 0xdf) - 'A')+10 : (number[0] - '0')); character *= 16; character += (number[1] >= 'A' ? ((number[1] & 0xdf) - 'A')+10 : (number[1] - '0')); return(character); } /* * Main program routine */ main() { /* Initialize some constants */ char short_months[12][4] = { "Jan", "Feb", "Mar", "Apr", "May","Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; char long_months[12][10] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; char short_days[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; char long_days[7][10] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; /* Initialize some variables */ char finalpath[1024] = {""}, *userid, filepath[255] = {""}, *filename, *splitpoint; char answer[4096]={""}, addition[1024]={""}; char ampm1[3], ampm2[3], ampm3[5], ampm4[5]; char code[] = " "; char fullyear[5]={""}, *shortyear; char sizestring[30]={""}; char timezone[21]={""}; int i = 0, j = 0, ampmhour, missing_env = 0; long filesize; struct stat fileinfo; struct tm *modtime; struct passwd *passwd; /* Declare the pointers for CGI variables */ char *server_name, *document_root, *document_uri, *document_name, *path_info, *query_string; char nullstr[] = {"\0\0\0"}; /* Start by outputting the MIME header */ printf("Content-type: text/plain\n\r\n\r"); /* Get the CGI environment variables needed */ if( !(server_name = getenv("SERVER_NAME")) ) missing_env = 1; if( !(document_root = getenv("DOCUMENT_ROOT")) ) missing_env = 1; if( !(document_uri = getenv("DOCUMENT_URI")) ) missing_env = 1; if( !(document_name = getenv("DOCUMENT_NAME")) ) missing_env = 1; if( !(path_info = getenv("PATH_INFO")) ) path_info = nullstr; if( !(query_string = getenv("QUERY_STRING")) ) query_string = nullstr; if(missing_env) { printf("

One or more required environment variables was missing.

\n"); exit(-1); } /* Save the path and file name of this page in case we need to output them */ if(path_info[0] == '\0') strcpy(filepath, document_uri); else strcpy(filepath, path_info); filename = strrchr(filepath, '/'); filename[0] = '\0'; /* Terminate it before the file name */ filename++; /* * Parse the file specification * The file can be specified as either an Absolute Path to DocumentRoot or to a user directory * or as a relative path from a file in DocumentRoot of a user directory */ /* File specified as an ABSOLUTE PATH... */ /* ...to a file in DocumentRoot */ if(path_info[1] == '/') { path_info++; /* Chop off the extra leading '/' */ strcpy(finalpath, document_root); strcat(finalpath, path_info); } /* ...to a file in a www directory in a user account */ else if(path_info[1] == '~') { userid = path_info + 2; /* Chop off the extra leading '/' and the '~' */ splitpoint = strchr(userid, '/'); splitpoint[0] = '\0'; splitpoint++; /* Get the user's home directory */ passwd = getpwnam(userid); strcpy(finalpath, passwd->pw_dir); strcat(finalpath, "/www/"); strcat(finalpath, splitpoint); } /* File specified as an RELATIVE PATH... */ else { /* ...from a file in a user directory */ if(document_uri[1] == '~') { userid = document_uri + 2; /* Chop off the leading '/' and '~' */ splitpoint = strchr(userid, '/'); splitpoint[0] = '\0'; /* Get the user's home directory */ passwd = getpwnam(userid); strcpy(finalpath, passwd->pw_dir); strcat(finalpath, "/www"); splitpoint[0] = '/'; document_uri = splitpoint; } /* ...from a file in DocumentRoot */ else strcpy(finalpath, document_root); /* If no path is specified at all, return date for calling file */ if(path_info[0] == '\0' || (path_info[0] == '/' && path_info[1] == '\0') ) { strcat(finalpath, document_uri); } else { /* Get rid of the file name from the URI path */ splitpoint = strrchr(document_uri, '/'); splitpoint[0] = '\0'; strcat(finalpath, document_uri); strcat(finalpath, path_info); } } /* If the file doesn't exist or isn't readable, don't do anything */ if(stat(finalpath, &fileinfo) == 0 ) { modtime = localtime(&fileinfo.st_mtime); filesize = fileinfo.st_size; strftime(timezone, 20, "%Z", modtime); /* The year is returned not as a two digit year, but as an integer offset from 1900 */ /* Get the full 4-digit year */ sprintf(fullyear, "%d", 1900 + modtime->tm_year); /* Then get the last two digits from that */ shortyear = fullyear + 2; /* Automatically determine size units */ if(filesize < 1024) sprintf(sizestring, "%d bytes", filesize); else if(filesize < 1048576) sprintf(sizestring, "%.*fk", ( ((filesize % 1024) < 100) ? 0 : 1), ((float)filesize / 1024) ); else if(filesize < 1073741824) sprintf(addition, "%.*fM", ( ((filesize % 1048576) < 100) ? 0 : 1), ((float)filesize / 1048576) ); else sprintf(addition, "%.*fG", ( ((filesize % 1073741824) < 100) ? 0 : 1), ((double)filesize / 1073741824) ); if(modtime->tm_hour < 12) { strcpy(ampm1, "am"); strcpy(ampm2, "AM"); strcpy(ampm3, "a.m."); strcpy(ampm4, "A.M."); } else { strcpy(ampm1, "pm"); strcpy(ampm2, "PM"); strcpy(ampm3, "p.m."); strcpy(ampm4, "P.M."); } ampmhour = modtime->tm_hour; if(modtime->tm_hour > 12) ampmhour -= 12; if(modtime->tm_hour == 0) ampmhour = 12; /* Use the default, if no format specified */ if(query_string[0] == '\0') printf("%s-%d-%d", fullyear, modtime->tm_mon + 1, modtime->tm_mday); else { /* Interpret the Query String one character at a time */ while (*query_string != '\0') { if(*query_string == '%') { code[0] = *(++query_string); code[1] = *(++query_string); /* This is ugly, but it's the simplest way to do it :o) */ if(strcmp(code,"WS")==0) strcpy(addition, short_days[modtime->tm_wday]); else if(strcmp(code,"WL")==0) strcpy(addition, long_days[modtime->tm_wday]); else if(strcmp(code,"WN")==0) sprintf(addition, "%d", modtime->tm_wday + 1); else if(strcmp(code,"WP")==0) sprintf(addition, "%02d", modtime->tm_wday + 1); else if(strcmp(code,"MS")==0) strcpy(addition, short_months[modtime->tm_mon]); else if(strcmp(code,"ML")==0) strcpy(addition, long_months[modtime->tm_mon]); else if(strcmp(code,"MN")==0) sprintf(addition, "%d", modtime->tm_mon + 1); else if(strcmp(code,"MP")==0) sprintf(addition, "%02d", modtime->tm_mon + 1); else if(strcmp(code,"DM")==0) sprintf(addition, "%d", modtime->tm_mday); else if(strcmp(code,"DP")==0) sprintf(addition, "%02d", modtime->tm_mday); else if(strcmp(code,"Y2")==0) strcpy(addition, shortyear); else if(strcmp(code,"Y4")==0) strcpy(addition, fullyear); else if(strcmp(code,"YD")==0) sprintf(addition, "%d", modtime->tm_yday); else if(strcmp(code,"YP")==0) sprintf(addition, "%03d", modtime->tm_yday); else if(strcmp(code,"H1")==0) sprintf(addition, "%d", ampmhour); else if(strcmp(code,"P1")==0) sprintf(addition, "%02d", ampmhour); else if(strcmp(code,"H2")==0) sprintf(addition, "%d", modtime->tm_hour); else if(strcmp(code,"P2")==0) sprintf(addition, "%02d", modtime->tm_hour); else if(strcmp(code,"MM")==0) sprintf(addition, "%d", modtime->tm_min); else if(strcmp(code,"PM")==0) sprintf(addition, "%02d", modtime->tm_min); else if(strcmp(code,"SS")==0) sprintf(addition, "%d", modtime->tm_sec); else if(strcmp(code,"PS")==0) sprintf(addition, "%02d", modtime->tm_sec); else if(strcmp(code,"A1")==0) strcpy(addition, ampm1); else if(strcmp(code,"A2")==0) strcpy(addition, ampm2); else if(strcmp(code,"A3")==0) strcpy(addition, ampm3); else if(strcmp(code,"A4")==0) strcpy(addition, ampm4); else if(strcmp(code,"TZ")==0) strcpy(addition, timezone); else if(strcmp(code,"SA")==0) strcpy(addition, sizestring); else if(strcmp(code,"SB")==0) sprintf(addition, "%d", filesize); else if(strcmp(code,"SK")==0) sprintf(addition, "%.*f", ( ((filesize % 1024) < 100) ? 0 : 1), ((float)filesize / 1024) ); else if(strcmp(code,"SM")==0) sprintf(addition, "%.*f", ( ((filesize % 1048576) < 100) ? 0 : 1), ((float)filesize / 1048576) ); else if(strcmp(code,"SG")==0) sprintf(addition, "%.*f", ( ((filesize % 1073741824) < 100) ? 0 : 1), ((double)filesize / 1073741824) ); else if(strcmp(code,"FN")==0) strcpy(addition, filename); else if(strcmp(code,"FP")==0) strcpy(addition, filepath); else if(strcmp(code,"DR")==0) strcpy(addition, document_root); else if(strcmp(code,"SN")==0) strcpy(addition, server_name); else if(strchr("0123456789ABCDEF", code[0]) && strchr("0123456789ABCDEF", code[1])) { addition[0] = hex2char(code); addition[1] = '\0'; } else { strcpy(addition, "%"); strcat(addition, code); } } /* If the character just read was a percent sign */ else { if(*query_string == '+') *query_string = ' '; addition[0] = *query_string; addition[1] = '\0'; } /* Only add it if it won't excede the size of the answer buffer */ if(strlen(answer)+strlen(addition) < 4095) { strcat(answer, addition); } query_string++; } /* while (*query_string != '\0') */ printf("%s", answer); } /* Either use the default, or use the specified format */ } /* If the file's modified date is read successfully */ else { printf("[Error: Couldn't open file '%s']\n", finalpath); } } /* main */