/* ------------------------------------------------------------ */
/*
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 1998 Xavier Roche and other contributors

SPDX-License-Identifier: GPL-3.0-or-later

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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, see <http://www.gnu.org/licenses/>.

Ethical use: we kindly ask that you NOT use this software to harvest email
addresses or to collect any other private information about people. Doing so
would dishonor our work and waste the many hours we have spent on it.

Please visit our Website: http://www.httrack.com
*/

/* ------------------------------------------------------------ */
/* File: httrack.c subroutines:                                 */
/*       hash table system (fast index)                         */
/* Author: Xavier Roche                                         */
/* ------------------------------------------------------------ */

/* Internal engine bytecode */
#define HTS_INTERNAL_BYTECODE

#include "htsopt.h"
#include "htshash.h"

/* specific definitions */
#include "htsbase.h"
#include "htsglobal.h"
#include "htsmd5.h"
#include "htscore.h"
#include "coucal.h"
/* END specific definitions */

/* Specific macros */
#ifndef malloct
#define malloct malloc
#define freet free
#define calloct calloc
#define strcpybuff strcpy
#endif

// GESTION DES TABLES DE HACHAGE
// Méthode à 2 clés (adr+fil), 2e cle facultative
// hash[no_enregistrement][pos]->hash est un index dans le tableau général liens
// type: numero enregistrement - 0 est case insensitive (sav) 1 (adr+fil) 2 (former_adr+former_fil)
// recherche dans la table selon nom1,nom2 et le no d'enregistrement

/* Key free handler (NOOP) ; addresses are kept */
static void key_freehandler(void *arg, coucal_key value) {
}

/* Key strdup (pointer copy) */
static coucal_key key_duphandler(void *arg, coucal_key_const name) {
  union {
    coucal_key_const roname;
    coucal_key name;
  } u;
  u.roname = name;
  return u.name;
}

/* Key sav hashes are using case-insensitive version */
static coucal_hashkeys key_sav_hashes(void *arg, coucal_key_const key) {
  hash_struct *const hash = (hash_struct*) arg;
  convtolower(hash->catbuff, sizeof(hash->catbuff), (const char *) key);
  return coucal_hash_string(hash->catbuff);
}

/* Key sav comparison is case-insensitive */
static int key_sav_equals(void *arg,
                          coucal_key_const a_,
                          coucal_key_const b_) {
  const char *const a = (const char*) a_;
  const char *const b = (const char*) b_;
  return strcasecmp(a, b) == 0;
}

static const char* key_sav_debug_print(void *arg,
                                       coucal_key_const a) {
  return (const char*) a;
}

static const char* value_sav_debug_print(void *arg, coucal_value_const a) {
  return (char*) a.ptr;
}

/* Dedup-key host for ADR: --host-alias first, so a rule may name the www. form
   the urlhack collapse would otherwise have eaten. */
static const char *key_host_alias(const hash_struct *hash, const char *adr,
                                  char *buf, size_t bufsize) {
  const char *const canon =
      hts_host_alias(hash->host_alias, adr, hash->norm_host, buf, bufsize);

  return canon != NULL ? canon : adr;
}

/* see htshash.h */
const char *hash_url_key(hash_struct *hash, const char *adr, const char *fil,
                         char *dst, size_t dstsize) {
  char BIGSTK aliasbuf[HTS_URLMAXSIZE * 2];
  const char *const adr_canon =
      adr != NULL ? key_host_alias(hash, adr, aliasbuf, sizeof(aliasbuf))
                  : NULL;
  const char *const adr_norm =
      adr_canon != NULL
          ? (hash->norm_host ? jump_normalized_const(adr_canon)
                             : jump_identification_const(adr_canon))
          : NULL;

  // copy address
  assertf(adr_norm != NULL);
  /* clip, don't abort: a wire URL feeds this, and the key only feeds the hash,
     so a clipped one costs a collision and never a wrong dedup */
  (void) strclipbuff(dst, dstsize, adr_norm);

  // copy link
  assertf(fil != NULL);
  {
    /* resolve the per-URL strip keys; strip applies even when urlhack is off */
    char BIGSTK keybuf[HTS_URLMAXSIZE];
    const char *const keys = hts_query_strip_keys(hash->strip_query, adr_canon,
                                                  fil, keybuf, sizeof(keybuf));
    const size_t used = strlen(dst);
    char *const tail = &dst[used];
    const size_t avail = dstsize - used;

    /* normalizing never expands, and it takes a path of at most its own
       HTS_URLMAXSIZE * 2, so bound on both */
    if ((hash->norm_slash || hash->norm_query || keys != NULL) &&
        strlen(fil) < avail && strlen(fil) < HTS_URLMAXSIZE * 2) {
      fil_normalized_filtered_ex(fil, tail, keys, hash->norm_slash,
                                 hash->norm_query);
    } else {
      (void) strclipbuff(tail, avail, fil);
    }
  }
  return dst;
}

/* Pseudo-key (lien_url structure) hash function */
static coucal_hashkeys
key_adrfil_hashes_generic(void *arg, coucal_key_const value, const int former) {
  hash_struct *const hash = (hash_struct *) arg;
  const lien_url *const lien = (const lien_url *) value;
  const char *const adr = !former ? lien->adr : lien->former_adr;
  const char *const fil = !former ? lien->fil : lien->former_fil;

  return coucal_hash_string(
      hash_url_key(hash, adr, fil, hash->normfil, sizeof(hash->normfil)));
}

/* Pseudo-key (lien_url structure) comparison function */
static int key_adrfil_equals_generic(void *arg,
                                     coucal_key_const a_,
                                     coucal_key_const b_, 
                                     const int former) {
  hash_struct *const hash = (hash_struct *) arg;
  const lien_url*const a = (const lien_url*) a_;
  const lien_url*const b = (const lien_url*) b_;
  const char *const a_adr = !former ? a->adr : a->former_adr;
  const char *const b_adr = !former ? b->adr : b->former_adr;
  const char *const a_fil = !former ? a->fil : a->former_fil;
  const char *const b_fil = !former ? b->fil : b->former_fil;
  char BIGSTK a_aliasbuf[HTS_URLMAXSIZE * 2], b_aliasbuf[HTS_URLMAXSIZE * 2];
  const char *a_canon, *b_canon;
  const char *ja;
  const char *jb;

  // safety
  assertf(a_adr != NULL);
  assertf(b_adr != NULL);
  assertf(a_fil != NULL);
  assertf(b_fil != NULL);

  a_canon = key_host_alias(hash, a_adr, a_aliasbuf, sizeof(a_aliasbuf));
  b_canon = key_host_alias(hash, b_adr, b_aliasbuf, sizeof(b_aliasbuf));

  // skip scheme and authentication to the domain (possibly without www.)
  ja = hash->norm_host ? jump_normalized_const(a_canon)
                       : jump_identification_const(a_canon);
  jb = hash->norm_host ? jump_normalized_const(b_canon)
                       : jump_identification_const(b_canon);
  assertf(ja != NULL);
  assertf(jb != NULL);
  if (strcasecmp(ja, jb) != 0) {
    return 0;
  }

  // now compare pathes
  {
    char BIGSTK ka[HTS_URLMAXSIZE], kb[HTS_URLMAXSIZE];
    const char *const keysa =
        hts_query_strip_keys(hash->strip_query, a_canon, a_fil, ka, sizeof(ka));
    const char *const keysb =
        hts_query_strip_keys(hash->strip_query, b_canon, b_fil, kb, sizeof(kb));

    if (hash->norm_slash || hash->norm_query || keysa != NULL ||
        keysb != NULL) {
      fil_normalized_filtered_ex(a_fil, hash->normfil, keysa, hash->norm_slash,
                                 hash->norm_query);
      fil_normalized_filtered_ex(b_fil, hash->normfil2, keysb, hash->norm_slash,
                                 hash->norm_query);
      return strcmp(hash->normfil, hash->normfil2) == 0;
    } else {
      return strcmp(a_fil, b_fil) == 0;
    }
  }
}

static const char* key_adrfil_debug_print_(void *arg,
                                           coucal_key_const a_,
                                           const int former) {
  hash_struct *const hash = (hash_struct*) arg;
  const lien_url*const a = (const lien_url*) a_;
  const char *const a_adr = !former ? a->adr : a->former_adr;
  const char *const a_fil = !former ? a->fil : a->former_fil;
  snprintf(hash->normfil, sizeof(hash->normfil), "%s%s", a_adr, a_fil);
  return hash->normfil;
}

static const char* key_adrfil_debug_print(void *arg,
                                          coucal_key_const a_) {
  return key_adrfil_debug_print_(arg, a_, 0);
}

static const char* key_former_adrfil_debug_print(void *arg,
                                                 coucal_key_const a_) {
  return key_adrfil_debug_print_(arg, a_, 1);
}

static const char* value_adrfil_debug_print(void *arg,
                                            coucal_value_const value) {
  hash_struct *const hash = (hash_struct*) arg;
  snprintf(hash->normfil2, sizeof(hash->normfil2), "%d", (int) value.intg);
  return hash->normfil2;
}

/* "adr"/"fil" lien_url structure members hashing function */
static coucal_hashkeys key_adrfil_hashes(void *arg, coucal_key_const value_) {
  return key_adrfil_hashes_generic(arg, value_, 0);
}

/* "adr"/"fil" lien_url structure members comparison function */
static int key_adrfil_equals(void *arg,
                             coucal_key_const a,
                             coucal_key_const b) {
  return key_adrfil_equals_generic(arg, a, b, 0);
}

/* "former_adr"/"former_fil" lien_url structure members hashing function */
static coucal_hashkeys key_former_adrfil_hashes(void *arg, coucal_key_const value_) {
  return key_adrfil_hashes_generic(arg, value_, 1);
}

/* "former_adr"/"former_fil" lien_url structure members comparison function */
static int key_former_adrfil_equals(void *arg,
                                    coucal_key_const a,
                                    coucal_key_const b) {
  return key_adrfil_equals_generic(arg, a, b, 1);
}

void hash_init(httrackp *opt, hash_struct *hash, hts_boolean normalized) {
  hash->sav = coucal_new(0);
  hash->adrfil = coucal_new(0);
  hash->former_adrfil = coucal_new(0);
  /* urlhack is the umbrella; per-feature negatives opt out of each part */
  hash->norm_host = normalized && !opt->no_www_dedup;
  hash->norm_slash = normalized && !opt->no_slash_dedup;
  hash->norm_query = normalized && !opt->no_query_dedup;
  /* snapshot the query-strip list (not owned; valid for the hash lifetime) */
  hash->strip_query =
      StringNotEmpty(opt->strip_query) ? StringBuff(opt->strip_query) : NULL;
  /* same for the host-alias rules: they apply whatever urlhack says */
  hash->host_alias = hts_host_alias_rules(opt);

  hts_set_hash_handler(hash->sav, opt);
  hts_set_hash_handler(hash->adrfil, opt);
  hts_set_hash_handler(hash->former_adrfil, opt);

  coucal_set_name(hash->sav, "hash->sav");
  coucal_set_name(hash->adrfil, "hash->adrfil");
  coucal_set_name(hash->former_adrfil, "hash->former_adrfil");

  /* Case-insensitive comparison ; keys are direct char* filenames */
  coucal_value_set_key_handler(hash->sav,
                                key_duphandler,
                                key_freehandler,
                                key_sav_hashes,
                                key_sav_equals,
                                hash);

  /* URL-style comparison ; keys are lien_url structure pointers casted 
     to char* */
  coucal_value_set_key_handler(hash->adrfil,
                                key_duphandler,
                                key_freehandler,
                                key_adrfil_hashes,
                                key_adrfil_equals,
                                hash);
  coucal_value_set_key_handler(hash->former_adrfil,
                                key_duphandler,
                                key_freehandler,
                                key_former_adrfil_hashes,
                                key_former_adrfil_equals,
                                hash);

  /* pretty-printing */
  coucal_set_print_handler(hash->sav,
                            key_sav_debug_print,
                            value_sav_debug_print,
                            NULL);
  coucal_set_print_handler(hash->adrfil,
                            key_adrfil_debug_print,
                            value_adrfil_debug_print,
                            hash);
  coucal_set_print_handler(hash->former_adrfil,
                            key_former_adrfil_debug_print,
                            value_adrfil_debug_print,
                            hash);
}

void hash_free(hash_struct *hash) {
  if (hash != NULL) {
    coucal_delete(&hash->sav);
    coucal_delete(&hash->adrfil);
    coucal_delete(&hash->former_adrfil);
  }
}

/* Test helper: do the two URLs dedupe to the same key under opt's urlhack
   flags? Exercises the live hash compare (norm_host/slash/query resolution). */
hts_boolean hash_url_equals(httrackp *opt, const char *adra, const char *fila,
                            const char *adrb, const char *filb) {
  hash_struct hash;
  lien_url la, lb;
  hts_boolean eq;

  memset(&la, 0, sizeof(la));
  memset(&lb, 0, sizeof(lb));
  la.adr = key_duphandler(NULL, adra);
  la.fil = key_duphandler(NULL, fila);
  lb.adr = key_duphandler(NULL, adrb);
  lb.fil = key_duphandler(NULL, filb);
  hash_init(opt, &hash, opt->urlhack);
  eq = key_adrfil_equals(&hash, &la, &lb);
  hash_free(&hash);
  return eq;
}

// retour: position ou -1 si non trouvé
int hash_read(const hash_struct * hash, const char *nom1, const char *nom2,
              hash_struct_type type) {
  intptr_t intvalue;
  lien_url lien;

  /* read */
  switch(type) {
  case HASH_STRUCT_FILENAME:
    if (coucal_read(hash->sav, nom1, &intvalue)) {
      return (int) intvalue;
    } else {
      return -1;
    }
    break;
  case HASH_STRUCT_ADR_PATH:
    memset(&lien, 0, sizeof(lien));
    lien.adr = key_duphandler(NULL, nom1);
    lien.fil = key_duphandler(NULL, nom2);
    if (coucal_read(hash->adrfil, (char*) &lien, &intvalue)) {
      return (int) intvalue;
    } else {
      return -1;
    }
    break;
  case HASH_STRUCT_ORIGINAL_ADR_PATH:
    memset(&lien, 0, sizeof(lien));
    lien.former_adr = key_duphandler(NULL, nom1);
    lien.former_fil = key_duphandler(NULL, nom2);
    if (coucal_read(hash->former_adrfil, (char*) &lien, &intvalue)) {
      return (int) intvalue;
    } else {
      return -1;
    }
    break;
  default:
    assertf(! "unexpected case");
    return -1;
    break;
  }
}

// enregistrement lien lpos dans les 3 tables hash1..3
void hash_write(hash_struct * hash, size_t lpos) {
  /* first entry: destination filename (lowercased) */
  coucal_write(hash->sav, (*hash->liens)[lpos]->sav, lpos);

  /* second entry: URL address and path */
  coucal_write(hash->adrfil, (*hash->liens)[lpos], lpos);

  /* third entry: URL address and path before redirect */
  if ((*hash->liens)[lpos]->former_adr) {        // former_adr existe?
    coucal_write(hash->former_adrfil, (*hash->liens)[lpos], lpos);
  }
}
