/* <!-- copyright */
/*
 * libmetalink
 *
 * Copyright (c) 2008 Tatsuhiro Tsujikawa
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
/* copyright --> */
#include "metalink_pstate_v4.h"

#include <time.h>
#ifdef HAVE_TIME64_H
#include <time64.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

#include "metalink_pstm.h"
#include "metalink_helper.h"
#ifndef HAVE_STRPTIME
#include "strptime.h"
#endif /* !HAVE_STRPTIME */
#ifndef HAVE_TIMEGM
#include "timegm.h"
#endif /* !HAVE_TIMEGM */

static time_t my_timegm(struct tm *tm) {
#ifdef HAVE__MKGMTIME
  /* WINE does not implement _mkgmtime() yet */
  return _mkgmtime(tm);
#elif defined(HAVE_MKGMTIME)
  return mkgmtime(tm);
#elif defined(HAVE_TIMEGM64)
  return (time_t)timegm64(tm);
#else
  return timegm(tm);
#endif
}

/* parse a RFC3339 formatted date, ie. 2010-05-01T12:15:02Z or
   2010-05-01T12:16:02+01:00 */
static time_t parse_date(const char *date) {
  time_t t = 0;
  int sign = 0;
  struct tm tm, offset;
  char *rest;

  if (strlen(date) < 20) {
    return t;
  }

  memset(&tm, 0, sizeof(tm));

  rest = strptime(date, "%Y-%m-%dT%H:%M:%S", &tm);

  if (rest == NULL) {
    return t;
  }

  t = my_timegm(&tm);

  while (*rest != 'Z' && *rest != '+' && *rest != '-' && *rest != '\0') {
    rest++;
  }

  if (*rest == '\0') {
    /* not a valid RFC3339 date, but we can still return what has already been
     * parsed */
    return t;
  }
  if (*rest == '+') {
    sign = -1;
  } else if (*rest == '-') {
    sign = +1;
  }

  if (sign != 0) {
    memset(&offset, 0, sizeof(offset));
    if (strptime(rest + 1, "%H:%M", &offset) == NULL)
      return t;
    t += sign * (offset.tm_hour * 3600 + offset.tm_min * 60);
  }

  return t;
}

/* metalink state <metalink> */
void metalink_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                 const char **attrs) {
  metalink_error_t r;

  if (ns_uri != METALINK_NS_V4) {
    metalink_pstm_enter_skip_state(stm);
    return;
  }

  switch (name) {
  case METALINK_TOKEN_FILE: {
    const char *fname;
    metalink_file_t *file;

    fname = attrs[METALINK_ATTR_TOKEN_NAME];
    if (!metalink_check_safe_path(fname)) {
      metalink_pstm_enter_skip_state(stm);
      return;
    }

    file = metalink_pctrl_new_file_transaction(stm->ctrl);
    if (!file) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }
    r = metalink_pctrl_file_set_name(stm->ctrl, fname);
    if (r != 0) {
      error_handler(stm, r);
      return;
    }

    metalink_pstm_enter_file_state_v4(stm);
    break;
  }
  case METALINK_TOKEN_GENERATOR:
    metalink_pstm_enter_generator_state(stm);
    break;
  case METALINK_TOKEN_ORIGIN: {
    const char *dynamic_attr;
    dynamic_attr = attrs[METALINK_ATTR_TOKEN_DYNAMIC];
    if (dynamic_attr && strcmp("true", dynamic_attr) == 0) {
      metalink_pctrl_set_origin_dynamic(stm->ctrl, 1);
    }
    metalink_pstm_enter_origin_state(stm);
    break;
  }
  case METALINK_TOKEN_PUBLISHED:
    metalink_pstm_enter_published_state_v4(stm);
    break;
  case METALINK_TOKEN_UPDATED:
    metalink_pstm_enter_updated_state_v4(stm);
    break;
  default:
    metalink_pstm_enter_skip_state(stm);
  }
}

void metalink_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                               const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;
  (void)characters;

  r = metalink_pctrl_metalink_accumulate_files(stm->ctrl);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }
  metalink_pstm_enter_fin_state(stm);
}

/* generator state <generator> */
void generator_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                  const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void generator_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                const char *characters) {
  (void)name;
  (void)ns_uri;

  metalink_pctrl_set_generator(stm->ctrl, characters);
  metalink_pstm_enter_metalink_state_v4(stm);
}

/* origin state <origin> */
void origin_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                               const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void origin_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                             const char *characters) {
  (void)name;
  (void)ns_uri;

  metalink_pctrl_set_origin(stm->ctrl, characters);
  metalink_pstm_enter_metalink_state_v4(stm);
}

/* file state <file> */
void file_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                             const char **attrs) {
  metalink_error_t r;

  if (ns_uri != METALINK_NS_V4) {
    metalink_pstm_enter_skip_state(stm);
    return;
  }

  switch (name) {
  case METALINK_TOKEN_URL: {
    const char *location;
    const char *value;
    long int priority = 999999;
    metalink_resource_t *resource;

    resource = metalink_pctrl_new_resource_transaction(stm->ctrl);
    if (!resource) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }

    location = attrs[METALINK_ATTR_TOKEN_LOCATION];
    if (location) {
      r = metalink_pctrl_resource_set_location(stm->ctrl, location);
      if (r != 0) {
        error_handler(stm, r);
        return;
      }
    }

    value = attrs[METALINK_ATTR_TOKEN_PRIORITY];
    if (value) {
      errno = 0;
      priority = strtol(value, 0, 10);
      if (errno == ERANGE || priority < 0 || priority > INT_MAX) {
        priority = 999999;
      }
    }
    metalink_pctrl_resource_set_priority(stm->ctrl, (int)priority);

    metalink_pstm_enter_url_state(stm);
    break;
  }
  case METALINK_TOKEN_METAURL: {
    const char *mediatype;
    const char *metaurl_name;
    const char *value;
    long int priority = 999999;
    metalink_metaurl_t *metaurl;

    metaurl = metalink_pctrl_new_metaurl_transaction(stm->ctrl);
    if (!metaurl) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }

    mediatype = attrs[METALINK_ATTR_TOKEN_MEDIATYPE];
    if (mediatype) {
      r = metalink_pctrl_metaurl_set_mediatype(stm->ctrl, mediatype);
      if (r != 0) {
        error_handler(stm, r);
        return;
      }
    } else {
      /* mediatype argument is mandatory, skip if not present */
      metalink_pstm_enter_skip_state(stm);
      return;
    }

    metaurl_name = attrs[METALINK_ATTR_TOKEN_NAME];
    if (metaurl_name) {
      r = metalink_pctrl_metaurl_set_name(stm->ctrl, metaurl_name);
      if (r != 0) {
        error_handler(stm, r);
        return;
      }
    }

    value = attrs[METALINK_ATTR_TOKEN_PRIORITY];
    if (value) {
      errno = 0;
      priority = strtol(value, 0, 10);
      if (errno == ERANGE || priority < 0 || priority > INT_MAX) {
        priority = 999999;
      }
    }
    metalink_pctrl_metaurl_set_priority(stm->ctrl, (int)priority);

    metalink_pstm_enter_metaurl_state_v4(stm);
    break;
  }
  case METALINK_TOKEN_HASH: {
    const char *type;
    metalink_checksum_t *checksum;

    type = attrs[METALINK_ATTR_TOKEN_TYPE];
    if (!type) {
      metalink_pstm_enter_skip_state(stm);
      return;
    }
    checksum = metalink_pctrl_new_checksum_transaction(stm->ctrl);
    if (!checksum) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }
    r = metalink_checksum_set_type(checksum, type);
    if (r != 0) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }
    metalink_pstm_enter_hash_state(stm);
    break;
  }
  case METALINK_TOKEN_PIECES: {
    const char *type;
    const char *value;
    long int length;
    metalink_chunk_checksum_t *chunk_checksum;

    type = attrs[METALINK_ATTR_TOKEN_TYPE];
    if (!type) {
      metalink_pstm_enter_skip_state(stm);
      return;
    }

    value = attrs[METALINK_ATTR_TOKEN_LENGTH];
    if (value) {
      errno = 0;
      length = strtol(value, 0, 10);
      if (errno == ERANGE || length < 0 || length > INT_MAX) {
        /* Invalid piece size: skip this tag */
        metalink_pstm_enter_skip_state(stm);
        return;
      }
    } else {
      metalink_pstm_enter_skip_state(stm);
      return;
    }

    chunk_checksum = metalink_pctrl_new_chunk_checksum_transaction(stm->ctrl);
    if (!chunk_checksum) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }
    r = metalink_chunk_checksum_set_type(chunk_checksum, type);
    if (r != 0) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }
    metalink_chunk_checksum_set_length(chunk_checksum, (int)length);

    metalink_pstm_enter_pieces_state_v4(stm);
    break;
  }
  case METALINK_TOKEN_SIGNATURE: {
    const char *mediatype;
    metalink_signature_t *signature;

    mediatype = attrs[METALINK_ATTR_TOKEN_MEDIATYPE];
    if (!mediatype) {
      metalink_pstm_enter_skip_state(stm);
      return;
    }
    signature = metalink_pctrl_new_signature_transaction(stm->ctrl);
    if (!signature) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }
    r = metalink_signature_set_mediatype(signature, mediatype);
    if (r != 0) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }
    metalink_pstm_enter_signature_state_v4(stm);
    break;
  }
  case METALINK_TOKEN_PUBLISHER: {
    const char *publisher_name;
    const char *url;

    publisher_name = attrs[METALINK_ATTR_TOKEN_NAME];
    if (!publisher_name) {
      /* name is mandatory */
      metalink_pstm_enter_skip_state(stm);
      return;
    }
    r = metalink_pctrl_file_set_publisher_name(stm->ctrl, publisher_name);
    if (r != 0) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }

    url = attrs[METALINK_ATTR_TOKEN_URL];
    if (url) {
      /* url is optional */
      r = metalink_pctrl_file_set_publisher_url(stm->ctrl, url);
      if (r != 0) {
        error_handler(stm, METALINK_ERR_BAD_ALLOC);
        return;
      }
    }
    metalink_pstm_enter_skip_state(stm);
    break;
  }
  case METALINK_TOKEN_DESCRIPTION:
    metalink_pstm_enter_description_state_v4(stm);
    break;
  case METALINK_TOKEN_COPYRIGHT:
    metalink_pstm_enter_copyright_state_v4(stm);
    break;
  case METALINK_TOKEN_IDENTITY:
    metalink_pstm_enter_identity_state_v4(stm);
    break;
  case METALINK_TOKEN_LOGO:
    metalink_pstm_enter_logo_state_v4(stm);
    break;
  case METALINK_TOKEN_LANGUAGE:
    metalink_pstm_enter_language_state(stm);
    break;
  case METALINK_TOKEN_OS:
    metalink_pstm_enter_os_state(stm);
    break;
  case METALINK_TOKEN_SIZE:
    metalink_pstm_enter_size_state(stm);
    break;
  case METALINK_TOKEN_VERSION:
    metalink_pstm_enter_version_state(stm);
    break;
  default:
    metalink_pstm_enter_skip_state(stm);
  }
}

void file_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                           const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;
  (void)characters;

  r = metalink_pctrl_commit_file_transaction(stm->ctrl);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }
  metalink_pstm_enter_metalink_state_v4(stm);
}

/* description state <description> */
void description_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                    const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void description_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                  const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;

  r = metalink_pctrl_file_set_description(stm->ctrl, characters);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }
  metalink_pstm_enter_file_state_v4(stm);
}

/* copyright state <copyright> */
void copyright_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                  const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void copyright_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;

  r = metalink_pctrl_file_set_copyright(stm->ctrl, characters);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }
  metalink_pstm_enter_file_state_v4(stm);
}

/* identity state <identity> */
void identity_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                 const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void identity_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                               const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;

  r = metalink_pctrl_file_set_identity(stm->ctrl, characters);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }
  metalink_pstm_enter_file_state_v4(stm);
}

/* logo state <logo> */
void logo_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                             const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void logo_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                           const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;

  r = metalink_pctrl_file_set_logo(stm->ctrl, characters);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }
  metalink_pstm_enter_file_state_v4(stm);
}

/* signature state <signature> */
void signature_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                  const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void signature_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;

  r = metalink_pctrl_signature_set_signature(stm->ctrl, characters);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }

  r = metalink_pctrl_commit_signature_transaction(stm->ctrl);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }

  metalink_pstm_enter_file_state_v4(stm);
}

/* pieces state <pieces> */
void pieces_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                               const char **attrs) {
  (void)attrs;

  if (ns_uri != METALINK_NS_V4) {
    metalink_pstm_enter_skip_state(stm);
    return;
  }

  if (name == METALINK_TOKEN_HASH) {
    metalink_piece_hash_t *piece_hash;

    piece_hash = metalink_pctrl_new_piece_hash_transaction(stm->ctrl);
    if (!piece_hash) {
      error_handler(stm, METALINK_ERR_BAD_ALLOC);
      return;
    }

    metalink_pstm_enter_piece_hash_state(stm);
  } else {
    metalink_pstm_enter_skip_state(stm);
  }
}

void pieces_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                             const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;
  (void)characters;

  r = metalink_pctrl_commit_chunk_checksum_transaction(stm->ctrl);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }

  metalink_pstm_enter_file_state_v4(stm);
}

/* metaurl state <metaurl> */
void metaurl_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void metaurl_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                              const char *characters) {
  metalink_error_t r;

  (void)name;
  (void)ns_uri;

  r = metalink_pctrl_metaurl_set_url(stm->ctrl, characters);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }

  r = metalink_pctrl_commit_metaurl_transaction(stm->ctrl);
  if (r != 0) {
    error_handler(stm, r);
    return;
  }

  metalink_pstm_enter_file_state_v4(stm);
}

/* published state <published> */
void published_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                  const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void published_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                const char *characters) {
  time_t t = parse_date(characters);

  (void)name;
  (void)ns_uri;

  metalink_pctrl_set_published(stm->ctrl, t);
  metalink_pstm_enter_metalink_state_v4(stm);
}

/* updated state <updated> */
void updated_state_start_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                                const char **attrs) {
  (void)name;
  (void)ns_uri;
  (void)attrs;

  metalink_pstm_enter_skip_state(stm);
}

void updated_state_end_fun_v4(metalink_pstm_t *stm, int name, int ns_uri,
                              const char *characters) {
  time_t t = parse_date(characters);

  (void)name;
  (void)ns_uri;

  metalink_pctrl_set_updated(stm->ctrl, t);
  metalink_pstm_enter_metalink_state_v4(stm);
}
