/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "net.h"
#include "str.h"
#include "hash.h"
#include "llist.h"
#include "array.h"
#include "ioloop.h"
#include "istream.h"
#include "istream-timeout.h"
#include "ostream.h"
#include "time-util.h"
#include "file-lock.h"
#include "iostream-rawlog.h"
#include "iostream-ssl.h"
#include "http-response-parser.h"
#include "http-client-private.h"
/*
* Connection
*/
static inline void
struct http_client_request *req)
{
}
static inline bool
struct http_client_request **_req)
{
return http_client_request_unref(_req);
}
static inline void
const char *reason)
{
}
unsigned int
{
return pending_count;
}
{
}
{
return FALSE;
return TRUE;
}
static void
{
return;
if (set->no_auto_retry) {
} else {
}
/* drop reference from connection */
continue;
/* retry the request, which may drop it */
if (set->no_auto_retry)
else
}
}
}
static void
{
/* drop reference from connection */
continue;
/* resubmit the request, which may drop it */
}
}
static void
{
/* drop reference from connection */
continue;
/* drop request if not already aborted */
}
}
static void
{
/* drop reference from connection */
continue;
/* drop request if not already aborted */
"Aborting");
}
}
/* drop reference from connection */
/* drop request if not already aborted */
"Aborting");
}
}
}
static const char *
{
unsigned int connected_msecs;
} else {
}
}
}
static void
{
"Aborting connection with temporary error: %s", error);
}
const char *error)
{
const char *sslerr;
error = "Connection lost";
else
}
/* this isn't really a "connection lost", but that we
don't trust the remote's SSL certificate. don't
retry. */
return;
}
}
}
struct http_client_connection *conn)
{
t_strdup_printf("write(%s) failed: %s",
} else {
"Remote disconnected");
}
}
{
int ret;
if (conn->in_req_callback) {
/* this can happen when a nested ioloop is created inside request
callback. we currently don't reuse connections that are occupied
this way, but theoretically we could, although that would add
quite a bit of complexity.
*/
return 0;
}
return 0;
/* Active ioloop is different from what we saw earlier;
we may have missed a disconnection event on this connection.
Verify status by reading from connection. */
t_strdup_printf("read(%s) failed: %s",
stream_errno != 0 ?
"EOF"));
return -1;
}
/* we may have read some data */
}
return 1;
}
static void
{
return;
break;
}
}
if (conn->connect_succeeded)
}
static void
{
/* cannot get here unless connection was established at some point */
}
{
return;
}
/* set timeout for this connection */
/* instant death for (urgent) connections above limit */
timeout = 0;
} else {
/* kill duplicate connections quicker;
linearly based on the number of connections */
}
"Lost peer; going idle (timeout = %u msecs)",
timeout);
} else {
}
}
{
return;
}
/* timeout already set */
return;
}
!conn->in_req_callback &&
set->max_idle_time_msecs > 0) {
/* set timeout for this connection */
/* instant death for (urgent) connections above limit */
timeout = 0;
} else {
/* kill duplicate connections quicker;
linearly based on the number of connections */
}
"No more requests queued; going idle (timeout = %u msecs)",
timeout);
}
}
static void
{
break;
}
}
}
struct http_client_peer *peer)
{
}
}
static void
{
HTTP_CLIENT_REQUEST_ERROR_TIMED_OUT, "Request timed out");
}
struct http_client_connection *conn)
{
unsigned int timeout_msecs =
return;
}
if (timeout_msecs == 0)
;
else {
}
}
struct http_client_connection *conn)
{
}
struct http_client_connection *conn)
{
}
static void
{
unsigned int wait_count;
"Expected 100-continue response timed out; sending payload anyway");
}
{
bool pipelined;
int ret;
if (ret == 0) {
}
return ret;
}
/* claim request, but no urgent request can be second in line */
return 0;
if (pshared->no_payload_sync)
/* add request to wait list and add a reference */
return -1;
if (req->connect_tunnel)
/* RFC 7231, Section 5.1.1: Expect
o A client that sends a 100-continue expectation is not required to
wait for any specific length of time; such a client MAY proceed to
send the message body even if it has not yet received a response.
Furthermore, since 100 (Continue) responses cannot be sent through
an HTTP/1.0 intermediary, such a client SHOULD NOT wait for an
indefinite period before sending the message body.
*/
}
return 1;
}
{
(struct http_client_connection *)_conn;
const char *error;
unsigned int msecs;
switch (_conn->disconnect_reason) {
"connect(%s) failed: Connection timed out in %u.%03u secs",
} else {
"SSL handshaking with %s failed: Connection timed out in %u.%03u secs",
}
break;
if (conn->connect_failed) {
break;
}
return;
default:
break;
}
}
{
}
static void
{
if (conn->close_indicated) {
return;
}
}
{
"Response payload stream destroyed (%u ms after initial response)",
/* caller is allowed to change the socket fd to blocking while reading
the payload. make sure here that it's switched back. */
/* drop reference from connection */
/* finish request if not already aborted */
}
/* input stream may have pending input. make sure input handler
gets called (but don't do it directly, since we get get here
somewhere from the API user's code, which we can't really know what
state it is in). this call also triggers sending a new request if
necessary. */
if (!conn->disconnected) {
}
/* room for new requests */
if (http_client_connection_check_ready(conn) > 0)
}
{
return;
/* payload already gone */
return;
}
/* destroy the payload, so that the timeout istream is closed */
/* not going to happen, but check for it anyway */
return;
}
/* the application still holds a reference to the payload stream, but it
is closed and we don't care about it anymore, so act as though it is
destroyed. */
}
static bool
struct http_client_connection *conn,
struct http_client_request *req,
struct http_response *response)
{
bool retrying;
/* wrap the stream to capture the destroy event without
destroying the actual payload stream. we are already expected
to be on the correct ioloop, so there should be no need to
switch the stream's ioloop here. */
req);
/* the callback may add its own I/O, so we need to remove
our one before calling it */
/* we've received the request itself, and we can't reset the
timeout during the payload reading. */
}
if (conn->disconnected) {
/* the callback managed to get this connection disconnected */
if (!retrying)
return FALSE;
}
if (retrying) {
/* retrying, don't destroy the request */
}
return http_client_connection_unref(&conn);
}
/* maintain request reference while payload is pending */
/* request is dereferenced in payload destroy callback */
/* already finished reading the payload */
}
} else {
}
return http_client_connection_unref(&conn);
}
return FALSE;
}
{
(struct http_client_connection *)_conn;
unsigned int count;
const char *error;
/* finish SSL negotiation by reading from input stream */
break;
}
if (ret < 0) {
/* failed somehow */
"SSL handshaking with %s failed: "
"read(%s) failed: %s",
stream_errno != 0 ?
return;
}
/* not finished */
return;
}
/* ready for first request */
}
/* We came here from a timeout added by
http_client_payload_destroyed(). The IO couldn't be added
back immediately in there, because the HTTP API user may
still have had its own IO pointed to the same fd. It should
be removed by now, so we can add it back. */
finished++;
}
/* we've seen activity from the server; reset request timeout */
/* get first waiting request */
if (count > 0) {
/* determine whether to expect a response payload */
} else {
}
/* drop connection with broken output if last possible input was
received */
return;
}
while ((ret=http_response_parse_next
/* server sent response without any requests in the wait list */
"Server explicitly closed connection: 408 %s",
} else {
"Got unexpected input from server: %u %s",
}
return;
}
/* Got some response; cancel response timeout */
/* RFC 7231, Section 6.2:
A client MUST be able to parse one or more 1xx responses received
prior to a final response, even if the client does not expect one. A
user agent MAY ignore unexpected 1xx responses.
*/
if (req->payload_sync_continue) {
"Got 100-continue response after timeout");
continue;
}
"Got expected 100-continue response");
"Request aborted before sending payload was complete.");
return;
}
return;
/* ignore other 1xx for now */
continue;
} else if (!req->payload_sync &&
/* got early response from server while we're still sending request
payload. we cannot recover from this reliably, so we stop sending
payload and close the connection once the response is processed */
"Got early input from server; "
"request payload not completely sent (will close connection)");
}
"Got %u response for request %s (took %u ms + %u ms in queue)",
/* make sure connection output is unlocked if 100-continue failed */
}
/* remove request from queue */
}
if (!aborted) {
/* response cannot be 2xx if request payload was not completely sent
*/
"Server responded with success response "
"before all payload was sent");
return;
}
blocks via http_client_request_send_payload()
and we're not waiting for 100 continue */
if (!req->payload_wait ||
/* failed Expect: */
/* drop Expect: continue */
/* redirection */
/* redirect (possibly after delay) */
}
/* service unavailable */
/* automatically retry after delay if indicated */
/* request timeout (by server) */
/* automatically retry */
/* connection close is implicit, although server should indicate
that explicitly */
}
}
if (!handled) {
/* response for application */
return;
}
}
finished++;
/* server closing connection? */
if (conn->close_indicated) {
return;
}
/* get next waiting request */
if (count > 0) {
/* determine whether to expect a response payload */
} else {
/* no more requests waiting for the connection */
}
/* drop connection with broken output if last possible input was
received */
return;
}
}
if (ret <= 0 &&
t_strdup_printf("read(%s) failed: %s",
stream_errno != 0 ?
"EOF"));
return;
}
if (ret < 0) {
return;
}
if (finished > 0) {
/* connection still alive after (at least one) request;
we can pipeline -> mark for subsequent connections */
/* room for new requests */
}
}
static int
{
unsigned int count;
bool pipelined;
int ret;
return 0;
"Request aborted before sending payload was complete.");
if (count == 1) {
} else {
}
return 0;
}
return 0;
return -1;
return -1;
}
if (!conn->output_locked) {
/* room for new requests */
if (http_client_connection_check_ready(conn) > 0)
}
return 0;
}
{
int ret;
/* we've seen activity from the server; reset request timeout */
if (ret < 0)
return ret;
}
return 1;
if (http_client_connection_continue_request(conn) < 0)
return -1;
return 1;
}
void
struct http_client_tunnel *tunnel)
{
/* claim connection streams */
/* detach from connection */
}
static void
{
/* connected */
/* remove from pending list */
break;
}
}
/* indicate connection success */
/* start raw log */
}
/* direct tunneling connections handle connect requests just by providing a
raw connection */
return;
}
"No raw connect requests pending; closing useless connection");
return;
}
/* start protocol I/O */
}
static int
{
"ignoring invalid certificate: %s", error);
} else {
return -1;
}
return 0;
}
static int
const char **error_r)
{
const char *error;
}
"Couldn't initialize SSL client for %s: %s",
return -1;
}
return -1;
}
} else {
/* wait for handshake to complete; connection input handler does the rest
by reading from the input stream */
}
return 0;
}
static void
{
(struct http_client_connection *)_conn;
const char *error;
if (!success) {
} else {
if (set->socket_send_buffer_size > 0) {
set->socket_send_buffer_size) < 0)
}
if (set->socket_recv_buffer_size > 0) {
set->socket_recv_buffer_size) < 0)
}
}
return;
}
}
}
};
};
struct connection_list *
{
return connection_list_init
}
static void
{
}
{
}
static void
unsigned int timeout_msecs)
{
return;
}
/* don't use connection.h timeout because we want this timeout
to include also the SSL handshake */
if (timeout_msecs > 0) {
}
}
static void
{
unsigned int msecs;
"Tunnel connect(%s) failed: "
"Connection timed out in %u.%03u secs",
}
static void
struct http_client_connection *conn)
{
"Tunnel connect(%s) failed: %s", name,
return;
}
}
static void
unsigned int timeout_msecs)
{
/* don't use connection.h timeout because we want this timeout
to include also the SSL handshake */
if (timeout_msecs > 0) {
}
}
struct http_client_connection *
{
static unsigned int id = 0;
unsigned int timeout_msecs;
conn_type = "HTTP";
break;
conn_type = "HTTPS";
break;
conn_type = "Tunneled HTTPS";
break;
conn_type = "Raw";
break;
conn_type = "Unix";
break;
}
if (timeout_msecs == 0)
break;
break;
default:
}
"%s connection created (%d parallel connections exist)%s",
return conn;
}
{
}
static void
{
if (conn->disconnected)
return;
/* the stream is still accessed by lib-http caller. */
}
if (conn->connect_initialized)
/* remove this connection from the lists */
break;
}
}
break;
}
}
}
{
return TRUE;
if (conn->connect_initialized)
return FALSE;
}
{
}
{
if (conn->connect_initialized)
}
conn->to_requests =
}
conn->to_connect =
}
}
}
conn->to_response =
}
}