/* =====================================================================

   LiMo - Liblary
      Rero2's Resistrict Data Archive System.
                   - レジスト制限付きデータアーカイブシステム

                                  Programed by Rero2 (K.Kunikane)

====================================================================== */

/* -------------------------------------------------------------
 --- History

  Feb.22.2000  設計・製作開始
  Feb.28.2000  ひとまず完成

-------------------------------------------------------------- */

/* -------------------------------------------------------------
 --- Preface

  lm_rrda_open        -- アーカイブファイルのオープン＆初期化
  lm_rrda_close       -- アーカイブファイルのクローズ
  lm_rrda_check_open  -- ファイルがオープンされているか？

  lm_rrda_check_key   -- 正しいキーが設定されているか

  lm_rrda_check_data  -- データが存在しているか
  lm_rrda_data_size   -- データのサイズを返す
  lm_rrda_load_data   -- データの読み込み


-------------------------------------------------------------- */

/* -------------------------------------------------------------
 --- Include
-------------------------------------------------------------- */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include  <stdio.h>
#include  <string.h>
#include  <zlib.h>
#include  "lm_rrda.h"

/* --- 標準エラー出力するかどうか */
#define VERBOSE

/* --- 解凍時の出力チャンク単位 */
#define ZLIB_BUFSIZE  4096

/* --- 解凍時の1ファイルのサイズ制限(安全策) */
#define Z_OUTMAX 1024*1024*5

/* --- gzip ヘッダー部 flag byte */
#define ASCII_FLAG   0x01
#define HEAD_CRC     0x02
#define EXTRA_FIELD  0x04
#define ORIG_NAME    0x08
#define COMMENT      0x10
#define RESERVED     0xe0

/* -------------------------------------------------------------
 --- 各種設定事項
-------------------------------------------------------------- */


/* -------------------------------------------------------------
 --- Structure
-------------------------------------------------------------- */

typedef struct _rrda_file RRDAFile;
struct _rrda_file
{
  unsigned long  top;
  unsigned long  size;
  int            resist_flag;
};


/* -------------------------------------------------------------
 --- ソース内関数宣言
-------------------------------------------------------------- */

/* --- ファイルの中からディレクトリデータを取り出す */
void get_dir(void);

/* --- gz データのヘッダーチェックとスキップ */
unsigned char *gz_header_check(unsigned char*, long);

/* --- メモリ上の gz データを解凍し、ポインタを返す */
unsigned char *gunzip_data(unsigned char*, long, long*);

/* --- メモリ上から一行を抽出し進めたポインタを返す */
unsigned char *mem_gets(unsigned char*, int, unsigned char*);

/* --- 指定ファイルをディレクトリから探す */
void file_search(unsigned char*, RRDAFile*);

/* --- 指定ファイルをロードする */
unsigned char *get_file(unsigned char*, long*);


/* -------------------------------------------------------------
 --- ソース内変数宣言
-------------------------------------------------------------- */

/* --- アーカイブファイル名 */
static unsigned char *RRDAFilename;

/* --- キーコード */
static unsigned char RRDAKey[4];

/* --- キーコードが正しいかどうかのフラグ */
static int RRDAKeyFlag = FALSE;

/* --- Directory データ */
static unsigned char *RRDADir_f = NULL;
static unsigned char *RRDADir_r = NULL;

/* --- Resistrict エリアのアクセス権フラグ */
static int RRDAResistrictFlag;

/* -----------------------------------------------------------------------
 --- インプリメント
------------------------------------------------------------------------ */

/* -------------------------------------------------------------
  lm_rrda_open        -- アーカイブファイルのオープン＆初期化

   filename - アーカイブファイルのファイル名
   key      - ４文字のキーストリング
   return : TRUE=オープン完了, FALSE=オープン不可
-------------------------------------------------------------- */
int  lm_rrda_open(char *filename, char *key)
{
  int  i, f, l;

  /* --- データが残っていると悪いので強制開放 */
  lm_rrda_close();

  /* --- 引数エラー */
  if (filename == NULL) return(FALSE);

  /* --- キーのコピー */
  f = 1;
  /* - キーの値がおかしくないことを確認 */
  if (key != NULL) {
    for(i=0; i<4; i++) {
      if (key[i] == 0) {
	f = 0;
	break;
      }
    }
    if (key[4] != 0) {
      f = 0;
    }
    if (f == 0) {
      return(FALSE);
    }
    for(i=0; i<4; i++) {
      RRDAKey[i] = key[i];
    }
  }

  /* --- ファイルネームのコピー */
  l = (int)strlen(filename);
  RRDAFilename = (char *)malloc((size_t)l + 1);
  if (RRDAFilename == NULL) return(FALSE);
  strcpy(RRDAFilename, filename);

  /* --- Directory データの取得 */
  get_dir();
}


/* -------------------------------------------------------------
  lm_rrda_close       -- アーカイブファイルのクローズ

   return : void 
-------------------------------------------------------------- */
void  lm_rrda_close(void)
{
  /* --- キーのクリア */
  RRDAKey[0] = RRDAKey[1] = RRDAKey[2] = RRDAKey[3] = 0;
  RRDAKeyFlag = FALSE;

  /* --- 保持ファイル名の開放 */
  if (RRDAFilename != NULL) {
    free(RRDAFilename);
    RRDAFilename = NULL;
  }

  /* --- DIRデータの開放 */
  if (RRDADir_f != NULL) {
    free(RRDADir_f);
    RRDADir_f = NULL;
  }
  if (RRDADir_r != NULL) {
    free(RRDADir_r);
    RRDADir_r = NULL;
  }
}


/* -------------------------------------------------------------
  lm_rrda_check_open  -- ファイルがオープンされているか？

   return : 
-------------------------------------------------------------- */
int  lm_rrda_check_open(void)
{
  if ((RRDAFilename == NULL) || (RRDADir_f == NULL)) {
    return(FALSE);
  }
  return(TRUE);
}


/* -------------------------------------------------------------
  lm_rrda_check_key   -- 正しいキーが設定されているか

   return : 
-------------------------------------------------------------- */
int  lm_rrda_check_key(void)
{
  return(RRDAKeyFlag);
}


/* -------------------------------------------------------------
  lm_rrda_check_data  -- データが存在しているか

   return : 
-------------------------------------------------------------- */
int  lm_rrda_check_data(char *dataname)
{
  RRDAFile file;

  file_search(dataname, &file);
  if ((file.top == 0) || (file.size == 0)) {
    return(FALSE);
  }
  return(TRUE);
}


/* -------------------------------------------------------------
  lm_rrda_data_size   -- データのサイズを返す

   return : 
-------------------------------------------------------------- */
unsigned long  lm_rrda_data_size(char *dataname)
{
  unsigned char *t;
  long  s;

  t = get_file(dataname, &s);
  if (t == NULL) {
    s = 0;
  }
  else {
    free(t);
  }
  
  return(s);
}


/* -------------------------------------------------------------
  lm_rrda_load_data   -- データの読み込み

   return : 
-------------------------------------------------------------- */
void  *lm_rrda_load_data(char *dataname)
{
  return(get_file(dataname, NULL));
}



/* -----------------------------------------------------------------------
 --- ローカル関数
------------------------------------------------------------------------ */

/* -------------------------------------------------------------
  mem_gets      -- メモリ上から一行を抽出し進めたポインタを返す
-------------------------------------------------------------- */
unsigned char *mem_gets(unsigned char *out, int max, unsigned char *in)
{
  int  i;

  for(i=0; i<max; i++) {
    if (*in == '\n') {
      out[i] = 0;
      in++;
      break;
    }
    else {
      out[i] = *in;
      in++;
    }
  }
  return(in);
}


/* -------------------------------------------------------------
  get_dir   -- ファイルの中からディレクトリデータを取り出す
-------------------------------------------------------------- */
void get_dir(void)
{
  FILE  *fp;
  unsigned char line[256];
  unsigned char *zipdata, *work, *r_dir;
  unsigned char *start2, *end2;
  long  start, end, size, r_start, r_size, zbody_size;
  long  key_ptr, i;

  /* --- ファイル名未指定エラー */
  if (RRDAFilename == NULL) {
#ifdef VERBOSE
      fprintf(stderr, "lm_rrda : filename missing.\n");
#endif
      return;
    }

  /* --- ファイルオープン */
  fp = fopen(RRDAFilename, "r");
  if (fp == NULL) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : file not open.(%s)\n", RRDAFilename);
#endif
    return;
  }

  /* --- 前半部のサイズとヘッダーを調べる */
  fgets(line, 256, fp);
  if (line == NULL) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : dir_f header wrong(%s)\n", RRDAFilename);
#endif
    fclose(fp);
    return;
  }
  if ((line[0] == 'R') && (line[1] == 'R') &&
      (line[2] == 'D') && (line[3] == 'A')) {
    fgets(line, 256, fp);
    sscanf(line, "%08x\n", &size);
    fgets(line, 256, fp);
    sscanf(line, "%08x,%08x\n", &r_start, &r_size);
    /* -- データ部分の検出 */
    start = ftell(fp);
    while(1) {
      fgets(line, 256, fp);
      end = ftell(fp);
      if (strncmp(line, "END", 3) == 0) break;
      /* - 安全策 */
      if (ftell(fp) >= size) break;
    }
    /* -- ディレクトリデータ読み込み */
    RRDADir_f = (char *)malloc(end-start);
    if (RRDADir_f == NULL) {
#ifdef VERBOSE
      fprintf(stderr, "lm_rrda : dir_f cannot memory allocate.\n");
#endif
      fclose(fp);
      return;
    }
    fseek(fp, start, SEEK_SET);
    if (fread(RRDADir_f, sizeof(char), (end-start), fp) == 0) {
#ifdef VERBOSE
      fprintf(stderr, "lm_rrda : dir_f read error.(%s)\n", RRDAFilename);
#endif
      fclose(fp);
      free(RRDADir_f);
      RRDADir_f = NULL;
      return;
    }
  }
  else {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : dir_f header wrong(%s)\n", RRDAFilename);
#endif
    fclose(fp);
    return;
  }

  /* --- 後半部の読み込みと検証 */
  zipdata = (char*)malloc(r_size);
  if (zipdata == NULL) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : dir_r cannot memory allocate.\n");
#endif
    fclose(fp);
    return;
  }
  fseek(fp, r_start, SEEK_SET);
  if (fread(zipdata, sizeof(char), r_size, fp) == 0) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : dir_r read error.(%s)\n", RRDAFilename);
#endif
    fclose(fp);
    free(zipdata);
    return;
  }
  /* -- 以上でディレクトリデータは読み終り */
  fclose(fp);
  /* -- キーのチェックと開錠 */
  RRDAKeyFlag = FALSE;
  if (RRDAKey[0] == 0) {
    free(zipdata);
    return;
  }
  key_ptr = r_start;
  work = zipdata;
  for(i=0; i<r_size; i++) {
    *work = (*work ^ 0xaa) ^ RRDAKey[key_ptr % 4];
    work++;
    key_ptr++;
  }
  /* -- 後半ディレクトリは圧縮されているので解凍 */
  work = gz_header_check((unsigned char *)zipdata, r_size);
  if (work == NULL) {
    /* -- キーが違うかデータが壊れているか */
    free(zipdata);
    return;
  }
  zbody_size = r_size - (long)(work - zipdata);
  r_dir = gunzip_data(work, zbody_size, NULL);
  free(zipdata);
  if (r_dir == NULL) {
    /* -- 解凍できませんでした */
    return;
  }
  /* -- 後半ヘッダーの検証 */
  if ((r_dir[0] == 'R') && (r_dir[1] == 'R') &&
      (r_dir[2] == 'D') && (r_dir[3] == 'A')) {
    /* - キーは正しかった様です */
    RRDAKeyFlag = TRUE;
    work = mem_gets(line, 256, r_dir);
    work = mem_gets(line, 256, work);
    work = mem_gets(line, 256, work);
    /* -- データ部分の検出 */
    start2 = work;
    while(1) {
      work = mem_gets(line, 256, work);
      end2 = work;
      if (strncmp(line, "END", 3) == 0) break;
      /* - 安全策 */
      if (work >= (r_dir + r_size)) break;
    }
    /* -- ディレクトリデータ読み込み */
    RRDADir_r = (char *)malloc(end2-start2);
    if (RRDADir_r == NULL) {
#ifdef VERBOSE
      fprintf(stderr, "lm_rrda : dir_r cannot memory allocate.\n");
#endif
      free(r_dir);
      return;
    }
    memcpy(RRDADir_r, start2, (end2-start2));
    free(r_dir);
  }
  else {
    /* -- キーが違うかデータが壊れているか */
    free(r_dir);
    return;
  }
}


/* -------------------------------------------------------------
  gz_header_check  -- gz データのヘッダーチェックとスキップ

    zlib の gzio.c 内ローカル関数 check_header() を参考に
    必要とする形式でリライトしたもの。
-------------------------------------------------------------- */
unsigned char *gz_header_check(unsigned char *data, long size)
{
  unsigned char   *limit, flags;
  unsigned long  l;

  limit = data + size;

  /* --- マジックナンバーのチェック */
  if ((data[0] != 0x1f) || (data[1] != 0x8b)) {
    /* -- Error キーが違うかデータが壊れているか */
    return(NULL);
  }
  data += 2;
  /* --- ファイルメソッドのチェック */
  /* Z_DEFLATED は zlib.h 内を参照 */
  flags = data[1];
  if ((data[0] != Z_DEFLATED) || ((flags & RESERVED) != 0)) {
    /* -- Error キーが違うかデータが壊れているか */
    return(NULL);
  }
  /* time 等の情報もまとめてスキップ */
  data += 8;
  /* --- extra フィールドのスキップ */
  if ((flags & EXTRA_FIELD) != 0) {
    l = (unsigned long)data[0] + (((unsigned long)data[1]) << 8);
    data += (l + 2);
  }
  /* --- オリジナルファイルネームのスキップ */
  if ((flags & ORIG_NAME) != 0) {
    while(*data != 0) {
      data++;
      if (data > limit) return(NULL);
    }
    data++;
  }
  /* --- ファイルコメントのスキップ */
  if ((flags & COMMENT) != 0) {
    while(*data != 0) {
      data++;
      if (data > limit) return(NULL);
    }
    data++;
  }
  /* --- ヘッダーCRC */
  if ((flags & HEAD_CRC) != 0) {
    data += 2;
  }
  /* --- ポインタ溢れチェック */
  if (data > limit) return(NULL);
  /* --- ヘッダースキップ終了 */

  return(data);
}


/* -------------------------------------------------------------
  gunzip_data   -- メモリ上の gz データを解凍し、ポインタを返す

    ヘッダーは既にスキップされて圧縮データ本体へのポインタが
    渡されているものとする。
-------------------------------------------------------------- */
unsigned char *gunzip_data(unsigned char *data, long size, long *filesize)
{
  z_stream  z;
  unsigned char  *outtmp, *output, *resize;
  unsigned long  outptr;
  int  status;

  /* --- メモリ管理の指定(オート) */
  z.zalloc = Z_NULL;
  z.zfree  = Z_NULL;
  z.opaque = Z_NULL;

  /* --- 展開結果バッファ */
  output = NULL;
  outptr = 0;
  outtmp = (unsigned char *)malloc(ZLIB_BUFSIZE);
  if (outtmp == NULL) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : decompress malloc error.\n");
#endif
    return(NULL);
  }

  /* --- zlib 初期化 */
  z.next_in = Z_NULL;
  z.avail_in = 0;
  if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : zlib init error.\n");
#endif
    return(NULL);
  }

  /* --- 解凍データ */
  z.avail_in = size;
  z.next_in = data;
  z.avail_out = ZLIB_BUFSIZE;
  z.next_out = outtmp;
  status = Z_OK;

  /* --- 解凍処理 */
  while((status != Z_STREAM_END) && (outptr < Z_OUTMAX)) {
    /* -- zlib call */
    status = inflate(&z, Z_NO_FLUSH);
    /* -- inflate error */
    if ((status != Z_OK) && (status != Z_STREAM_END)) {
#ifdef VERBOSE
      fprintf(stderr, "lm_rrda : decompress error.\n");
#endif
      free(outtmp);
      if (output != NULL) free(output);
      return(NULL);
    }
    /* -- 解凍データの追加保持 */
    if (output == NULL) {
      output = (unsigned char *)malloc(ZLIB_BUFSIZE);
      if (output == NULL) {
#ifdef VERBOSE
	fprintf(stderr, "lm_rrda : decompress output malloc error.\n");
#endif
	free(outtmp);
	return(NULL);
      }
      memcpy(output, outtmp, ZLIB_BUFSIZE);
      outptr = ZLIB_BUFSIZE;
      z.avail_out = ZLIB_BUFSIZE;
      z.next_out = outtmp;
    }
    else {
      output = (unsigned char *)realloc(output, outptr+(ZLIB_BUFSIZE-z.avail_out));
      memcpy(output+outptr, outtmp, (ZLIB_BUFSIZE-z.avail_out));
      outptr += (ZLIB_BUFSIZE-z.avail_out);
      z.avail_out = ZLIB_BUFSIZE;
      z.next_out = outtmp;
    }
  }

  /* --- zlib 後かたづけ */
  if (inflateEnd(&z) != Z_OK) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : zlib end error.\n");
#endif
    return(NULL);
  }

  /* --- 結果を返して終了 */
  if (filesize != NULL) {
    *filesize = outptr;
  }
  free(outtmp);
  return(output);
}


/* -------------------------------------------------------------
  file_search   -- 指定ファイルをディレクトリから探す
-------------------------------------------------------------- */
void file_search(unsigned char *name, RRDAFile *filedata)
{
  unsigned char *dir, line[256], dataname[256];
  long  start, size;
  int  stat;

  /* --- エラー時のデフォルト値設定 */
  filedata->top  = 0;
  filedata->size = 0;
  filedata->resist_flag = FALSE;

  /* --- フリーエリアからデータを探す */
  if (RRDADir_f != NULL) {
    dir = RRDADir_f;
    stat = 1;
    while(stat == 1) {
      dir = mem_gets(line, 256, dir);
      if (strncmp(line, "END", 3) == 0) break;
      sscanf(line, "%08x,%08x,%s\n", &start, &size, dataname);
      if (strcmp(dataname, name) == 0) {
	filedata->top  = start;
	filedata->size = size;
	filedata->resist_flag = FALSE;
	stat = 0;
      }
    }
  }

  /* --- 制限エリアからデータを探す */
  if (RRDADir_r != NULL) {
    dir = RRDADir_r;
    stat = 1;
    while(stat == 1) {
      dir = mem_gets(line, 256, dir);
      if (strncmp(line, "END", 3) == 0) break;
      sscanf(line, "%08x,%08x,%s\n", &start, &size, dataname);
      if (strcmp(dataname, name) == 0) {
	filedata->top  = start;
	filedata->size = size;
	filedata->resist_flag = TRUE;
	stat = 0;
      }
    }
  }
}


/* -------------------------------------------------------------
  get_file      -- 指定ファイルをロードする
-------------------------------------------------------------- */
unsigned char *get_file(unsigned char *dataname, long *size)
{
  FILE  *fp;
  RRDAFile file;
  unsigned char *zipwork, *work, *dataptr;
  long  i, zbody_size, bodysize, key_ptr;

  /* --- ファイル名未指定エラー */
  if (RRDAFilename == NULL) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : filename missing.\n");
#endif
    return(NULL);
  }
  /* --- ファイルオープン */
  fp = fopen(RRDAFilename, "r");
  if (fp == NULL) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : file not open.(%s)\n", RRDAFilename);
#endif
    return(NULL);
  }
  /* --- 目的ファイルを狙いうち */
  file_search(dataname, &file);
  if ((file.top == 0) || (file.size == 0)) {
    close(fp);
    return(NULL);
  }
  /* --- 圧縮データ読み込みワーク */
  zipwork = (unsigned char *)malloc(file.size);
  if (zipwork == NULL) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : fileload cannot memory allocate.\n");
#endif
    fclose(fp);
    return(NULL);
  }
  /* --- データ読み込み(圧縮され) */
  fseek(fp, file.top, SEEK_SET);
  if (fread(zipwork, sizeof(char), file.size, fp) != file.size) {
#ifdef VERBOSE
    fprintf(stderr, "lm_rrda : file read error.(%s - %s)\n",
	    RRDAFilename, dataname);
#endif
    fclose(fp);
    free(zipwork);
    return(NULL);
  }
  /* -- 以上でディレクトリデータは読み終り */
  fclose(fp);
  /* -- キーの開錠 */
  if (file.resist_flag == TRUE) {
    key_ptr = file.top;
    work = zipwork;
    for(i=0; i<file.size; i++) {
      *work = (*work ^ 0xaa) ^ RRDAKey[key_ptr % 4];
      work++;
      key_ptr++;
    }
  }
  /* -- 圧縮解凍 */
  work = gz_header_check((unsigned char *)zipwork, file.size);
  if (work == NULL) {
    /* -- キーが違うかデータが壊れているか */
    free(zipwork);
    return(NULL);
  }
  zbody_size = file.size - (long)(work - zipwork);
  dataptr = gunzip_data(work, zbody_size, &bodysize);
  free(zipwork);
  if (dataptr == NULL) {
    /* -- 解凍できませんでした */
    return(NULL);
  }

  if (size != NULL) {
    *size = bodysize;
  }
  return(dataptr);
}

