// This file is part of the Boost Network library
// Based on the Pion Network Library (r421)
// Copyright Atomic Labs, Inc. 2007-2008
// See http://cpp-netlib.sourceforge.net for library home page.
//
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_NETWORK_PROTOCOL_HTTP_MESSAGE_IPP
#define BOOST_NETWORK_PROTOCOL_HTTP_MESSAGE_IPP

#include <boost/network/protocol/http/message.hpp>
#include <boost/lexical_cast.hpp>
#include <cstdlib>
#include <cstdio>

namespace boost {
namespace network {
namespace http {

// static member functions of boost::network::http::message

template <typename Tag>
typename message_impl<Tag>::string_type const message_impl<Tag>::url_decode(
    typename message_impl<Tag>::string_type const &str) {
  char decode_buf[3];
  typename message_impl<Tag>::string_type result;
  result.reserve(str.size());

  for (typename message_impl<Tag>::string_type::size_type pos = 0;
       pos < str.size(); ++pos) {
    switch (str[pos]) {
      case '+':
        // convert to space character
        result += ' ';
        break;
      case '%':
        // decode hexidecimal value
        if (pos + 2 < str.size()) {
          decode_buf[0] = str[++pos];
          decode_buf[1] = str[++pos];
          decode_buf[2] = '\0';
          result += static_cast<char>(strtol(decode_buf, 0, 16));
        } else {
          // recover from error by not decoding character
          result += '%';
        }
        break;
      default:
        // character does not need to be escaped
        result += str[pos];
    }
  };

  return result;
}

template <typename Tag>
typename message_impl<Tag>::string_type const message_impl<Tag>::url_encode(
    typename message_impl<Tag>::string_type const &str) {
  char encode_buf[4];
  typename message_impl<Tag>::string_type result;
  encode_buf[0] = '%';
  result.reserve(str.size());

  // character selection for this algorithm is based on the following url:
  // http://www.blooberry.com/indexdot/html/topics/urlencoding.htm

  for (typename message_impl<Tag>::string_type::size_type pos = 0;
       pos < str.size(); ++pos) {
    switch (str[pos]) {
      default:
        if (str[pos] >= 32 && str[pos] < 127) {
          // character does not need to be escaped
          result += str[pos];
          break;
        }
      // else pass through to next case

      case '$':
      case '&':
      case '+':
      case ',':
      case '/':
      case ':':
      case ';':
      case '=':
      case '?':
      case '@':
      case '"':
      case '<':
      case '>':
      case '#':
      case '%':
      case '{':
      case '}':
      case '|':
      case '\\':
      case '^':
      case '~':
      case '[':
      case ']':
      case '`':
        // the character needs to be encoded
        sprintf(encode_buf + 1, "%02X", str[pos]);
        result += encode_buf;
        break;
    }
  };

  return result;
}

template <typename Tag>
typename message_impl<Tag>::string_type const
message_impl<Tag>::make_query_string(
    typename query_container<Tag>::type const &query_params) {
  typename message_impl<Tag>::string_type query_string;
  for (typename query_container<Tag>::type::const_iterator i =
           query_params.begin();
       i != query_params.end(); ++i) {
    if (i != query_params.begin()) query_string += '&';
    query_string += url_encode(i->first);
    query_string += '=';
    query_string += url_encode(i->second);
  }
  return query_string;
}

template <typename Tag>
typename message_impl<Tag>::string_type const
message_impl<Tag>::make_set_cookie_header(
    typename message_impl<Tag>::string_type const &name,
    typename message_impl<Tag>::string_type const &value,
    typename message_impl<Tag>::string_type const &path, bool const has_max_age,
    unsigned long const max_age) {
  typename message_impl<Tag>::string_type set_cookie_header(name);
  set_cookie_header += "=\"";
  set_cookie_header += value;
  set_cookie_header += "\"; Version=\"1\"";
  if (!path.empty()) {
    set_cookie_header += "; Path=\"";
    set_cookie_header += path;
    set_cookie_header += '\"';
  }
  if (has_max_age) {
    set_cookie_header += "; Max-Age=\"";
    set_cookie_header +=
        boost::lexical_cast<message_impl<Tag>::string_type>(max_age);
    set_cookie_header += '\"';
  }
  return set_cookie_header;
}

template <typename Tag>
bool message_impl<Tag>::base64_decode(
    const typename message_impl<Tag>::string_type &input,
    typename message_impl<Tag>::string_type &output) {
  static const char nop = -1;
  static const char decoding_data[] = {
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, 62,  nop,
      nop, nop, 63,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  nop, nop,
      nop, nop, nop, nop, nop, 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
      10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,
      25,  nop, nop, nop, nop, nop, nop, 26,  27,  28,  29,  30,  31,  32,  33,
      34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
      49,  50,  51,  nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop,
      nop};

  unsigned int input_length = input.size();
  const char *input_ptr = input.data();

  // allocate space for output string
  output.clear();
  output.reserve(((input_length + 2) / 3) * 4);

  // for each 4-bytes sequence from the input, extract 4 6-bits sequences
  // by droping first two bits
  // and regenerate into 3 8-bits sequence

  for (unsigned int i = 0; i < input_length; i++) {
    char base64code0;
    char base64code1;
    char base64code2 = 0;  // initialized to 0 to suppress warnings
    char base64code3;

    base64code0 = decoding_data[static_cast<int>(input_ptr[i])];
    if (base64code0 == nop)  // non base64 character
      return false;
    if (!(++i < input_length))  // we need at least two input bytes for
                                // first byte output
      return false;
    base64code1 = decoding_data[static_cast<int>(input_ptr[i])];
    if (base64code1 == nop)  // non base64 character
      return false;

    output += ((base64code0 << 2) | ((base64code1 >> 4) & 0x3));

    if (++i < input_length) {
      char c = input_ptr[i];
      if (c == '=') {  // padding , end of input
        BOOST_ASSERT((base64code1 & 0x0f) == 0);
        return true;
      }
      base64code2 = decoding_data[static_cast<int>(input_ptr[i])];
      if (base64code2 == nop)  // non base64 character
        return false;

      output += ((base64code1 << 4) & 0xf0) | ((base64code2 >> 2) & 0x0f);
    }

    if (++i < input_length) {
      char c = input_ptr[i];
      if (c == '=') {  // padding , end of input
        BOOST_ASSERT((base64code2 & 0x03) == 0);
        return true;
      }
      base64code3 = decoding_data[static_cast<int>(input_ptr[i])];
      if (base64code3 == nop)  // non base64 character
        return false;

      output += (((base64code2 << 6) & 0xc0) | base64code3);
    }
  }

  return true;
}

template <typename Tag>
bool message_impl<Tag>::base64_encode(
    typename message_impl<Tag>::string_type const &input,
    typename message_impl<Tag>::string_type &output) {
  static const char encoding_data[] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

  unsigned int input_length = input.size();
  const char *input_ptr = input.data();

  // allocate space for output string
  output.clear();
  output.reserve(((input_length + 2) / 3) * 4);

  // for each 3-bytes sequence from the input, extract 4 6-bits sequences
  // and encode using
  // encoding_data lookup table.
  // if input do not contains enough chars to complete 3-byte sequence,use
  // pad char '='
  for (unsigned int i = 0; i < input_length; i++) {
    int base64code0 = 0;
    int base64code1 = 0;
    int base64code2 = 0;
    int base64code3 = 0;

    base64code0 = (input_ptr[i] >> 2) & 0x3f;  // 1-byte 6 bits
    output += encoding_data[base64code0];
    base64code1 = (input_ptr[i] << 4) & 0x3f;  // 1-byte 2 bits +

    if (++i < input_length) {
      base64code1 |= (input_ptr[i] >> 4) & 0x0f;  // 2-byte 4 bits
      output += encoding_data[base64code1];
      base64code2 = (input_ptr[i] << 2) & 0x3f;  // 2-byte 4 bits +

      if (++i < input_length) {
        base64code2 |= (input_ptr[i] >> 6) & 0x03;  // 3-byte 2 bits
        base64code3 = input_ptr[i] & 0x3f;          // 3-byte 6 bits
        output += encoding_data[base64code2];
        output += encoding_data[base64code3];
      } else {
        output += encoding_data[base64code2];
        output += '=';
      }
    } else {
      output += encoding_data[base64code1];
      output += '=';
      output += '=';
    }
  }

  return true;
}

}  // namespace http

}  // namespace network

}  // namespace boost

#endif  // BOOST_NETWORK_PROTOCOL_HTTP_MESSAGE_HPP
