http-client-request.c revision 5b6bfa39481f719c1fd6ed3febeaf7f0be9e8886
/* Copyright (c) 2013-2016 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "net.h"
#include "str.h"
#include "hash.h"
#include "array.h"
#include "llist.h"
#include "time-util.h"
#include "istream.h"
#include "ostream.h"
#include "dns-lookup.h"
#include "http-url.h"
#include "http-date.h"
#include "http-auth.h"
#include "http-response-parser.h"
#include "http-transfer.h"
#include "http-client-private.h"
const char *http_request_state_names[] = {
"new",
"queued",
"payload_out",
"waiting",
"got_response",
"payload_in",
"finished",
"aborted"
};
/*
* Logging
*/
static inline void
static inline void
const char *format, ...)
{
i_debug("http-client: request %s: %s",
}
}
/*
* Request
*/
static bool
const char *
{
}
}
static struct http_client_request *
{
static unsigned int id_counter = 0;
struct http_client_request *req;
/* default to client-wide settings: */
return req;
}
struct http_client_request *
{
struct http_client_request *req;
return req;
}
struct http_client_request *
{
struct http_client_request *req;
}
return req;
}
struct http_client_request *
void *context)
{
struct http_client_request *req;
return req;
}
struct http_client_request *
void *context)
{
struct http_client_request *req;
const char *hostname;
return req;
}
static void
{
client->requests_count++;
}
static void
{
/* only decrease pending request counter if this request was submitted */
client->requests_count--;
}
}
{
}
{
return TRUE;
/* cannot be destroyed while it is still pending */
}
return FALSE;
}
{
}
}
{
}
bool ssl)
{
}
{
}
{
}
{
/* allow calling for retries */
/* mark presence of special headers */
switch (key[0]) {
case 'a': case 'A':
break;
case 'c': case 'C':
break;
case 'd': case 'D':
break;
case 'e': case 'E':
break;
case 'h': case 'H':
break;
case 'p': case 'P':
break;
case 't': case 'T':
break;
case 'u': case 'U':
break;
}
}
const char *key)
{
const unsigned char *data, *p;
/* allow calling for retries */
/* key was found from header, replace its value */
break;
}
}
}
{
}
{
int ret;
if (ret < 0) {
i_error("i_stream_get_size(%s) failed: %s",
}
req->payload_size = 0;
}
/* prepare request payload sync using 100 Continue response from server */
}
{
unsigned char *payload_data;
if (size == 0)
return;
}
unsigned int msecs)
{
}
{
req->timeout_msecs = 0;
}
unsigned int msecs)
{
}
unsigned int max_attempts)
{
}
{
}
{
}
const char *proxy_socket)
{
}
{
}
{
}
unsigned int msecs)
{
}
const struct http_response *response)
{
unsigned int max;
return 0; /* no delay */
if (retry_after < ioloop_time)
return 0; /* delay already expired */
return -1; /* delay too long */
return 1; /* valid delay */
}
const char *
{
}
const char *
{
}
enum http_request_state
{
}
{
/* RFC 7230, Section 3.3:
The presence of a message body in a response depends on both the
request method to which it is responding and the response status code
(Section 3.1.2 of [RFC7230]). Responses to the HEAD request method
(Section 4.3.2 of [RFC7231]) never include a message body because the
associated response header fields (e.g., Transfer-Encoding,
Content-Length, etc.), if present, indicate only what their values
would have been if the request method had been GET (Section 4.3.1 of
[RFC7231]). 2xx (Successful) responses to a CONNECT request method
(Section 4.3.6 of [RFC7231]) switch to tunnel mode instead of having a
message body.
*/
}
{
struct http_client_host *host;
if (req->connect_tunnel) {
/* connect requests require authority form for request target */
} else {
/* absolute target url */
}
/* determine what host to contact to submit this request */
if (have_proxy) {
} else if (proxy_socket_path != NULL) {
} else {
}
} else {
}
/* use submission date if no date is set explicitly */
/* prepare value for Host header */
/* debug label */
/* update request target */
if (!have_proxy) {
/* if we don't have a proxy, CONNECT requests are handled by creating
the requested connection directly */
if (req->connect_direct)
}
if (req->timeout_msecs > 0) {
}
}
}
{
}
void
struct http_client_peer_addr *addr)
{
/* the IP address may be unassigned in the returned peer address, since
that is only available at this stage when the target URL has an
explicit IP address. */
if (host_socket != NULL) {
} else if (req->connect_direct) {
if (req->ssl_tunnel)
else
} else {
}
}
static void
{
/* drop payload output stream */
}
/* advance state only when request didn't get aborted in the mean time */
/* we're now waiting for a response from the server */
}
/* release connection */
}
static int
{
int ret;
} else {
}
req->payload_size = 0;
/* Wait for payload data to be written */
break;
}
}
ret = 1;
break;
ret = -1;
break;
default:
ret = 0;
break;
}
/* callback may have messed with our pointer,
so unref using local variable */
if (!http_client_request_unref(&req))
/* Return status */
return ret;
}
{
int ret;
if (ret < 0)
else {
}
return ret;
}
{
int ret;
return ret < 0 ? -1 : 0;
}
{
(void)http_client_connection_output(conn);
}
{
/* chunked ostream needs to write to the parent stream's buffer */
switch (res) {
/* finished sending */
if (!req->payload_chunked &&
return -1;
}
if (req->payload_wait) {
/* this chunk of input is finished
(client needs to act; disable timeout) */
} else {
/* finished sending payload */
}
return 0;
/* input is blocking (client needs to act; disable timeout) */
if (!pipelined)
return 0;
/* output is blocking (server needs to act; enable timeout) */
if (!pipelined)
return 0;
/* we're in the middle of sending a request, so the connection
will also have to be aborted */
/* the payload stream assigned to this request is broken,
fail this the request immediately */
"Broken payload stream");
return -1;
/* failed to send request */
return -1;
}
i_unreached();
}
{
int ret = 0;
/* create request line */
/* create special headers implicitly if not set explicitly using
http_client_request_add_header() */
if (!req->have_hdr_host) {
}
if (!req->have_hdr_date) {
}
if (!req->have_hdr_authorization &&
struct http_auth_credentials auth_creds;
}
if (http_client_request_to_proxy(req) &&
struct http_auth_credentials auth_creds;
}
}
}
if (req->payload_chunked) {
// FIXME: can't do this for a HTTP/1.0 server
if (!req->have_hdr_body_spec)
} else {
/* send Content-Length if we have specified a payload,
even if it's 0 bytes. */
if (!req->have_hdr_body_spec) {
req->payload_size);
}
}
}
if (!req->have_hdr_connection &&
Section 19.7.1:
A client MUST NOT send the Keep-Alive connection token to a proxy
server as HTTP/1.0 proxy servers do not obey the rules of HTTP/1.1
for parsing the Connection header field.
*/
}
/* request line + implicit headers */
/* explicit headers */
} else {
}
/* end of header */
ret = -1;
} else {
if (!req->payload_sync) {
ret = -1;
} else {
}
} else {
if (!pipelined)
}
ret = -1;
}
}
return ret;
}
{
int ret;
T_BEGIN {
if (ret < 0)
} T_END;
return ret;
}
struct http_response *response)
{
unsigned int total_msecs =
"%s (%u retries in %u.%03u secs)",
}
/* retrying */
return FALSE;
} else {
}
}
return TRUE;
}
static bool
{
struct http_response response;
/* retrying */
return FALSE;
} else {
}
}
return TRUE;
}
{
bool destroy;
req->delayed_error);
if (destroy)
}
{
/* we're still in http_client_request_submit() or in the callback
during a retry attempt. delay reporting the error, so the caller
doesn't have to handle immediate or nested callbacks. */
} else {
}
}
{
return;
}
{
return;
}
{
/* parse URL */
return;
}
t_strdup_printf("Redirected more than %d times",
} else {
"Redirect refused");
}
return;
}
/* rewind payload stream */
"Redirect failed: Cannot resend payload; stream is not seekable");
return;
} else {
}
}
/* drop payload output stream from previous attempt */
origin_url, target);
/* RFC 7231, Section 6.4.4:
-> A 303 `See Other' redirect status response is handled a bit differently.
Basically, the response content is located elsewhere, but the original
(POST) request is handled already.
*/
// FIXME: should we provide the means to skip this step? The original
// request was already handled at this point.
/* drop payload */
req->payload_size = 0;
req->payload_offset = 0;
}
/* resubmit */
}
{
/* rewind payload stream */
"Resubmission failed: Cannot resend payload; stream is not seekable");
return;
} else {
}
}
/* drop payload output stream from previous attempt */
}
{
if (!http_client_request_try_retry(req))
}
{
/* don't ever retry if we're sending data in small blocks via
http_client_request_send_payload() and we're not waiting for a
100 continue (there's no way to rewind the payload for a retry)
*/
if (req->payload_wait &&
return FALSE;
/* limit the number of attempts for each request */
return FALSE;
return TRUE;
}
void (*callback)(void *),
void *context)
{
}
struct http_client_tunnel *tunnel)
{
}