/* pb.c */ /* Andrew Davison, July 1996 (ad@cs.mu.oz.au) */ /* A Web page containing a GIF is returned. The GIF contains the user's three biorhythm curves for a specified month/year. The user supplies their birthday and month/year of interest via a form. The number of days since the user's birthday is calculated by converting the birthday and month/year of interest into their number of Julian days. The difference is the start day for the biorhythm equation. The gnuplot commands are in PLOT_CMDS, and this is modified and copied into TEMP_CMDS before being displayed. The two changes insert the start day and the month/year details. gnuplot saves its drawing into <PIC>.ppm, and this is converted into GIF format with ppmtogif. URL_PIC is the URL of PIC. Tools used: gnuplot, ppmtogif */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* for read(), lseek(), write(), close(), lockf() */ #include <fcntl.h> /* for open() */ #define BKGROUND "http://www.cs.mu.oz.au/~ad/chalk.jpg" #define MAX_ENTRIES 2 /* number of input fields */ #define MAXLEN 500 /* max length of a string */ #define NOVAL "$$no-value$$" /* when no value is found */ #define COUNT_NM "/home/staff/ad/www_public/code/biorhythm/count" #define PLOT_CMDS "/home/staff/ad/www_public/code/biorhythm/bio.plt" #define TEMP_CMDS "/home/staff/ad/www_public/code/biorhythm/tmp.plt" #define PIC "/home/staff/ad/www_public/code/biorhythm/pbio" #define URL_PIC "http://www.cs.mu.oz.au/~ad/code/biorhythm/pbio" #define DELNO 4 #define LOCKFNM "/home/staff/ad/www_public/code/biorhythm/lock##" typedef struct { /* structure for HTML name/value pair */ char *name; char *val; } entry; typedef struct date { int day; int month; /* 1 to 12 */ int year; /* e.g. 1996 */ } Date; Date start_date(entry entries[], int etnum, long int *start); char *get_val(entry entries[], int etnum, char *field); Date check_date(Date d); long int toJulian(Date d); int get_count(); void make_bpic(long int start, Date sd, int count); void modify_plot(char *plotnm, char *tempnm, long int start, Date d); /* Locking Utilities */ int my_lock(void); void my_unlock(int fd); /* Standard Form Functions */ void start_reply(char *title); void cgi_errs(); int build_entries(entry entries[]); char *find_value(entry entries[], int etnum, char *name); /* Debugging Utilities */ int input_entries(entry entries[]); void show_entries(entry entries[], int etnum); /* CGI utilities written by Rob McCool */ char *makeword(char *line, char stop); char *fmakeword(FILE *f, char stop, int *len); void unescape_url(char *url); char x2c(char *what); void plustospace(char *str); int main() { entry entries[MAX_ENTRIES]; int etnum = 0, count; Date sd; long int start; start_reply("Biorhythm Output"); cgi_errs(); etnum = build_entries(entries); /* etnum = input_entries(entries); show_entries(entries, etnum); */ sd = start_date(entries, etnum, &start); printf("<P>Number of days since your birth: %ld</P>\n", start); count = get_count(); make_bpic(start, sd, count); printf("<P><img src=\"%s%d.gif\" alt=\"[No Graph]\"></P>\n", URL_PIC, count); printf("</body></html>\n"); return 1; } Date start_date(entry entries[], int etnum, long int *start) /* Get the user's birthday (bd) and month/year of interest (sd) from the form details. Work out the number of days between bd and sd, and store it in start. sd is also returned. */ { char *val; Date bd, sd; long int bdjul, sdjul; val = get_val(entries, etnum, "birth"); sscanf(val, "%d/%d/%d", &bd.day, &bd.month, &bd.year); bd = check_date(bd); printf("<P>Birthday: %d/%d/%d<br>\n", bd.day, bd.month, bd.year); bdjul = toJulian(bd); val = get_val(entries, etnum, "bio"); sscanf(val, "%d/%d", &sd.month, &sd.year); sd.day = 1; sd = check_date(sd); printf("Biorhythm month: %d/%d</P>\n", sd.month, sd.year); sdjul = toJulian(sd); if (sdjul >= bdjul) *start = sdjul - bdjul; else { printf("<P><b>Error</b>: your biorhythm month starts before your birthday; adjusting it to be the birthday</P>\n"); *start = 0; return bd; } return sd; } char *get_val(entry entries[], int etnum, char *field) /* Terminate if no value has been entered */ { char *val; val = find_value(entries, etnum, field); if (strcmp(val, NOVAL) == 0) { printf("<P>Error: No value found in \"%s\" field</P>\n", field); printf("</body></html>\n"); exit(1); } return val; } Date check_date(Date d) /* Check the date for obvious mistakes */ { if (d.year < 0) { printf("<P><b>Error</b>: year cannot be less than 0; adjusting to 0</P>\n"); d.year = 0; } if (d.year > 9000) { printf("<P><b>Error</b>: year cannot be more than 9000; adjusting to 9000</P>\n"); d.year = 9000; } if (d.month < 1) { printf("<P><b>Error</b>: month cannot be less than 1; adjusting to 1</P>\n"); d.month = 1; } if (d.month > 12) { printf("<P><b>Error</b>: month cannot be more than 12; adjusting to 12</P>\n"); d.month = 12; } if (d.day < 1) { printf("<P><b>Error</b>: day cannot be less than 1; adjusting to 1</P>\n"); d.day = 1; } if ((d.month == 1) || (d.month == 3) || (d.month == 5) || (d.month == 7) || (d.month == 8) || (d.month == 10) || (d.month == 12)) { if (d.day > 31) { printf("<P><b>Error</b>: day cannot be more than 31; adjusting to 31</P>\n"); d.day = 31; } } else if ((d.month == 4) || (d.month == 6) || (d.month == 9) || (d.month == 11)) { if (d.day > 30) { printf("<P><b>Error</b>: day cannot be more than 30; adjusting to 30</P>\n"); d.day = 30; } } else if (d.month == 2) { /* february: don't bother with leap year checking */ if (d.day > 29) { printf("<P><b>Error</b>: day cannot be more than 29; adjusting to 29</P>\n"); d.day = 29; } } return d; } long int toJulian(Date d) /* Calculates the Julian day for the given date. This code is taken from the function MdyToJulian() in Astrolog (v.5.10), available from ftp.magitech.com in the directory /pub/astrology/astrolog. It seems to be based on algorithms in Michael Erlewine's 'Manual of Computer Programming for Astrologers', available from Matrix Software. */ { long im, jul; im = 12*((long)d.year+4800) + (long)d.month - 3; jul = (2*(im%12) + 7 + 365*im)/12; jul += (long)d.day + im/48 - 32083; if (jul > 2299171) /* Take care of dates in */ jul += im/4800 - im/1200 + 38; /* Gregorian calendar. */ return jul; } int get_count() /* The count is extracted from COUNT_NM, and the value in the file is incremented. The use of lockf() means that lower-level UNIX file manipulation (e.g. open(), read()) must be utilised. */ { char ln[MAXLEN]; int fd, cnt, size; if ((fd = open(COUNT_NM, O_RDWR)) == -1) { /* both read and write */ printf("<P><b>Error</b>: Cannot open %s for changing</P>\n", COUNT_NM); printf("</body></html>\n"); exit(1); } lockf(fd, F_LOCK, 0L); /* (wait for) exclusive lock */ size = read(fd, ln, MAXLEN); ln[size] = '\0'; sscanf(ln, "%d\n", &cnt); lseek(fd, 0L, 0); /* rewind to start of file */ sprintf(ln, "%d\n", cnt+1); size = strlen(ln); write(fd, ln, size); lockf(fd, F_ULOCK, 0L); /* unlock */ close(fd); return cnt; } void make_bpic(long int start, Date sd, int count) /* Housekeeping: remove an old biorhythm GIF. Creating a new biorhythm GIF involves making a new version of the gnuplot commands in PLOT_CMDS by changing start and the month/year of interest (sd), to create TEMP_CMDS. TEMP_CMDS is evaluated by gnuplot to create PIC. PIC is in ".ppm" format, and so is converted into a GIF with a unique name by using ppmtogif and count. Since TEMP_CMDS and PIC are not unique, use locking to stop any other invocations of pb from working on them at the same time: an 'exclusion' zone. */ { char cmd[MAXLEN]; int lock_fd; /* Remove old biorhythm picture */ sprintf(cmd, "%s%d.gif", PIC, count-DELNO); remove(cmd); lock_fd = my_lock(); /* start of 'exclusion' zone */ modify_plot(PLOT_CMDS, TEMP_CMDS, start, sd); /* Plot the biorhythm */ sprintf(cmd, "/usr/local/bin/gnuplot %s", TEMP_CMDS); system(cmd); /* Change the biorhythm into GIF form */ sprintf(cmd, "/home/staff/ad/pbm/pbm-mundook/ppmtogif %s.ppm > %s%d.gif", PIC, PIC, count); system(cmd); my_unlock(lock_fd); /* end of 'exclusion' zone */ } void modify_plot(char *plotnm, char *tempnm, long int start, Date d) /* Open the template gnuplot commands file (plotnm) and modify it, saving the new version in tempnm. The changes are to replace '?' by start and '@' by d. */ { FILE *fp, *tfp; int c; if ((fp = fopen(plotnm, "r")) == NULL) { printf("<P><b>Error</b>: Cannot open %s for reading</P>\n", plotnm); printf("</body></html>\n"); exit(1); } else if ((tfp = fopen(tempnm, "w")) == NULL) { printf("<P><b>Error</b>: Cannot open %s for writing</P>\n", tempnm); printf("</body></html>\n"); exit(1); } else { while ((c = fgetc(fp)) != EOF) if (c == '?') /* marker for start digit */ fprintf(tfp, "%ld", start); else if (c == '@') /* marker for start date */ fprintf(tfp, "%d/%d", d.month, d.year); else fputc(c, tfp); fclose(fp); fclose(tfp); } } /* Locking Utilities */ int my_lock(void) { int fd; if ((fd = open(LOCKFNM, O_RDWR)) == -1) printf("<P>Error: Cannot use locking</P>\n"); else lockf(fd, F_LOCK, 0L); return fd; } void my_unlock(int fd) { if (fd != -1) { lockf(fd, F_ULOCK, 0L); close(fd); } } /* Standard Form Functions */ void start_reply(char *title) /* Output the HTML header, including the title and H1 container */ { printf("Content-type: text/html\n\n"); printf("<html><head><title>%s</title></head>\n", title); printf("<body background=\"%s\">", BKGROUND); printf("<H1 align=center>%s</H1>\n", title); fflush(NULL); /* force output to occur now */ } void cgi_errs() /* Check for errors in the REQUEST_METHOD and CONTENT_TYPE environment variables. */ { if(strcmp(getenv("REQUEST_METHOD"),"POST")) { printf("<P>This script should be referenced with a METHOD of POST.\n"); printf("If you don't understand this, read "); printf("<A HREF=\"http://www.ncsa.uiuc.edu/SDG/Software/Mosaic/Docs /fill-out-forms/overview.html\">forms overview</A>.</P>\n"); printf("</body></html>"); exit(1); } if(strcmp(getenv("CONTENT_TYPE"), "application/x-www-form-urlencoded")) { printf("<P>This script can only be used to decode form results.</P>\n"); printf("</body></html>"); exit(1); } } int build_entries(entry entries[]) /* Fill in the entries array with nameString=valueString pairs from the form. */ { int len, x; len = atoi(getenv("CONTENT_LENGTH")); for(x=0; len && (!feof(stdin)) && (x < MAX_ENTRIES); x++) { entries[x].val = fmakeword(stdin,'&',&len); plustospace(entries[x].val); unescape_url(entries[x].val); entries[x].name = makeword(entries[x].val,'='); } return x; } char *find_value(entry entries[], int etnum, char *name) /* Search through the entries array for a name string which matches name and return its associated value string. Otherwise return NOVAL. */ { int x; char *value; for(x=0; x < etnum; x++) if (strcmp(entries[x].name, name) == 0) { value = (char *)malloc(sizeof(char)*(strlen(entries[x].val)+1)); strcpy(value, entries[x].val); break; } if ((x == etnum) || (value[0] == '\0')) { value = (char *)malloc(sizeof(char)*(strlen(NOVAL)+1)); strcpy(value, NOVAL); } return value; } /* Debugging Utilities */ int input_entries(entry entries[]) /* Fill in the entries array with nameString=valueString pairs entered by the user at the keyboard. Each pair should be entered in the format: nameString=valueString Terminate the input with <ctrl>d */ { int num = 0, namelen, linelen; char line[MAXLEN]; printf("\nEnter name=value pairs:\n\n"); while (gets(line) && (num < MAX_ENTRIES)) { linelen = strlen(line); namelen = strcspn(line, "="); entries[num].name = (char *)malloc(sizeof(char)*(namelen+1)); strncpy(entries[num].name, line, namelen); entries[num].name[namelen] = '\0'; entries[num].val = (char *)malloc(sizeof(char)*(linelen-namelen)); strcpy(entries[num].val, &line[namelen+1]); num++; } return num; } void show_entries(entry entries[], int etnum) /* Output the nameString=valueString pairs as an HTML unnumbered list */ { int x; printf("<P>You submitted these nameString=valueString pairs:</P>\n"); printf("<ul>\n"); for(x=0; x < etnum; x++) printf("<li><code>%s = %s</code>\n", entries[x].name, entries[x].val); printf("</ul>\n"); printf("</body></html>\n\n"); } /* CGI Utilities by Rob McCool; comments by Andrew Davison */ char *makeword(char *line, char stop) /* Builds a word by extracting characters from line up to the stopping character in stop (or until line is exhausted). */ { int x = 0, y = 0; char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1)); for(x=0; ((line[x]) && (line[x] != stop)); x++) word[x] = line[x]; word[x] = '\0'; if(line[x]) ++x; while((line[y++] = line[x++])); return word; } char *fmakeword(FILE *f, char stop, int *len) /* Similar to makeword(): builds a word by extracting characters from the file f up to the stopping character in stop (or until f is exhausted). The function is also supplied with the length of the string (len) left unread in f. */ { int wsize = 102400, ll = 0; char *word = (char *) malloc(sizeof(char) * (wsize + 1)); while(1) { word[ll] = (char)fgetc(f); if(ll == wsize) { word[ll+1] = '\0'; wsize += 102400; word = (char *)realloc(word,sizeof(char)*(wsize+1)); } --(*len); if((word[ll] == stop) || (feof(f)) || (!(*len))) { if(word[ll] != stop) ll++; word[ll] = '\0'; return word; } ++ll; } } void unescape_url(char *url) /* Convert the hexadecimal characters in url into ASCII. They are detected by starting with a '%'. */ { register int x, y; 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'; } char x2c(char *what) /* Convert the hexadecimal (consisting of two characters) into a single ASCII character. */ { register char digit; 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 plustospace(char *str) /* Replace '+'s by spaces in str */ { register int x; for(x=0; str[x]; x++) if(str[x] == '+') str[x] = ' '; }