/****************************************************************************** * JH Counter * * programmed by gwangyi * * * * This program is available under the Creative Commons-Attribution- * * Noncommercial-Share Alike 2.0 Korea license or any later. * * * * Homepage: http://blog.gwangyi.kr * ******************************************************************************/ /* * How to compile: * gcc -lgd counter.c -ocounter.cgi * If it requires iconv, you can add -liconv */ #include #include #include // for GD (libgd 2.0.x) #include // for gdFontGetLarge() #include // for gdFontGetTiny() #include #include #include #include #include #include #include #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) > (b) ? (b) : (a)) void error_output(unsigned char *); // Print error message in png image void counter_output(int, int, int); // Print counter image with several options // hit_counter: // [in] param : counter file name // [out] today : today counter // [out] yesterday: yesterday counter // // returns total counter int hit_counter(const char * param, int *today, int *yesterday) { int cntfd; int counter = 0; int today_cnt = 0, yesterday_cnt = 0; int update_yesterday = 0; struct stat s; struct tm today_tm, modified_tm; time_t now = time(NULL); if(!stat(param, &s)) // get file counter file stat { memcpy(&today_tm, gmtime(&now), sizeof(today_tm)); memcpy(&modified_tm, localtime(&s.st_mtime), sizeof(modified_tm)); if(!(today_tm.tm_year == modified_tm.tm_year && today_tm.tm_mon == modified_tm.tm_mon && today_tm.tm_mday == modified_tm.tm_mday)) // if first of today update_yesterday = 1; // update yesterday counter } cntfd = open(param, O_CREAT | O_RDWR, 0666); read(cntfd, &counter, sizeof(int)); read(cntfd, &yesterday_cnt, sizeof(int)); read(cntfd, &today_cnt, sizeof(int)); // get counter values if(update_yesterday) // if update yesterday, { yesterday_cnt = today_cnt; // today counter is actually yesterday counter today_cnt = 0; } counter ++; today_cnt ++; lseek(cntfd, 0, SEEK_SET); // set to first write(cntfd, &counter, sizeof(int)); write(cntfd, &yesterday_cnt, sizeof(int)); write(cntfd, &today_cnt, sizeof(int)); close(cntfd); if(today) *today = today_cnt; if(yesterday) *yesterday = yesterday_cnt; return counter; } int main() { int cnt, t, y; cnt = hit_counter("cnt", &t, &y); counter_output(cnt, t, y); return 0; } void error_output(unsigned char * msg) { gdFontPtr font = gdFontGetLarge(); // error message uses built-in font gdImagePtr im = gdImageCreate(font->w * strlen(msg) + 4, font->h + 4); int black = gdImageColorAllocate(im, 0, 0, 0); int white = gdImageColorAllocate(im, 255, 255, 255); gdImageString(im, font, 2, 2, msg, white); puts("Content-type: image/png\n"); gdImagePng(im, stdout); gdImageDestroy(im); exit(0); } void counter_output(int cnt, int today, int yesterday) { char * query_string = getenv("QUERY_STRING"); char * token = strtok(query_string, "&"); int bgcolor = 0, fgcolor = 0xffffff; int len = 8; int verbose = 0; double pt = 15.0; char * img = NULL; char cnt_str[31] = {0, }; int ptr; gdImagePtr im = NULL; gdFontPtr vfont = (gdFontPtr)gdFontGetTiny(); // verbose font while(token) // Parse query string { // This routine DOES NOT support %00 format encoded characters. char * key = token, * value; value = strchr(token, '='); if(value) { *value = 0; value++; if(!strcmp(key, "bgcolor")) // bgcolor { // background color in RRGGBB int r, g, b; sscanf(value, "%02x%02x%02x", &r, &g, &b); bgcolor = (0xff & r) | ((0xff & g) << 8) | ((0xff & b) << 16); } else if(!strcmp(key, "fgcolor")) // fgcolor { // color of text in RRGGBB int r, g, b; sscanf(value, "%02x%02x%02x", &r, &g, &b); fgcolor = (0xff & r) | ((0xff & g) << 8) | ((0xff & b) << 16); } else if(!strcmp(key, "img")) // img { // image theme. char * probe = value; while(*probe && ((*probe >= 'a' && *probe <= 'z') || (*probe >= 'A' && *probe <= 'Z') || (*probe >= '0' && *probe <= '9') || *probe == '-' || *probe == '_')) probe++; if(!*probe) // img must consist of [a-zA-Z0-9-_] { img = value; } } else if(!strcmp(key, "pt")) // pt { // if counter.ttf exists, pt means size of counter character sscanf(value, "%lf", &pt); if(pt <= 2.0 || pt > 127.0) pt = 15.0; } else if(!strcmp(key, "len")) // len { // length of counter sscanf(value, "%d", &len); if(len <= 0 || len > 30) len = 8; } else if(!strcmp(key, "verbose")) // verbose { // if 1, today and yesterday counter is available. if(strlen(value) == 0 || !strcmp(value, "0")) verbose = 0; else verbose = 1; } } token = strtok(NULL, "&"); } ptr = len; while(ptr > 0) { cnt_str[--ptr] = cnt % 10 + '0'; cnt /= 10; } if(!img) // if image theme is not specified, counter printed in some font. { if(access("./counter.ttf", R_OK)) // counter.ttf is NOT available, { // print counter in built-in large font. gdFontPtr font = (gdFontPtr)gdFontGetLarge(); im = gdImageCreate(font->w * len + 4, font->h + 4 + (vfont->h + 2) * (verbose * 2)); gdImageColorAllocate(im, bgcolor & 0xff, (bgcolor >> 8) & 0xff, (bgcolor >> 16) & 0xff); fgcolor = gdImageColorAllocate(im, fgcolor & 0xff, (fgcolor >> 8) & 0xff, (fgcolor >> 16) & 0xff); gdImageString(im, font, 2, 2 + (2 + vfont->h) * (verbose * 2), cnt_str, fgcolor); } else // if counter.ttf is available, { int geo[8]; char * err = gdImageStringFT(NULL, geo, 0, "./counter.ttf", pt, 0, 0, 0, cnt_str); // get geometric of counter if(err) { error_output(err); } im = gdImageCreate(max(geo[2], geo[4]) - min(geo[0], geo[6]) + 4, max(geo[1], geo[3]) - min(geo[5], geo[7]) + 4 + (vfont->h + 2) * (verbose * 2)); gdImageColorAllocate(im, bgcolor & 0xff, (bgcolor >> 8) & 0xff, (bgcolor >> 16) & 0xff); fgcolor = gdImageColorAllocate(im, fgcolor & 0xff, (fgcolor >> 8) & 0xff, (fgcolor >> 16) & 0xff); gdImageStringFT(im, geo, fgcolor, "./counter.ttf", pt, 0, 2 - geo[6], 2 - geo[7] + (vfont->h + 2) * (verbose * 2), cnt_str); } } else // if image theme is specified, { char path[260] = {0, }; char format[4] = {0, }; char * probe; FILE * fmt; int pos; int width, height; gdImagePtr (*create_fn)(FILE * in); gdImagePtr digits[10] = { NULL, }; // Image theme must consist of gif, png or jpg format of 0-9 files. snprintf(path, 256, "./img/%s/0.", img); pos = strlen(path) - 2; if(strcpy(&path[pos + 2], "jpg") && !access(path, R_OK)) create_fn = gdImageCreateFromJpeg; if(strcpy(&path[pos + 2], "png") && !access(path, R_OK)) create_fn = gdImageCreateFromPng; if(strcpy(&path[pos + 2], "gif") && !access(path, R_OK)) create_fn = gdImageCreateFromGif; else create_fn = NULL; if(create_fn) // if image theme files are supported, { int x; int i; probe = cnt_str; width = 0; height = 0; while(*probe) // calculate width and height { if(!digits[*probe - '0']) // load required image { FILE * fp; path[pos] = *probe; fp = fopen(path, "rb"); if(!fp) error_output("Theme does not exist!"); digits[*probe - '0'] = create_fn(fp); fclose(fp); } width += gdImageSX(digits[*probe - '0']); height = max(height, gdImageSY(digits[*probe - '0'])); probe++; } probe = cnt_str; im = gdImageCreateTrueColor(width, height + (vfont->h + 2) * (verbose * 2)); gdImageFilledRectangle(im, 0, 0, width, height + (vfont->h + 2) * (verbose * 2), gdTrueColor(bgcolor & 0xff, (bgcolor >> 8) & 0xff, (bgcolor >> 16) & 0xff)); // Clear background x = 0; while(*probe) // print each digit { gdImagePtr digit = digits[*probe - '0']; gdImageCopy(im, digit, x, (height - gdImageSY(digit)) / 2 + (vfont->h + 2) * (verbose * 2), 0, 0, gdImageSX(digit), gdImageSY(digit)); x += gdImageSX(digit); probe ++; } for(i = 0; i < 10; i++) if(digits[i]) gdImageDestroy(digits[i]); } else error_output("Theme is not supported!"); fgcolor = gdTrueColor(fgcolor & 0xff, (fgcolor >> 8) & 0xff, (fgcolor >> 16) & 0xff); // If we use image theme, GD image is truecolor and foreground color(verbose text color) is not allocated. so allocate in this time. } if(im) { if(verbose) { // print verbose information char buf[100]; snprintf(buf, 99, "today : %d", today); gdImageString(im, vfont, 2, 2, buf, fgcolor); snprintf(buf, 99, "yesterday: %d", yesterday); gdImageString(im, vfont, 2, 2 + (vfont->h), buf, fgcolor); } puts("Content-type: image/png\n"); gdImagePng(im, stdout); gdImageDestroy(im); } else error_output("Unknown error!"); }