<commit_before>/*
 * Copyright 2017-2019 AVSystem <<EMAIL>>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <avs_commons_config.h>

#include <assert.h>
#include <string.h>

#include <avsystem/commons/errno.h>
#include <avsystem/commons/memory.h>
#include <avsystem/commons/stream/stream_net.h>

#include "chunked.h"
#include "client.h"
#include "headers.h"
#include "http_log.h"
#include "http_stream.h"

VISIBILITY_SOURCE_BEGIN

#define HTTP_MOVE_LIMIT 5

#ifdef AVS_UNIT_TESTING
#define avs_net_socket_create avs_net_socket_create_TEST_WRAPPER
int avs_net_socket_create_TEST_WRAPPER(avs_net_abstract_socket_t **socket,
                                       avs_net_socket_type_t type,
                                       ...);
#endif

static const char *default_port_for_protocol(const char *protocol) {
    if (strcmp(protocol, "http") == 0) {
        return "80";
    } else if (strcmp(protocol, "https") == 0) {
        return "443";
    } else {
        LOG(WARNING, "unknown protocol '%s'", protocol);
        return "";
    }
}

static const char *resolve_port(const avs_url_t *parsed_url) {
    const char *port = avs_url_port(parsed_url);
    if (port) {
        return port;
    } else {
        return default_port_for_protocol(avs_url_protocol(parsed_url));
    }
}

avs_errno_t _avs_http_socket_new(avs_net_abstract_socket_t **out,
                                 avs_http_t *client,
                                 const avs_url_t *url) {
    avs_errno_t result = AVS_NO_ERROR;
    avs_net_ssl_configuration_t ssl_config_full;
    LOG(TRACE, "http_new_socket");
    assert(out != NULL);
    *out = NULL;
    if (client->ssl_configuration) {
        ssl_config_full = *client->ssl_configuration;
    } else {
        memset(&ssl_config_full, 0, sizeof(ssl_config_full));
    }
    if (client->tcp_configuration) {
        ssl_config_full.backend_configuration = *client->tcp_configuration;
    }
    const char *protocol = avs_url_protocol(url);
    if (strcmp(protocol, "http") == 0) {
        LOG(TRACE, "creating TCP socket");
        if (avs_net_socket_create(out, AVS_NET_TCP_SOCKET,
                                  &ssl_config_full.backend_configuration)) {
            result = AVS_EIO;
        }
    } else if (strcmp(protocol, "https") == 0) {
        LOG(TRACE, "creating SSL socket");
        if (avs_net_socket_create(out, AVS_NET_SSL_SOCKET, &ssl_config_full)) {
            result = AVS_EIO;
        }
    }
    if (!result) {
        assert(*out);
        LOG(TRACE, "socket OK, connecting");
        if (avs_net_socket_connect(*out, avs_url_host(url),
                                   resolve_port(url))) {
            result = avs_net_socket_errno(*out);
            if (!result) {
                result = AVS_EIO;
            }
        }
    }
    if (result) {
        avs_net_socket_cleanup(out);
        LOG(ERROR, "http_new_socket: failure: %d", (int) result);
    } else {
        LOG(TRACE, "http_new_socket: success");
    }
    return result;
}

static int reconnect_tcp_socket(avs_net_abstract_socket_t *socket,
                                const avs_url_t *url,
                                avs_errno_t *out_error_code) {
    LOG(TRACE, "reconnect_tcp_socket");
    *out_error_code = AVS_NO_ERROR;
    if (!socket || avs_net_socket_close(socket)
        || avs_net_socket_connect(socket, avs_url_host(url),
                                  resolve_port(url))) {
        LOG(ERROR, "reconnect failed");
        *out_error_code = avs_net_socket_errno(socket);
        return -1;
    }
    return 0;
}

int _avs_http_redirect(http_stream_t *stream, avs_url_t **url_move) {
    avs_net_abstract_socket_t *old_socket =
            avs_stream_net_getsock(stream->backend);
    avs_net_abstract_socket_t *new_socket = NULL;
    int result = 0;
    LOG(TRACE, "http_redirect");
    ++stream->redirect_count;
    if (stream->redirect_count > HTTP_MOVE_LIMIT) {
        LOG(ERROR, "redirect count exceeded");
        return AVS_HTTP_ERROR_TOO_MANY_REDIRECTS;
    }
    if (avs_stream_reset(stream->backend)) {
        LOG(ERROR, "stream reset failed");
        return -1;
    }
    stream->flags.close_handling_required = 0;
    _avs_http_auth_reset(&stream->auth);
    avs_net_socket_close(old_socket);

    if ((result = (int) _avs_http_socket_new(&new_socket, stream->http,
                                             *url_move))) {
        return result;
    }
    if (avs_stream_net_setsock(stream->backend, new_socket)) {
        /* error, clean new socket */
        avs_net_socket_cleanup(&new_socket);
        LOG(ERROR, "setsock failed");
        return -1;
    }
    avs_net_socket_cleanup(&old_socket);
    avs_url_free(stream->url);
    stream->url = *url_move;
    *url_move = NULL;
    stream->flags.no_expect = 0;
    stream->flags.keep_connection = 1;
    if ((stream->auth.credentials.user || stream->auth.credentials.password)
        && strcmp(avs_url_protocol(stream->url), "https") == 0) {
        stream->auth.state.flags.type = HTTP_AUTH_TYPE_BASIC;
    }
    return 0;
}

int _avs_http_prepare_for_sending(http_stream_t *stream) {
    LOG(TRACE, "http_prepare_for_sending");
    stream->flags.should_retry = 0;

    /* check stream state */
    if (stream->body_receiver) {<commit_msg>Map any error from _avs_http_socket_new to -1<commit_after>/*
 * Copyright 2017-2019 AVSystem <<EMAIL>>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <avs_commons_config.h>

#include <assert.h>
#include <string.h>

#include <avsystem/commons/errno.h>
#include <avsystem/commons/memory.h>
#include <avsystem/commons/stream/stream_net.h>

#include "chunked.h"
#include "client.h"
#include "headers.h"
#include "http_log.h"
#include "http_stream.h"

VISIBILITY_SOURCE_BEGIN

#define HTTP_MOVE_LIMIT 5

#ifdef AVS_UNIT_TESTING
#define avs_net_socket_create avs_net_socket_create_TEST_WRAPPER
int avs_net_socket_create_TEST_WRAPPER(avs_net_abstract_socket_t **socket,
                                       avs_net_socket_type_t type,
                                       ...);
#endif

static const char *default_port_for_protocol(const char *protocol) {
    if (strcmp(protocol, "http") == 0) {
        return "80";
    } else if (strcmp(protocol, "https") == 0) {
        return "443";
    } else {
        LOG(WARNING, "unknown protocol '%s'", protocol);
        return "";
    }
}

static const char *resolve_port(const avs_url_t *parsed_url) {
    const char *port = avs_url_port(parsed_url);
    if (port) {
        return port;
    } else {
        return default_port_for_protocol(avs_url_protocol(parsed_url));
    }
}

avs_errno_t _avs_http_socket_new(avs_net_abstract_socket_t **out,
                                 avs_http_t *client,
                                 const avs_url_t *url) {
    avs_errno_t result = AVS_NO_ERROR;
    avs_net_ssl_configuration_t ssl_config_full;
    LOG(TRACE, "http_new_socket");
    assert(out != NULL);
    *out = NULL;
    if (client->ssl_configuration) {
        ssl_config_full = *client->ssl_configuration;
    } else {
        memset(&ssl_config_full, 0, sizeof(ssl_config_full));
    }
    if (client->tcp_configuration) {
        ssl_config_full.backend_configuration = *client->tcp_configuration;
    }
    const char *protocol = avs_url_protocol(url);
    if (strcmp(protocol, "http") == 0) {
        LOG(TRACE, "creating TCP socket");
        if (avs_net_socket_create(out, AVS_NET_TCP_SOCKET,
                                  &ssl_config_full.backend_configuration)) {
            result = AVS_EIO;
        }
    } else if (strcmp(protocol, "https") == 0) {
        LOG(TRACE, "creating SSL socket");
        if (avs_net_socket_create(out, AVS_NET_SSL_SOCKET, &ssl_config_full)) {
            result = AVS_EIO;
        }
    }
    if (!result) {
        assert(*out);
        LOG(TRACE, "socket OK, connecting");
        if (avs_net_socket_connect(*out, avs_url_host(url),
                                   resolve_port(url))) {
            result = avs_net_socket_errno(*out);
            if (!result) {
                result = AVS_EIO;
            }
        }
    }
    if (result) {
        avs_net_socket_cleanup(out);
        LOG(ERROR, "http_new_socket: failure: %d", (int) result);
    } else {
        LOG(TRACE, "http_new_socket: success");
    }
    return result;
}

static int reconnect_tcp_socket(avs_net_abstract_socket_t *socket,
                                const avs_url_t *url,
                                avs_errno_t *out_error_code) {
    LOG(TRACE, "reconnect_tcp_socket");
    *out_error_code = AVS_NO_ERROR;
    if (!socket || avs_net_socket_close(socket)
        || avs_net_socket_connect(socket, avs_url_host(url),
                                  resolve_port(url))) {
        LOG(ERROR, "reconnect failed");
        *out_error_code = avs_net_socket_errno(socket);
        return -1;
    }
    return 0;
}

int _avs_http_redirect(http_stream_t *stream, avs_url_t **url_move) {
    avs_net_abstract_socket_t *old_socket =
            avs_stream_net_getsock(stream->backend);
    avs_net_abstract_socket_t *new_socket = NULL;
    int result = 0;
    LOG(TRACE, "http_redirect");
    ++stream->redirect_count;
    if (stream->redirect_count > HTTP_MOVE_LIMIT) {
        LOG(ERROR, "redirect count exceeded");
        return AVS_HTTP_ERROR_TOO_MANY_REDIRECTS;
    }
    if (avs_stream_reset(stream->backend)) {
        LOG(ERROR, "stream reset failed");
        return -1;
    }
    stream->flags.close_handling_required = 0;
    _avs_http_auth_reset(&stream->auth);
    avs_net_socket_close(old_socket);

    if (_avs_http_socket_new(&new_socket, stream->http, *url_move)
        != AVS_NO_ERROR) {
        return -1;
    }

    if (avs_stream_net_setsock(stream->backend, new_socket)) {
        /* error, clean new socket */
        avs_net_socket_cleanup(&new_socket);
        LOG(ERROR, "setsock failed");
        return -1;
    }
    avs_net_socket_cleanup(&old_socket);
    avs_url_free(stream->url);
    stream->url = *url_move;
    *url_move = NULL;
    stream->flags.no_expect = 0;
    stream->flags.keep_connection = 1;
    if ((stream->auth.credentials.user || stream->auth.credentials.password)
        && strcmp(avs_url_protocol(stream->url), "https") == 0) {
        stream->auth.state.flags.type = HTTP_AUTH_TYPE_BASIC;
    }
    return 0;
}

int _avs_http_prepare_for_sending(http_stream_t *stream) {
    LOG(TRACE, "http_prepare_for_sending");
    stream->flags.should_retry = 0;

    /* check stream state */
    if (stream->body_receiver) {
{'fraction_non_alphanumeric': 0.10993858662745437, 'fraction_numerical': 0.006054839546752011, 'mean_word_length': 2.672808132147395, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 16, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1421848', 'n_tokens_mistral': 3841, 'n_tokens_neox': 3783, 'n_words': 761}