/*

Kukkaisvoima a lightweight weblog system.  

Copyright (C) 2006-2008 Petteri Klemola

Kukkaisvoima is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as
published by the Free Software Foundation.

Kukkaisvoima is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.

This is special version of Kukkaisvoima made with C programming
language. For the normal version written with Python see
http://23.fi/kukkaisvoima .

*/

#define _GNU_SOURCE 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <time.h>

/* Config variables */
/* Url of the blog (without trailing /) */
#define BASEURL "http://23.fi/kukkaisvoima"
#define BLOGNAME "Kukkaisvoima"
#define SLOGAN "Default installation"
#define DESCRIPTION "Jee"
#define ENCODING "iso-8859-15"
/* Use absolute url for this */
#define STYLESHEET "http://23.fi/css/23.css"
#define DEFAULTAUTHOR "You"
#define FAVICON "http://23.fi/favicon.ico"
#define DOCTYPE "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
/* Email to send comments to */
#define BLOGEMAIL "you@yourdomain"
/* Language for the feed */
#define LANGUAGE "en"
/* Number of entries per page */
#define NUMBEROFENTRIESPERPAGE 10
/* Directory which contains the blog entries (without the trailing
   /) */
#define DATADIR "."
/* Directory which contains the index and comments. Must be script
   writable directory */
#define INDEXDIR "temp"
/* Maximum comments per entry. Use -1 for no comments and 0 for no restriction */
#define MAXCOMMENTS 30
/* answer to spamquestion (question variable is l_nospam_question)*/
#define NOSPAMANSWER "5"
/* This is admin password to manage comments. password should be
   something other than "password" */
#define PASSWD "password"
/* Kukkaisvoima version */
#define VERSION "suvi 67"
/* Time stamp for feed */
#define RFC822TIME "%a, %d %b %Y %H:%M:%S +0200"


/* Language variables */
#define L_ARCHIVES "Archives"
#define L_CATEGORIES "Categories"
#define L_COMMENTS "Comments"
#define L_COMMENTS2 "Comments"
#define L_DATE "Date"
#define L_NEXTPAGE "Next page"
#define L_PREVIOUSPAGE "Previous page"
#define L_LEAVE_REPLY "Leave reply"
#define L_NO_COMMENTS_ALLOWED "No comments allowed"
#define L_NO_COMMENTS "No comments"
#define L_NAME_NEEDED "Name (needed)"
#define L_EMAIL_NEEDED "Email (needed)"
#define L_WEBPAGE "Webpage"
#define L_NO_HTML "No html allowed in reply"
#define L_NOSPAM_QUESTION "What\"s 2 + 3?"
#define L_DELETE_COMMENT "Delete comment"
#define L_PASSWD "Admin password"
#define L_ADMIN "Admin"
#define L_ADMIN_COMMENTS "Manage comments"
#define L_DO_YOU_DELETE "Your about to delete comment this, are you sure you want to that?"

/* For one blog entry*/
struct entry {
  struct stat *buf;
  struct tm date;
  char *filename;
  char **categories;
  char month[8]; /* format YEAR-MM\0 */
  int number_of_categories;
};

/* For the entries in the sidebar*/
struct side_dict {
  char *name;
  int number;
  struct entry **ent;
};

/* For the entries in the sidebar */
struct sidebar {
  struct side_dict **categories;
  int number_of_categories;
  struct side_dict **months;
  int number_of_months;
};

/* Compare function for entries. Compares entries according to their
   time stamp */
int compare_entries(const void *p1, const void *p2) {
  struct entry * const *e1 = p1;
  struct entry * const *e2 = p2;
  return (int) difftime(mktime(&((*e2)->date)), mktime(&((*e1)->date)));
}

/* Compare function for categories. Just simple alphabetical order */
int compare_categories(const void *p1, const void *p2) {
  struct side_dict * const *e1 = p1;
  struct side_dict * const *e2 = p2;
  return strcmp((*e1)->name, (*e2)->name);
}

/* Compare function for months. Just simple alphabetical order ;) */
int compare_months(const void *p1, const void *p2) {
  struct side_dict * const *e1 = p1;
  struct side_dict * const *e2 = p2;
  return strcmp((*e2)->name, (*e1)->name);
}

/* Helper function to stat with full path */
int kukka_stat(const char *path, struct stat *buf) {
  char *filename;
  int result;
  asprintf(&filename, "%s/%s", DATADIR, path);
  result = stat(filename, buf);
  free(filename);
  return result;
}

/* Helper function to fopen with full path */
FILE *kukka_fopen(const char *path, const char *mode) {
  char *filename;
  FILE *result;
  asprintf(&filename, "%s/%s", DATADIR, path);
  result = fopen(filename, mode);
  free(filename);
  return result;
}

/* Helper function to remove .txt from blog entry filenames */
char *remove_file_suffix(char *filename) {
  char *tmp, *fname;
  asprintf(&tmp, "%%0.%ds", strlen(filename) -4);
  asprintf(&fname, tmp, filename);
  free(tmp);
  return fname;
}

/* Helper function to see that filename is in correct form for blog
   entry */
int is_entry(char *filename) {
  int len = strlen(filename);
  char *cp = strdup(filename);
  int i;
  /* Check that it is in format of name:date:category1,category3.txt */
  strtok(cp, ":");
  for (i = 0; strtok(NULL, ":"); i++) ;
  if (len > 3 && strstr(&filename[len-4], ".txt") && i == 2) {
    return 1;
  }
  return 0;
}

/* Helper function to see if filename has category */
int has_category(char *filename, char *category) {
  char *cp, *token;

  /* name:date:category1,category3.txt */
  cp = strdup(filename);
  /* name */
  strtok(cp, ":");
  /* date */
  token = strtok(NULL, ":");
  /* categories*/
  token = strtok(NULL, ":");
  if (strstr(token, category)) {
    return 1;
  }
  return 0;
}

/* Make entry struct */
struct entry *make_entry(struct stat *buf, char *filename) {
  struct entry *e = malloc(sizeof(struct entry));
  char *token, *token2, *cp, *datestr;
  struct tm *ftime;
  int x = 0;

  e->buf = buf;
  e->filename = filename;
  e->categories = NULL;
  e->number_of_categories = 0;
  /* Get gategories and date */
  /* name:date:category1,category3.txt */
  cp = strdup(filename);
  /* name */
  strtok(cp, ":");
  /* date */
  ftime = localtime(&(buf->st_mtime));
  asprintf(&datestr, "%s %d:%d:%d", 
	   strtok(NULL, ":"), ftime->tm_hour, ftime->tm_min, ftime->tm_sec);
  strptime(datestr, "%Y-%m-%d %H:%M:%S", &(e->date));
  token = strtok(NULL, ":");
  token = strndup(token, strlen(token) - 4);

  e->categories = realloc(e->categories, (sizeof(char*) * ((x) + 1)));
  /* Get categories */
  e->categories[x] = strtok(token, ",");
  x++;
  for (; (token2 = strtok(NULL, ",")); x++) {
    e->categories = realloc(e->categories, (sizeof(char*) * ((x) + 1)));
    e->categories[x] = token2;
  }
  e->number_of_categories = x;

  strftime(e->month, 8, "%Y-%m", &(e->date));
  
  return e;
}

/* Wrapper around make_entry to check that filename exists and entry
   is in correct form. */
struct entry *read_entry(char *filename) {
  struct stat buf;

  if (!is_entry(filename) || kukka_stat(filename, &buf) < 0) {
    return NULL;
  }
  return make_entry(&buf, filename);
}

/* Wrapper around make_entry to read whole directory of entries and
   return array of those. */
struct entry **read_entries(int *x) {
  DIR *dir = opendir(DATADIR);

  struct entry **entries = NULL;
  struct dirent *d;

  *x = 0;
  while ((d = readdir(dir))) {
    struct stat buf;
    if (kukka_stat(d->d_name, &buf) < 0) {
      perror("Stat failed");
      return NULL;
    }
    else {
      if(!S_ISDIR(buf.st_mode) && is_entry(d->d_name)) {
	entries = (struct entry**) realloc(entries, (sizeof(struct entry*) * ((*x)+1)));
	entries[*x] = make_entry(&buf, d->d_name);
	(*x)++;
      }
    }
  }
  return entries;
}

/* Make sidebar struct */
struct sidebar *make_sidebar(struct entry **entries, int number_of_entries) {
  int i, ii, iii;
  struct sidebar *sb = malloc(sizeof(struct sidebar*) +
			      (sizeof(int) * 2) + 
			      (sizeof(struct side_dict)*2));
  struct entry *e = NULL;

  sb->number_of_categories = 0;
  sb->number_of_months = 0;
  sb->categories = NULL;
  sb->months = NULL;

  for (i = 0; i < number_of_entries; i++) {
    e = entries[i];
    /* Get the categories */
    for (ii = 0; ii < e->number_of_categories; ii++) {
      if (sb->categories == NULL) {
	sb->categories = 
	  realloc(sb->categories, 
		  (sizeof(struct side_dict*) * (sb->number_of_categories+1)));
	sb->categories[sb->number_of_categories] = 
	  malloc(sizeof (struct side_dict));
	sb->categories[sb->number_of_categories]->name = e->categories[ii];
	sb->categories[sb->number_of_categories]->number = 1;
	sb->categories[sb->number_of_categories]->ent = NULL;
	sb->categories[sb->number_of_categories]->ent = 
	  realloc(sb->categories[sb->number_of_categories]->ent, 
		  (sizeof(struct entry*) * 
		   (int) sb->categories[sb->number_of_categories]->number));
	sb->categories[sb->number_of_categories]->ent[0] = e;
	sb->number_of_categories++;
      }
      else {
	for (iii = 0; iii < sb->number_of_categories; iii++) {
	  if (strcasecmp(sb->categories[iii]->name, e->categories[ii]) == 0) {
	    sb->categories[iii]->number++;
	    sb->categories[iii]->ent = 
	      realloc(sb->categories[iii]->ent, 
		      (sizeof(struct entry*) * 
		       (int) sb->categories[iii]->number));
	    sb->categories[iii]->ent[(int) sb->categories[iii]->number-1] = e;
	    break; /* found */
	  }
	}
	/* not found */
	if (iii == sb->number_of_categories) {
	  sb->categories = 
	    realloc(sb->categories, 
		    (sizeof(struct side_dict*) * (sb->number_of_categories+1)));
	  sb->categories[sb->number_of_categories] = 
	    malloc(sizeof (struct side_dict));
	  sb->categories[sb->number_of_categories]->name = e->categories[ii];
	  sb->categories[sb->number_of_categories]->number = 1;
	  sb->categories[sb->number_of_categories]->ent = NULL;
	  sb->categories[sb->number_of_categories]->ent = 
	    realloc(sb->categories[sb->number_of_categories]->ent, 
		    (sizeof(struct entry*) * 
		     (int) sb->categories[sb->number_of_categories]->number));
	  sb->categories[sb->number_of_categories]->ent[0] = e;
	  sb->number_of_categories++;
	}
      }
    }
    /* Get the months */
    if (sb->months == NULL) {
      sb->months = (struct side_dict**) 
	realloc(sb->months, 
		(sizeof(struct side_dict*) * (sb->number_of_months+1)));
      sb->months = 
	realloc(sb->months, 
		(sizeof(struct side_dict*) * (sb->number_of_months+1)));
      sb->months[sb->number_of_months] = 
	malloc(sizeof (struct side_dict));
      sb->months[sb->number_of_months]->name = e->month;
      sb->months[sb->number_of_months]->number = 1;
      sb->months[sb->number_of_months]->ent = NULL;
      sb->months[sb->number_of_months]->ent = 
	realloc(sb->months[sb->number_of_months]->ent, 
		(sizeof(struct entry*) * 
		 (int) sb->months[sb->number_of_months]->number));
      sb->months[sb->number_of_months]->ent[0] = e;
      sb->number_of_months++;
    }
    else {
      for (iii = 0; iii < sb->number_of_months; iii++) {
	if (strcasecmp(sb->months[iii]->name, e->month) == 0) {
	  sb->months[iii]->number++;
	  sb->months[iii]->ent = 
	    realloc(sb->months[iii]->ent, 
		    (sizeof(struct entry*) * 
		     (int) sb->months[iii]->number));
	  sb->months[iii]->ent[(int) sb->months[iii]->number-1] = e;
	  break; /* found */
	}
      }
      /* not found */
      if (iii == sb->number_of_months) {
	sb->months = 
	  realloc(sb->months, 
		  (sizeof(struct side_dict*) * (sb->number_of_months+1)));
	sb->months[sb->number_of_months] = 
	  malloc(sizeof (struct side_dict));
	sb->months[sb->number_of_months]->name = e->month;
	sb->months[sb->number_of_months]->number = 1;
	sb->months[sb->number_of_months]->ent = NULL;
	sb->months[sb->number_of_months]->ent = 
	  realloc(sb->months[sb->number_of_months]->ent, 
		  (sizeof(struct entry*) * 
		   (int) sb->months[sb->number_of_months]->number));
	sb->months[sb->number_of_months]->ent[0] = e;
	sb->number_of_months++;
      }
    }
  }

  /* sort sidebar */
  qsort(sb->categories, 
	sb->number_of_categories, 
	sizeof(char *), 
	compare_categories);
  qsort(sb->months, 
	sb->number_of_months, 
	sizeof(char *), 
	compare_months);
  return sb;
}

/* Print feed header */
void print_feed_header(struct entry *ent) {
  char buf[255];//, *tmp;
  strftime(buf, sizeof(buf), RFC822TIME, &(ent->date));

  printf("Content-Type: text/xml; charset=%s\n\n", ENCODING);
  printf("<?xml version=\"1.0\" encoding=\"%s\"?>\n", ENCODING);
  printf("<!-- generator=\"Kukkaisvoima version %s\" -->\n", VERSION);
  printf("<rss version=\"2.0\"\n");
  printf("xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"\n");
  printf("xmlns:wfw=\"http://wellformedweb.org/CommentAPI/\"\n");
  printf("xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n");
  printf("<channel>\n");
  printf("<title>23sen uutiset</title>\n");
  printf("<link>http://23.fi/blogi</link>\n");
  printf("<description>Uutisia</description>\n");
  printf("<pubDate>%s</pubDate>\n", buf);
  printf("<lastBuildDate>%s</lastBuildDate>\n", buf);
  printf("<generator>%s</generator>\n", BASEURL);
  printf("<language>%s</language>\n", LANGUAGE);
}

/* Print feed footer */
void print_feed_footer(void) {
  printf("</channel>\n</rss>\n");
}

/* Print entry in feed format */
void print_feed_entry(struct entry *ent) {
  FILE *f = kukka_fopen(ent->filename, "r");
  ssize_t read;
  size_t len = 0;
  char *line = NULL;
  char *url = remove_file_suffix(ent->filename);
  int firstline = 1, i;
  char buf[255];//, *tmp;
  strftime(buf, sizeof(buf), RFC822TIME, &(ent->date));

  if (f) {
    printf("<item>\n");
    while ((read = getline(&line, &len, f) != -1)) {
      if (firstline) {
	printf("<title>%s</title>\n", line);
	printf("<link>%s/%s</link>\n", BASEURL, url);
	printf("<pubDate>%s</pubDate>\n", buf);
        printf("<dc:creator>%s</dc:creator>\n", DEFAULTAUTHOR);
	for (i = 0; i < ent->number_of_categories; i++) {
	  printf("<category>%s</category>\n", ent->categories[i]);
	}
        printf("<guid isPermaLink=\"false\">%s/%s/</guid>\n", BASEURL, url);
        printf("<description><![CDATA[ [...]]]></description>\n");
        printf("<content:encoded><![CDATA[");
	  firstline = 0;
      }
      else {
	printf("%s", line);
      }
      free(line);
      line = NULL;
    }
    free(line);
    printf("]]></content:encoded>\n");
    printf("</item>\n");
  }
}

/* Print html header for all webpages */
void print_html_header(struct sidebar *sb) {
  int i;

  printf("Content-Type: text/html\n\n");
  printf("%s", DOCTYPE);
  printf("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"%s\" lang=\"%s\">", 
	 LANGUAGE, LANGUAGE);
  printf("<head>");
  printf("<title>%s - %s</title>", BLOGNAME, SLOGAN);
  printf("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />", 
	 ENCODING);
  printf("<link rel=\"stylesheet\" href=\"%s\" type=\"text/css\" />", 
	 STYLESHEET);
  printf("<link rel=\"shortcut icon\" href=\"%s\"/>", FAVICON);
  printf("<link rel=\"alternate\" type=\"application/rss+xml\" title=\"%s RSS Feed\" href=\"%s/feed/\" />", 
	 BLOGNAME, BASEURL);
  for (i = 0; i < sb->number_of_categories; i++) {
    printf("<link rel=\"alternate\" type=\"application/rss+xml\" title=\"%s: %s RSS Feed\" href=\"%s/%s/feed/\" />", 
	   BLOGNAME, sb->categories[i]->name, BASEURL, sb->categories[i]->name);
  }
  printf("</head>");
  printf("<body>");
  printf("<div id=\"content1\">");
  printf("<div id=\"header\">");
  printf("<h1><a href=\"%s\">%s</a></h1>", BASEURL, BLOGNAME);
  printf("<div id=\"slogan\">%s</div>", SLOGAN);
  printf("</div>");
  printf("<div id=\"content2\">");
}

/* Print html footer for all webpages */
void print_html_footer(void) {
  printf("<div id=\"footer\">Powered by <a href=\"http://23.fi/kukkaisvoima\">Kukkaisvoima</a> version %s </div>", 
	 VERSION);
  printf("</div>");
  printf("</body>");
  printf("</html>");
}

/* Print entry in html */
void print_html_entry(struct entry *ent) {
  FILE *f = kukka_fopen(ent->filename, "r");
  ssize_t read;
  size_t len = 0;
  char *line = NULL;
  char buf[255];//, *tmp;
  char *url = remove_file_suffix(ent->filename);
  int firstline = 1, i;

  if (f) {

    printf("<div class=\"post\">");
    /* Read the first line and headers to it */
    while ((read = getline(&line, &len, f) != -1)) {
      if (firstline) {
	printf("<h2><a href=\"%s/%s\">%s</a></h2>\n", BASEURL, url, line);
	firstline = 0;
      }
      else {
	printf("%s", line);
      }
      free(line);
      line = NULL;
    }
    free(line);

    printf("</div>");
    /* Not implemented yet */
    /* printf("<div class=\"comlink\"><a href=\"%s/%s#leave_acomment\">%s</a></div>", */
    /* 	   BASEURL, url, L_NO_COMMENTS); */
    /* Not needed anymore */
    free(url);
    printf("<div class=\"categories\">%s: ", L_CATEGORIES);
    for (i = 0; i < ent->number_of_categories; i++) {
      if (i > 0) {
	printf(", <a href=\"%s/%s\">%s</a>", 
	       BASEURL, ent->categories[i], ent->categories[i]);
      }
      else {
	printf("<a href=\"%s/%s\">%s</a>", 
	       BASEURL, ent->categories[i], ent->categories[i]);
      }
    }
    printf("</div>");
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &(ent->date));
    printf("<div class=\"date\">%s: %s</div>",
	   L_DATE, buf);
    fclose(f);    
  }

}

/* Print sidebar */
void print_html_sidebar(struct sidebar *sb) {
  int i;

  printf("</div><div id=\"sidebar\">");
  printf("<h2>%s</h2>", L_CATEGORIES);
  printf("<ul>\n");
  for(i = 0; i < sb->number_of_categories; i++) {
    printf("<li><a href=\"%s/%s\">%s</a> (%d)</li>\n", 
	   BASEURL,
	   sb->categories[i]->name, 
	   sb->categories[i]->name,
	   sb->categories[i]->number);
  }
  printf("</ul>\n");
  printf("<h2>%s</h2>", L_ARCHIVES);
  printf("<ul>\n");
  for(i = 0; i < sb->number_of_months; i++) {
    printf("<li><a href=\"%s/%s\">%s</a> (%d)</li>\n", 
	   BASEURL,
	   sb->months[i]->name, 
	   sb->months[i]->name,
	   sb->months[i]->number);
  }
  printf("</ul>\n");
  printf("</div>");
}

 
int main(void) {
  char *path = getenv("PATH_INFO");
  char *query= getenv("QUERY_STRING");
  char **patharray = NULL;
  char **queryarray = NULL;
  char *filename;
  char *category = NULL;
  int number_of_entries = 0, i = 0, sizeofpath = 0, sizeofquery = 0, feed=0;
  char *token;
  int page = 0;
  int page_entries = 0;
  struct entry **entries = read_entries(&number_of_entries);
  struct sidebar *sb = make_sidebar(entries, number_of_entries);
  struct entry **entries_to_print = entries;
  struct entry *lonely_entry = NULL;

  /* GET information from path */
  if (path) {
    token = strtok(path, "/");
    if (token) {
      do {
	patharray = realloc( patharray, sizeof(char *) * (sizeofpath + 1));
	patharray[sizeofpath] = strdup(token);
	sizeofpath++;
      } while ((token = strtok(NULL, "/")));
    }
  }

  /* GET information from query */
  if (query) {
    token = strtok(query, "=");
    if (token) {
      do {
	queryarray = realloc( queryarray, sizeof(char *) * (sizeofquery + 1));
	queryarray[sizeofquery] = strdup(token);
	sizeofquery++;
      } while ((token = strtok(NULL, "=")));

      if (sizeofquery > 1 && strcasecmp(queryarray[0], "page") == 0) {
	if (strlen(queryarray[1]) < 3 && sscanf(queryarray[1], "%d", &page) == 1) {
	  if (page > 0) {
	    page_entries = NUMBEROFENTRIESPERPAGE * page;
	  }
	}
      }
      
    }
  }

  
  /* Check if this is feed */
  if (sizeofpath > 0 && strcasecmp(patharray[sizeofpath-1], "feed") == 0) {
    feed = 1;
  }
  /* Entry, category or month*/
  if (sizeofpath > 0 && sizeofpath < 3) {
    /* Look for category */
    for (i = 0; i < sb->number_of_categories; i++) {
      if (strcasecmp(patharray[0], sb->categories[i]->name) == 0) {
	category = sb->categories[i]->name;
	entries_to_print = sb->categories[i]->ent;
	number_of_entries = sb->categories[i]->number;
	goto out;
      }
    }
    /* No category found look for months */
    for (i = 0; i < sb->number_of_months; i++) {
      if (strcasecmp(patharray[0], sb->months[i]->name) == 0) {
	entries_to_print = sb->months[i]->ent;
	number_of_entries = sb->months[i]->number;
	goto out;
      }
    }
    /* No months or categories found look one entry */
    asprintf(&filename, "%s.txt", patharray[0]);
    if ((lonely_entry = read_entry(filename))) {
      number_of_entries = 1;
      entries_to_print[0] = lonely_entry;
    }
    /* Else just show the normal index*/
  }

 out:

  /* Sort entries according to date */
  qsort(entries_to_print, 
	number_of_entries, 
	sizeof(struct entry *), 
	compare_entries);

  /* Print entries */
  if (feed) {
    print_feed_header(entries_to_print[i]);
    for (i = page_entries; i < number_of_entries && 
	   i < (NUMBEROFENTRIESPERPAGE + page_entries); i++) {
      print_feed_entry(entries_to_print[i]);    
    }
    print_feed_footer();
  }
  else {
    print_html_header(sb);
    for (i = page_entries; i < number_of_entries && 
	   i < (NUMBEROFENTRIESPERPAGE + page_entries); i++) {
      print_html_entry(entries_to_print[i]);
    }
    /* Next and previous page links. Print links only if we have more
       than one entry to print */
    if (i > (page_entries + 1) &&
	number_of_entries > (page_entries + NUMBEROFENTRIESPERPAGE)) {
      printf("<div class=\"navi\">");
      if (page > 0) {
	if (sizeofpath == 1) {
	  printf("<a href=\"%s/%s?page=%d\">%s</a> ", 
		 BASEURL, patharray[0], page - 1, L_PREVIOUSPAGE);
	}
	else {
	  printf("<a href=\"%s?page=%d\">%s</a> ", 
		 BASEURL, page - 1,  L_PREVIOUSPAGE);
	}
      }
      if (sizeofpath == 1) {
	printf("<a href=\"%s/%s?page=%d\">%s</a>", 
	       BASEURL, patharray[0], page + 1, L_NEXTPAGE);
      }
      else {
	printf("<a href=\"%s?page=%d\">%s</a>", BASEURL, page + 1, L_NEXTPAGE);
      }
      printf("</div>");
    }
    print_html_sidebar(sb);
    print_html_footer();
  }
  return 0;
}

