/***************************************************************************
 *                                  _   _ ____  _
 *  Project                     ___| | | |  _ \| |
 *                             / __| | | | |_) | |
 *                            | (__| |_| |  _ <| |___
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution. The terms
 * are also available at https://curl.se/docs/copyright.html.
 *
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
 * copies of the Software, and permit persons to whom the Software is
 * furnished to do so, under the terms of the COPYING file.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 * SPDX-License-Identifier: curl
 *
 ***************************************************************************/
#include "curl_setup.h"

#include "urldata.h"
#include "sendf.h"
#include "curl_trc.h"
#include "headers.h"

#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API)

/* Generate the curl_header struct for the user. This function MUST assign all
   struct fields in the output struct. */
static void copy_header_external(struct Curl_header_store *hs,
                                 size_t index,
                                 size_t amount,
                                 struct Curl_llist_node *e,
                                 struct curl_header *hout)
{
  struct curl_header *h = hout;
  h->name = hs->name;
  h->value = hs->value;
  h->amount = amount;
  h->index = index;
  /* this will randomly OR a reserved bit for the sole purpose of making it
     impossible for applications to do == comparisons, as that would otherwise
     be tempting and then lead to the reserved bits not being reserved
     anymore. */
  h->origin = (unsigned int)(hs->type | (1 << 27));
  h->anchor = e;
}

/* public API */
CURLHcode curl_easy_header(CURL *curl,
                           const char *name,
                           size_t nameindex,
                           unsigned int origin,
                           int request,
                           struct curl_header **hout)
{
  struct Curl_eapi_guard guard;
  CURLHcode hresult = CURLHE_OK;
  CURLcode result;

  if(CURL_EAPI_ENTER(&guard, curl, easy_header, &result)) {
    struct Curl_easy *data = curl;
    struct Curl_llist_node *e;
    struct Curl_llist_node *e_pick = NULL;
    size_t match = 0;
    size_t amount = 0;
    struct Curl_header_store *hs = NULL;
    struct Curl_header_store *pick = NULL;
    if(!name || !hout || !data ||
       (origin > (CURLH_HEADER | CURLH_TRAILER | CURLH_CONNECT | CURLH_1XX |
                  CURLH_PSEUDO)) || !origin || (request < -1)) {
      hresult = CURLHE_BAD_ARGUMENT;
      goto out;
    }
    if(!Curl_llist_count(&data->state.httphdrs)) {
      hresult = CURLHE_NOHEADERS; /* no headers available */
      goto out;
    }
    if(request > data->state.requests) {
      hresult = CURLHE_NOREQUEST;
      goto out;
    }
    if(request == -1)
      request = data->state.requests;

    /* we need a first round to count amount of this header */
    for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) {
      hs = Curl_node_elem(e);
      if(curl_strequal(hs->name, name) &&
         (hs->type & origin) &&
         (hs->request == request)) {
        amount++;
        pick = hs;
        e_pick = e;
      }
    }
    if(!amount)
      hresult = CURLHE_MISSING;
    else if(nameindex >= amount)
      hresult = CURLHE_BADINDEX;
    if(hresult)
      goto out;

    if(nameindex == amount - 1)
      /* if the last or only occurrence is what's asked for, then we know it */
      hs = pick;
    else {
      for(e = Curl_llist_head(&data->state.httphdrs); e;
          e = Curl_node_next(e)) {
        hs = Curl_node_elem(e);
        if(curl_strequal(hs->name, name) &&
           (hs->type & origin) &&
           (hs->request == request) &&
           (match++ == nameindex)) {
          e_pick = e;
          break;
        }
      }
      if(!e) { /* this should not happen */
        hresult = CURLHE_MISSING;
        goto out;
      }
    }
    /* this is the name we want */
    copy_header_external(hs, nameindex, amount, e_pick,
                         &data->state.headerout[0]);
    *hout = &data->state.headerout[0];
    hresult = CURLHE_OK;
  }
out:
  CURL_EAPI_LEAVE(&guard);
  if(result)
    hresult = Curl_eapi_hcode(result);
  return hresult;
}

/* public API */
struct curl_header *curl_easy_nextheader(CURL *curl,
                                         unsigned int origin,
                                         int request,
                                         struct curl_header *prev)
{
  struct Curl_eapi_guard guard;
  struct curl_header *hd = NULL;
  CURLcode result;

  if(CURL_EAPI_ENTER(&guard, curl, easy_nextheader, &result)) {
    struct Curl_easy *data = curl;
    struct Curl_llist_node *pick;
    struct Curl_llist_node *e;
    struct Curl_header_store *hs;
    size_t amount = 0;
    size_t index = 0;

    if(request > data->state.requests)
      goto out;
    if(request == -1)
      request = data->state.requests;

    if(prev) {
      pick = prev->anchor;
      if(!pick)
        /* something is wrong */
        goto out;
      pick = Curl_node_next(pick);
    }
    else
      pick = Curl_llist_head(&data->state.httphdrs);

    if(pick) {
      /* make sure it is the next header of the desired type */
      do {
        hs = Curl_node_elem(pick);
        if((hs->type & origin) && (hs->request == request))
          break;
        pick = Curl_node_next(pick);
      } while(pick);
    }

    if(!pick)
      /* no more headers available */
      goto out;

    hs = Curl_node_elem(pick);

    /* count number of occurrences of this name within the mask and figure out
       the index for the currently selected entry */
    for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) {
      struct Curl_header_store *check = Curl_node_elem(e);
      if(curl_strequal(hs->name, check->name) &&
         (check->request == request) &&
         (check->type & origin))
        amount++;
      if(e == pick)
        index = amount - 1;
    }

    copy_header_external(hs, index, amount, pick,
                         &data->state.headerout[1]);
    hd = &data->state.headerout[1];
  }
out:
  CURL_EAPI_LEAVE(&guard);
  return hd;
}

static CURLcode namevalue(char *header, size_t hlen, unsigned int type,
                          char **name, char **value)
{
  char *end = header + hlen - 1; /* point to the last byte */
  DEBUGASSERT(hlen);
  *name = header;

  if(type == CURLH_PSEUDO) {
    if(*header != ':')
      return CURLE_BAD_FUNCTION_ARGUMENT;
    header++;
  }

  /* Find the end of the header name */
  while(*header && (*header != ':'))
    ++header;

  if(*header)
    /* Skip over colon, null it */
    *header++ = 0;
  else
    return CURLE_BAD_FUNCTION_ARGUMENT;

  /* skip all leading blank letters */
  while(ISBLANK(*header))
    header++;

  *value = header;

  /* skip all trailing space letters */
  while((end > header) && ISBLANK(*end))
    *end-- = 0; /* null-terminate */
  return CURLE_OK;
}

/*
 * Curl_headers_push() gets passed a full HTTP header to store. It gets called
 * immediately before the header callback. The header is CRLF, CR or LF
 * terminated.
 */
CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
                           size_t hlen, /* length of header */
                           unsigned char type)
{
  char *value = NULL;
  char *name = NULL;
  struct Curl_header_store *hs;
  CURLcode result = CURLE_OUT_OF_MEMORY;
  const size_t ilen = hlen;

  if((header[0] == '\r') || (header[0] == '\n'))
    /* ignore the body separator */
    return CURLE_OK;

  /* trim off newline characters */
  if(hlen && (header[hlen - 1] == '\n'))
    hlen--;
  if(hlen && (header[hlen - 1] == '\r'))
    hlen--;
  if(hlen == ilen)
    /* neither CR nor LF as terminator is not a valid header */
    return CURLE_WEIRD_SERVER_REPLY;

  if(ISBLANK(header[0])) {
    /* pass leading blanks */
    while(hlen && ISBLANK(*header)) {
      header++;
      hlen--;
    }
    if(!hlen)
      return CURLE_WEIRD_SERVER_REPLY;
  }
  if(Curl_llist_count(&data->state.httphdrs) >= MAX_HTTP_RESP_HEADER_COUNT) {
    failf(data, "Too many response headers, %d is max",
          MAX_HTTP_RESP_HEADER_COUNT);
    return CURLE_TOO_LARGE;
  }

  hs = curlx_calloc(1, sizeof(*hs) + hlen);
  if(!hs)
    return CURLE_OUT_OF_MEMORY;
  memcpy(hs->buffer, header, hlen);
  hs->buffer[hlen] = 0; /* null-terminate */

  result = namevalue(hs->buffer, hlen, type, &name, &value);
  if(!result) {
    hs->name = name;
    hs->value = value;
    hs->type = type;
    hs->request = data->state.requests;

    /* insert this node into the list of headers */
    Curl_llist_append(&data->state.httphdrs, hs, &hs->node);
  }
  else {
    failf(data, "Invalid response header");
    curlx_free(hs);
  }
  return result;
}

/*
 * Curl_headers_reset(). Reset the headers subsystem.
 */
static void headers_reset(struct Curl_easy *data)
{
  Curl_llist_init(&data->state.httphdrs, NULL);
}

struct hds_cw_collect_ctx {
  struct Curl_cwriter super;
};

static CURLcode hds_cw_collect_write(struct Curl_easy *data,
                                     struct Curl_cwriter *writer, int type,
                                     const char *buf, size_t blen)
{
  if((type & CLIENTWRITE_HEADER) && !(type & CLIENTWRITE_STATUS)) {
    unsigned char htype = (unsigned char)
      (type & CLIENTWRITE_CONNECT ? CURLH_CONNECT :
       (type & CLIENTWRITE_1XX ? CURLH_1XX :
        (type & CLIENTWRITE_TRAILER ? CURLH_TRAILER :
         CURLH_HEADER)));
    CURLcode result = Curl_headers_push(data, buf, blen, htype);
    CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d",
                   htype, blen, (int)result);
    if(result)
      return result;
  }
  return Curl_cwriter_write(data, writer->next, type, buf, blen);
}

static const struct Curl_cwtype hds_cw_collect = {
  "hds-collect",
  NULL,
  0,
  Curl_cwriter_def_init,
  hds_cw_collect_write,
  Curl_cwriter_def_flush,
  Curl_cwriter_def_close,
  sizeof(struct hds_cw_collect_ctx)
};

CURLcode Curl_headers_init(struct Curl_easy *data)
{
  struct Curl_cwriter *writer;
  CURLcode result;

  if(data->conn && (data->conn->scheme->protocol & PROTO_FAMILY_HTTP)) {
    /* avoid installing it twice */
    if(Curl_cwriter_get_by_name(data, hds_cw_collect.name))
      return CURLE_OK;

    result = Curl_cwriter_create(&writer, data, &hds_cw_collect,
                                 CURL_CW_PROTOCOL);
    if(result)
      return result;

    result = Curl_cwriter_add(data, writer);
    if(result) {
      Curl_cwriter_free(data, writer);
      return result;
    }
  }
  return CURLE_OK;
}

/*
 * Curl_headers_cleanup(). Free all stored headers and associated memory.
 */
CURLcode Curl_headers_cleanup(struct Curl_easy *data)
{
  struct Curl_llist_node *e;
  struct Curl_llist_node *n;

  for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) {
    struct Curl_header_store *hs = Curl_node_elem(e);
    n = Curl_node_next(e);
    curlx_free(hs);
  }
  headers_reset(data);
  return CURLE_OK;
}

#else /* HTTP-disabled builds below */

CURLHcode curl_easy_header(CURL *easy,
                           const char *name,
                           size_t index,
                           unsigned int origin,
                           int request,
                           struct curl_header **hout)
{
  (void)easy;
  (void)name;
  (void)index;
  (void)origin;
  (void)request;
  (void)hout;
  return CURLHE_NOT_BUILT_IN;
}

struct curl_header *curl_easy_nextheader(CURL *easy,
                                         unsigned int type,
                                         int request,
                                         struct curl_header *prev)
{
  (void)easy;
  (void)type;
  (void)request;
  (void)prev;
  return NULL;
}
#endif
