<commit_before>/*
 * Part of HTTPP.
 *
 * Distributed under the 3-clause BSD licence (See LICENCE.TXT file at the
 * project root).
 *
 * Copyright (c) 2014 <NAME>.  All rights reserved.
 *
 */

#include "Connection.hpp"

#include <curl/curl.h>
#include <memory>
#include <iostream>
#include <vector>

#include <boost/thread/future.hpp>
#include <boost/log/trivial.hpp>
#include <boost/asio.hpp>
#include <boost/algorithm/string.hpp>

#include "httpp/http/Protocol.hpp"
#include "httpp/http/client/Request.hpp"
#include "httpp/http/client/Response.hpp"

namespace HTTPP {
namespace HTTP {
namespace client {

namespace detail {

Connection::Connection(boost::asio::io_service& service)
: handle(curl_easy_init())
, socket(service)
{
    if (!handle)
    {
        throw std::runtime_error("Cannot initialize curl handle");
    }
}

Connection::~Connection()
{
    if (http_headers)
    {
        curl_slist_free_all(http_headers);
    }

    if (handle)
    {
        curl_easy_cleanup(handle);
    }

    if (!result_notified)
    {
        BOOST_LOG_TRIVIAL(error) << "Destroy a not completed connection";
        complete(std::make_exception_ptr(
            std::runtime_error("Destroy a non completed connection")));
    }
}

void Connection::init()
{

    if (!result_notified)
    {
        throw std::runtime_error("Recycle a not completed connection");
    }

    curl_easy_reset(handle);

    conn_setopt(CURLOPT_HEADERFUNCTION, &writeHd);
    conn_setopt(CURLOPT_WRITEHEADER, this);

    conn_setopt(CURLOPT_WRITEFUNCTION, &writefn);
    conn_setopt(CURLOPT_WRITEDATA, this);
    conn_setopt(CURLOPT_ERRORBUFFER, this->error_buffer);
    conn_setopt(CURLOPT_PRIVATE, this);
    conn_setopt(CURLOPT_OPENSOCKETDATA, this);
    conn_setopt(CURLOPT_OPENSOCKETFUNCTION, &opensocket);

    conn_setopt(CURLOPT_ERRORBUFFER, this->error_buffer);
    conn_setopt(CURLOPT_PRIVATE, this);

    if (http_headers)
    {
        curl_slist_free_all(http_headers);
        http_headers = nullptr;
    }

    expect_continue = false;
    header.clear();
    buffer.clear();

    promise = boost::promise<client::Response>();
}

Connection::ConnectionPtr
Connection::createConnection(boost::asio::io_service& service)
{
    return ConnectionPtr(new Connection(service));
}

size_t Connection::writeHd(char* buffer, size_t size, size_t nmemb, void* userdata)
{
    Connection* conn = (Connection*)userdata;
    auto actual_size = size * nmemb;
    conn->header.insert(std::end(conn->header), buffer, buffer + actual_size);
    return actual_size;
}

size_t Connection::writefn(char* buffer, size_t size, size_t nmemb, void* userdata)
{
    Connection* conn = (Connection*)userdata;

    auto actual_size = size * nmemb;
    conn->buffer.insert(std::end(conn->buffer), buffer, buffer + actual_size);
    return actual_size;
}

curl_socket_t Connection::opensocket(void* clientp,
                                        curlsocktype purpose,
                                        struct curl_sockaddr* address)
{
    Connection* conn = (Connection*)clientp;

    if (purpose == CURLSOCKTYPE_IPCXN)
    {
        boost::system::error_code ec;

        {
            // if a connection is recycled but the remote endpoint close
            // the connection we can't use the CLOSESOCKET hook since the
            // curl_multi_cleanup when cleaning the connection cache can
            // call it even if the Connection has been destroyed. Since the
            // curl_multi_remove_handle has been called, it is certainly a
            // bug in curl.

            boost::system::error_code ec;
            conn->socket.close(ec);
        }

        if (address->family == AF_INET)
        {
            conn->socket.open(boost::asio::ip::tcp::v4(), ec);
        }
        else
        {
            conn->socket.open(boost::asio::ip::tcp::v6(), ec);
        }

        if (ec)
        {
            BOOST_LOG_TRIVIAL(error)
                << "Cannot open a socket: " << ec.message();
            return CURL_SOCKET_BAD;
        }

        return conn->socket.native_handle();
    }

    return CURL_SOCKET_BAD;
}

void Connection::configureRequest(HTTPP::HTTP::Method method)
{

    switch (method)
    {
     case HTTPP::HTTP::Method::GET:
        conn_setopt(CURLOPT_HTTPGET, 1L);
        break;
     case HTTPP::HTTP::Method::POST:
        conn_setopt(CURLOPT_POST, 1L);
        break;
     case HTTPP::HTTP::Method::PUT:
        conn_setopt(CURLOPT_PUT, 1L);
        break;
     case HTTPP::HTTP::Method::HEAD:
        conn_setopt(CURLOPT_NOBODY, 1L);
        break;

     case HTTPP::HTTP::Method::DELETE_:
     case HTTPP::HTTP::Method::OPTIONS:<commit_msg>Use put as a custom method (avoid expect: 100-continue + chunked request)<commit_after>/*
 * Part of HTTPP.
 *
 * Distributed under the 3-clause BSD licence (See LICENCE.TXT file at the
 * project root).
 *
 * Copyright (c) 2014 <NAME>.  All rights reserved.
 *
 */

#include "Connection.hpp"

#include <curl/curl.h>
#include <memory>
#include <iostream>
#include <vector>

#include <boost/thread/future.hpp>
#include <boost/log/trivial.hpp>
#include <boost/asio.hpp>
#include <boost/algorithm/string.hpp>

#include "httpp/http/Protocol.hpp"
#include "httpp/http/client/Request.hpp"
#include "httpp/http/client/Response.hpp"

namespace HTTPP {
namespace HTTP {
namespace client {

namespace detail {

Connection::Connection(boost::asio::io_service& service)
: handle(curl_easy_init())
, socket(service)
{
    if (!handle)
    {
        throw std::runtime_error("Cannot initialize curl handle");
    }
}

Connection::~Connection()
{
    if (http_headers)
    {
        curl_slist_free_all(http_headers);
    }

    if (handle)
    {
        curl_easy_cleanup(handle);
    }

    if (!result_notified)
    {
        BOOST_LOG_TRIVIAL(error) << "Destroy a not completed connection";
        complete(std::make_exception_ptr(
            std::runtime_error("Destroy a non completed connection")));
    }
}

void Connection::init()
{

    if (!result_notified)
    {
        throw std::runtime_error("Recycle a not completed connection");
    }

    curl_easy_reset(handle);

    conn_setopt(CURLOPT_HEADERFUNCTION, &writeHd);
    conn_setopt(CURLOPT_WRITEHEADER, this);

    conn_setopt(CURLOPT_WRITEFUNCTION, &writefn);
    conn_setopt(CURLOPT_WRITEDATA, this);
    conn_setopt(CURLOPT_ERRORBUFFER, this->error_buffer);
    conn_setopt(CURLOPT_PRIVATE, this);
    conn_setopt(CURLOPT_OPENSOCKETDATA, this);
    conn_setopt(CURLOPT_OPENSOCKETFUNCTION, &opensocket);

    conn_setopt(CURLOPT_ERRORBUFFER, this->error_buffer);
    conn_setopt(CURLOPT_PRIVATE, this);

    if (http_headers)
    {
        curl_slist_free_all(http_headers);
        http_headers = nullptr;
    }

    expect_continue = false;
    header.clear();
    buffer.clear();

    promise = boost::promise<client::Response>();
}

Connection::ConnectionPtr
Connection::createConnection(boost::asio::io_service& service)
{
    return ConnectionPtr(new Connection(service));
}

size_t Connection::writeHd(char* buffer, size_t size, size_t nmemb, void* userdata)
{
    Connection* conn = (Connection*)userdata;
    auto actual_size = size * nmemb;
    conn->header.insert(std::end(conn->header), buffer, buffer + actual_size);
    return actual_size;
}

size_t Connection::writefn(char* buffer, size_t size, size_t nmemb, void* userdata)
{
    Connection* conn = (Connection*)userdata;

    auto actual_size = size * nmemb;
    conn->buffer.insert(std::end(conn->buffer), buffer, buffer + actual_size);
    return actual_size;
}

curl_socket_t Connection::opensocket(void* clientp,
                                        curlsocktype purpose,
                                        struct curl_sockaddr* address)
{
    Connection* conn = (Connection*)clientp;

    if (purpose == CURLSOCKTYPE_IPCXN)
    {
        boost::system::error_code ec;

        {
            // if a connection is recycled but the remote endpoint close
            // the connection we can't use the CLOSESOCKET hook since the
            // curl_multi_cleanup when cleaning the connection cache can
            // call it even if the Connection has been destroyed. Since the
            // curl_multi_remove_handle has been called, it is certainly a
            // bug in curl.

            boost::system::error_code ec;
            conn->socket.close(ec);
        }

        if (address->family == AF_INET)
        {
            conn->socket.open(boost::asio::ip::tcp::v4(), ec);
        }
        else
        {
            conn->socket.open(boost::asio::ip::tcp::v6(), ec);
        }

        if (ec)
        {
            BOOST_LOG_TRIVIAL(error)
                << "Cannot open a socket: " << ec.message();
            return CURL_SOCKET_BAD;
        }

        return conn->socket.native_handle();
    }

    return CURL_SOCKET_BAD;
}

void Connection::configureRequest(HTTPP::HTTP::Method method)
{

    switch (method)
    {
     case HTTPP::HTTP::Method::GET:
        conn_setopt(CURLOPT_HTTPGET, 1L);
        break;
     case HTTPP::HTTP::Method::POST:
        conn_setopt(CURLOPT_POST, 1L);
        break;
     case HTTPP::HTTP::Method::HEAD:
        conn_setopt(CURLOPT_NOBODY, 1L);
        break;

     case HTTPP::HTTP::Method::PUT:
     case HTTPP::HTTP::Method::DELETE_:
     case HTTPP::HTTP::Method::OPTIONS:
{'fraction_non_alphanumeric': 0.11884586506489328, 'fraction_numerical': 0.0025742786656655584, 'mean_word_length': 2.9143576826196473, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '946912', 'n_tokens_mistral': 2989, 'n_tokens_neox': 2898, 'n_words': 606}