/*
 * make CC=gcc CFLAGS="-g -Wall" LDLIBS=-ldb history-db-list
 *
 * list history database of ~/.netscape/history.db with Berkeley UNIX DB in C
 *
 * Copyright (C) 1999,2000 Taiji Yamada
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <limits.h>
#define min(a,b) (((a)<(b))?(a):(b))
#define max(a,b) (((a)>(b))?(a):(b))
#define swap(a,b) do {\
  struct tmp_t { char t[sizeof(a)]; } tmp;\
  tmp = *(struct tmp_t*)&(a);\
  *(struct tmp_t*)&(a) = *(struct tmp_t*)&(b);\
  *(struct tmp_t*)&(b) = tmp;\
} while(0)
#define chendian32(p) do {\
  swap((p)[0],(p)[3]);\
  swap((p)[1],(p)[2]);\
} while (0)
#define strtr(s, c1, c2) do {\
  char *p = strchr(s, c1);\
  while (p) {\
    *p = c2;\
    p = strchr(p+1, c1);\
  }\
} while (0)

#ifndef NO_DB185
#include "db.h" /* Berkeley UNIX DB library, db-1.85.tar.gz */
#else
#include "db_185.h" /* Berkeley UNIX DB library, db-2.7.7.tar.gz */
#endif

#if !defined(HISTORY_DB)
#define HISTORY_DB_NAME ".netscape/history.dat"
#else
#define HISTORY_DB_NAME ".netscape/"HISTORY_DB
#endif
char fsep = '\t', rsep = '\n';
int chendian_flag = 0;

size_t time_stamp(time_t *time, char *str) {
  struct tm *tm = localtime(time);
  return strftime(str, 20, "%Y/%m/%d %H:%M:%S", tm);
}

#define HISTORY_FPRINT_DEFAULT (0L)
#define HISTORY_FPRINT_LAST (1L<<0)
#define HISTORY_FPRINT_FIRST (1L<<1)
#define HISTORY_FPRINT_COUNT (1L<<2)
#define HISTORY_FPRINT_EXPIRE (1L<<3)
#define HISTORY_FPRINT_NO_TITLE (1L<<4)
#define HISTORY_FPRINT_ALL_ENTRY \
(HISTORY_FPRINT_LAST | \
 HISTORY_FPRINT_FIRST | \
 HISTORY_FPRINT_COUNT | \
 HISTORY_FPRINT_EXPIRE)

int history_fprint(FILE *fp, char *history_db, unsigned long mask) {
  DB *history;
  DBT key, data;
  int eod = 0;
  char *protocols[] = {
    "http:", "ftp:", "file:", "gopher:", NULL,
  };
    
  if (!(history=dbopen(history_db, O_RDONLY, 0, DB_HASH, NULL))) {
    fprintf(stderr, "illegal database file (%s)\n", history_db);
    return 0;
  }
  eod = history->seq(history, &key, &data, R_FIRST);
  while (!eod) {
    char *url, *title;
    unsigned char *buf;
    union { unsigned long i; unsigned char s[4]; } l, f, c, e;
    char last[20], first[20], expire[20];
    unsigned long count;

    if (!key.size) goto CONTINUE;
    url = (char*)key.data;
    {
      int i;
      for (i=0; protocols[i]!=NULL; i++)
        if (strncasecmp(url,protocols[i],strlen(protocols[i])) == 0) break;
      if (protocols[i]==NULL) goto CONTINUE;
      for (i=0; url[i]!='\0'; i++)
	if (url[i]<' ' || '~'<url[i]) goto CONTINUE;
    }
    if (!data.size) goto CONTINUE;
    buf = (unsigned char *)data.data;
    title = NULL;
    if (data.size > 16 && strlen(&buf[16]) > 0) {
      if (min(data.size-16,strlen(&buf[16]))+1 > 4096)
	goto CONTINUE;
      title = malloc((min(data.size-16,strlen(&buf[16]))+1)*sizeof(char));
      strcpy(title, &buf[16]);
      strtr(title, fsep, ' ');
      strtr(title, rsep, ' ');
    }
    if (!(mask&HISTORY_FPRINT_NO_TITLE) && title == NULL)
      goto CONTINUE;
    memcpy(l.s,&buf[0],4);
    memcpy(f.s,&buf[4],4);
    memcpy(c.s,&buf[8],4);
    memcpy(e.s,&buf[12],4);
    if (chendian_flag) {
      chendian32(l.s);
      chendian32(f.s);
      chendian32(c.s);
      chendian32(e.s);
    }
    time_stamp((time_t *)&l.i, last);
    time_stamp((time_t *)&f.i, first);
    time_stamp((time_t *)&e.i, expire);
    count = c.i;
    fprintf(fp, "%s%c%s", url, fsep, title?title:"");
    if (mask&HISTORY_FPRINT_LAST) fprintf(fp, "%c%s", fsep, last);
    if (mask&HISTORY_FPRINT_FIRST) fprintf(fp, "%c%s", fsep, first);
    if (mask&HISTORY_FPRINT_EXPIRE) fprintf(fp, "%c%s", fsep, expire);
    if (mask&HISTORY_FPRINT_COUNT) fprintf(fp, "%c%lu", fsep, count);
    fprintf(fp, "%c", rsep);
    free(title);
  CONTINUE:
    eod = history->seq(history, &key, &data, R_NEXT);
  }
  history->close(history);

  return !0;
}

int main(int argc, char *argv[])
{
  int i;
  char history_db[256];
  unsigned long mask = HISTORY_FPRINT_DEFAULT;
  int status;

  sprintf(history_db, "%s/%s", getenv("HOME"), HISTORY_DB_NAME);
  for (i=1; i<argc; i++)
    if (strcmp(argv[i],"-l")==0)
      mask |= HISTORY_FPRINT_LAST;
    else if (strcmp(argv[i],"-f")==0)
      mask |= HISTORY_FPRINT_FIRST;
    else if (strcmp(argv[i],"-c")==0)
      mask |= HISTORY_FPRINT_COUNT;
    else if (strcmp(argv[i],"-e")==0)
      mask |= HISTORY_FPRINT_EXPIRE;
    else if (strcmp(argv[i],"-a")==0)
      mask |= HISTORY_FPRINT_ALL_ENTRY;
    else if (strcmp(argv[i],"-T")==0)
      mask |= HISTORY_FPRINT_NO_TITLE;
    else if (strcmp(argv[i],"-C")==0)
      chendian_flag = !0;
    else if (strcmp(argv[i],"-h")==0) {
      printf("usage: %s [-l|-f|-c|-e|-a] [-T] [-C] [history.db|history.dat]\n", argv[0]);
      printf("\t-l: print last visit date\n");
      printf("\t-f: print first visit date\n");
      printf("\t-c: print visit count\n");
      printf("\t-e: print expire date\n");
      printf("\t-a: print all attributes\n");
      printf("\t-T: print all untitled URLs\n");
      printf("\t-C: change endian type of database file\n");
      exit(0);
    } else
      strcpy(history_db, argv[i]);
#if defined(sparc)
  if (strcmp(strrchr(history_db,'.'),".dat")==0)
    chendian_flag = !0;
#endif
  status = history_fprint(stdout, history_db, mask);
  return (status?0:-1);
}
