http-client-request.c revision 2ff548b46061f984def8d36736745333b8405a31
7cb128dc4cae2a03a742f63ba7afee23c78e3af0Phil Carmody/* Copyright (c) 2013-2015 Dovecot authors, see the included COPYING file */
1d048c5050f03c24251e5af8087e640de21b2d62Timo Sirainen "payload_out",
1d048c5050f03c24251e5af8087e640de21b2d62Timo Sirainen "got_response",
1d048c5050f03c24251e5af8087e640de21b2d62Timo Sirainen "payload_in",
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic inline void
7384b4e78eaab44693c985192276e31322155e32Stephan Boschhttp_client_request_debug(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic inline void
7384b4e78eaab44693c985192276e31322155e32Stephan Boschhttp_client_request_debug(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch const char *format, ...)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_label(req), t_strdup_vprintf(format, args));
9145c8b5eda526d05bd4a7ced20f6f6f2ff8df03Stephan Boschhttp_client_request_send_error(struct http_client_request *req,
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Boschhttp_client_request_new(struct http_client *client, const char *method,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_callback_t *callback, void *context)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch pool = pool_alloconly_create("http client request", 2048);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch req = p_new(pool, struct http_client_request, 1);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Boschhttp_client_request(struct http_client *client,
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch const char *method, const char *host, const char *target,
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch http_client_request_callback_t *callback, void *context)
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch req = http_client_request_new(client, method, callback, context);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch req->origin_url.host_name = p_strdup(req->pool, host);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch req->target = (target == NULL ? "/" : p_strdup(req->pool, target));
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Boschhttp_client_request_url(struct http_client *client,
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch const char *method, const struct http_url *target_url,
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch http_client_request_callback_t *callback, void *context)
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch req = http_client_request_new(client, method, callback, context);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch http_url_copy_authority(req->pool, &req->origin_url, target_url);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch req->target = p_strdup(req->pool, http_url_create_target(target_url));
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Boschhttp_client_request_connect(struct http_client *client,
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch req = http_client_request_new(client, "CONNECT", callback, context);
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch req->origin_url.host_name = p_strdup(req->pool, host);
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Boschhttp_client_request_connect_ip(struct http_client *client,
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_ref(struct http_client_request *req)
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_unref(struct http_client_request **_req)
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch /* cannot be destroyed while it is still pending */
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch i_assert(req->conn == NULL || req->conn->pending_request == NULL);
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch http_client_queue_drop_request(req->queue, req);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* only decrease pending request counter if this request was submitted */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_debug(req, "Destroy (requests left=%d)",
16eb9a737d42017fc875ef7b68afc25c3c9f8979Stephan Bosch http_client_queue_drop_request(req->queue, req);
a991cfe2157e58ee43bc580f517ce9ef0dfb7acfStephan Bosch if (client->requests_count == 0 && client->ioloop != NULL)
fca68889b287d8eed4babe72a231bd6079da012dStephan Bosch http_client_remove_request_error(req->client, req);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_set_port(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_set_ssl(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_set_urgent(struct http_client_request *req)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_add_header(struct http_client_request *req,
49287618521ff2c69385456de116e5d1581426c0Timo Sirainen i_assert(req->state == HTTP_REQUEST_STATE_NEW ||
49287618521ff2c69385456de116e5d1581426c0Timo Sirainen /* allow calling for retries */
49287618521ff2c69385456de116e5d1581426c0Timo Sirainen req->state == HTTP_REQUEST_STATE_GOT_RESPONSE ||
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch /* mark presence of special headers */
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch switch (key[0]) {
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch else if (strcasecmp(key, "Content-Length") == 0)
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch if (strcasecmp(key, "Transfer-Encoding") == 0)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch str_printfa(req->headers, "%s: %s\r\n", key, value);
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainenvoid http_client_request_remove_header(struct http_client_request *req,
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen const char *key)
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen const unsigned char *data, *p;
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen i_assert(req->state == HTTP_REQUEST_STATE_NEW ||
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen /* allow calling for retries */
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen req->state == HTTP_REQUEST_STATE_GOT_RESPONSE ||
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen while ((p = memchr(data, '\n', size)) != NULL) {
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen if (size > key_len && i_memcasecmp(data, key, key_len) == 0 &&
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen data[key_len] == ':' && data[key_len+1] == ' ') {
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen /* key was found from header, replace its value */
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen line_start_pos = str_len(req->headers) - size;
84740b03d3ee9e96a2e446a54729188764c99292Timo Sirainen str_delete(req->headers, line_start_pos, line_len);
6d573191bea1a64d6046be070487a5705a2d0204Stephan Boschvoid http_client_request_set_date(struct http_client_request *req,
6d573191bea1a64d6046be070487a5705a2d0204Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_set_payload(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW);
4521d35c263add6af3f1ae55b3760291767ce50cTimo Sirainen if ((ret = i_stream_get_size(input, TRUE, &req->payload_size)) <= 0) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* prepare request payload sync using 100 Continue response from server */
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if ((req->payload_chunked || req->payload_size > 0) && sync)
b66def5dadd3e7c250313a938d26ad113663f86bStephan Boschvoid http_client_request_set_timeout_msecs(struct http_client_request *req,
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch unsigned int msecs)
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW ||
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch req->state == HTTP_REQUEST_STATE_GOT_RESPONSE);
b66def5dadd3e7c250313a938d26ad113663f86bStephan Boschvoid http_client_request_set_timeout(struct http_client_request *req,
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW ||
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch req->state == HTTP_REQUEST_STATE_GOT_RESPONSE);
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Boschvoid http_client_request_delay_until(struct http_client_request *req,
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Boschvoid http_client_request_delay(struct http_client_request *req,
de0181258ab66b527ad8dc7e51a8efa76b4658d0Stephan Boschvoid http_client_request_delay_msecs(struct http_client_request *req,
de0181258ab66b527ad8dc7e51a8efa76b4658d0Stephan Bosch unsigned int msecs)
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Boschint http_client_request_delay_from_response(struct http_client_request *req,
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch unsigned int max;
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch return 0; /* no delay */
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch return 0; /* delay already expired */
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch max = (req->client->set.max_auto_retry_delay == 0 ?
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch req->client->set.request_timeout_msecs / 1000 :
19db4c57fd7acc9e54e5724ccfa0633a5665dfefTimo Sirainen if ((unsigned int)(retry_after - ioloop_time) > max)
42630b23d5a1b03cf6db4eaa2eb21e3ec4033b2cTimo Sirainenconst char *http_client_request_get_method(struct http_client_request *req)
b2a3fbfe1b436123bbe1849eeeef9bb0c28b1f90Timo Sirainenconst char *http_client_request_get_target(struct http_client_request *req)
1d048c5050f03c24251e5af8087e640de21b2d62Timo Sirainenhttp_client_request_get_state(struct http_client_request *req)
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Boschhttp_client_request_get_payload_type(struct http_client_request *req)
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch /* RFC 7230, Section 3.3:
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch The presence of a message body in a response depends on both the
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch request method to which it is responding and the response status code
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch (Section 3.1.2 of [RFC7230]). Responses to the HEAD request method
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch (Section 4.3.2 of [RFC7231]) never include a message body because the
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch associated response header fields (e.g., Transfer-Encoding,
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch Content-Length, etc.), if present, indicate only what their values
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch would have been if the request method had been GET (Section 4.3.1 of
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch [RFC7231]). 2xx (Successful) responses to a CONNECT request method
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch (Section 4.3.6 of [RFC7231]) switch to tunnel mode instead of having a
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch message body.
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch return HTTP_RESPONSE_PAYLOAD_TYPE_NOT_PRESENT;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch return HTTP_RESPONSE_PAYLOAD_TYPE_ONLY_UNSUCCESSFUL;
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Boschstatic void http_client_request_do_submit(struct http_client_request *req)
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch const struct http_url *proxy_url = client->set.proxy_url;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW);
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch authority = http_url_create_authority(&req->origin_url);
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch /* connect requests require authority form for request target */
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch /* absolute target url */
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch (http_url_create_host(&req->origin_url), req->target, NULL);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch /* determine what host to contact to submit this request */
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch if (req->origin_url.have_ssl && !client->set.no_ssl_tunnel &&
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch req->host_url = &req->origin_url; /* tunnel to origin server */
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch req->host_url = &req->origin_url; /* origin server */
6d573191bea1a64d6046be070487a5705a2d0204Stephan Bosch /* use submission date if no date is set explicitly */
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch /* prepare value for Host header */
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch req->authority = p_strdup(req->pool, authority);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch /* debug label */
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch req->label = p_strdup_printf(req->pool, "[%s %s]", req->method, target);
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch /* update request target */
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch /* if we don't have a proxy, CONNECT requests are handled by creating
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch the requested connection directly */
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch timeval_add_msecs(&req->timeout_time, req->timeout_msecs);
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch } else if ( client->set.request_absolute_timeout_msecs > 0) {
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch timeval_add_msecs(&req->timeout_time, client->set.request_absolute_timeout_msecs);
e47c2f17d8136c4d972d1074a3f84ba2ecef4fdcStephan Bosch host = http_client_host_get(req->client, req->host_url);
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Boschvoid http_client_request_submit(struct http_client_request *req)
069b28a2ef54072a221fe4ac67aaeb4e83fee6c1Timo Sirainenhttp_client_request_finish_payload_out(struct http_client_request *req)
aab7256cdcfb7abd01c822e3df8dd77a30c572e0Stephan Bosch /* drop payload output stream */
aab7256cdcfb7abd01c822e3df8dd77a30c572e0Stephan Bosch /* advance state only when request didn't get aborted in the mean time */
aab7256cdcfb7abd01c822e3df8dd77a30c572e0Stephan Bosch if (req->state != HTTP_REQUEST_STATE_ABORTED) {
aab7256cdcfb7abd01c822e3df8dd77a30c572e0Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT);
aab7256cdcfb7abd01c822e3df8dd77a30c572e0Stephan Bosch /* release connection */
aab7256cdcfb7abd01c822e3df8dd77a30c572e0Stephan Bosch http_client_request_debug(req, "Finished sending%s payload",
aab7256cdcfb7abd01c822e3df8dd77a30c572e0Stephan Bosch (req->state == HTTP_REQUEST_STATE_ABORTED ? " aborted" : ""));
069b28a2ef54072a221fe4ac67aaeb4e83fee6c1Timo Sirainenhttp_client_request_continue_payload(struct http_client_request **_req,
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch struct http_client_connection *conn = req->conn;
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_NEW ||
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT);
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch if (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT)
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch req->payload_input = i_stream_create_from_data(data, size);
99feb6521535a7dc59d8dda89981ceac084b3e88Timo Sirainen i_stream_set_name(req->payload_input, "<HTTP request payload>");
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch /* Wait for payload data to be written */
0d5c9a80e91a4073d5fd6820e9ddce2755221f64Stephan Bosch dns_client_switch_ioloop(client->set.dns_client);
a0613a630a412b0649b83c40c83f9fcfe50e1ad7Timo Sirainen while (req->state < HTTP_REQUEST_STATE_PAYLOAD_IN) {
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch http_client_request_debug(req, "Waiting for request to finish");
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen if (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT)
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen o_stream_set_flush_pending(req->payload_output, TRUE);
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch if (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT &&
0d5c9a80e91a4073d5fd6820e9ddce2755221f64Stephan Bosch dns_client_switch_ioloop(client->set.dns_client);
b36e026fb1e31bb76524cb345eb40c73e528507bStephan Bosch /* callback may have messed with our pointer,
b36e026fb1e31bb76524cb345eb40c73e528507bStephan Bosch so unref using local variable */
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch /* Return status */
069b28a2ef54072a221fe4ac67aaeb4e83fee6c1Timo Sirainenint http_client_request_send_payload(struct http_client_request **_req,
069b28a2ef54072a221fe4ac67aaeb4e83fee6c1Timo Sirainen return http_client_request_continue_payload(_req, data, size);
069b28a2ef54072a221fe4ac67aaeb4e83fee6c1Timo Sirainenint http_client_request_finish_payload(struct http_client_request **_req)
069b28a2ef54072a221fe4ac67aaeb4e83fee6c1Timo Sirainen return http_client_request_continue_payload(_req, NULL, 0);
a4e186e3ef267fc7a6b592788067c8c9c87d0785Stephan Boschstatic void http_client_request_payload_input(struct http_client_request *req)
a4e186e3ef267fc7a6b592788067c8c9c87d0785Stephan Bosch struct http_client_connection *conn = req->conn;
99feb6521535a7dc59d8dda89981ceac084b3e88Timo Sirainenint http_client_request_send_more(struct http_client_request *req,
99feb6521535a7dc59d8dda89981ceac084b3e88Timo Sirainen const char **error_r)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_client_connection *conn = req->conn;
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen /* chunked ostream needs to write to the parent stream's buffer */
fb025942616dfec7770455a7092d01f2e516314dTimo Sirainen o_stream_set_max_buffer_size(output, IO_BLOCK_SIZE);
99feb6521535a7dc59d8dda89981ceac084b3e88Timo Sirainen ret = o_stream_send_istream(output, req->payload_input);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch o_stream_set_max_buffer_size(output, (size_t)-1);
50d1446e71cfbdc5b6d7bafcf91b7bff453989d3Stephan Bosch /* the payload stream assigned to this request is broken,
50d1446e71cfbdc5b6d7bafcf91b7bff453989d3Stephan Bosch fail this the request immediately */
50d1446e71cfbdc5b6d7bafcf91b7bff453989d3Stephan Bosch "Broken payload stream");
50d1446e71cfbdc5b6d7bafcf91b7bff453989d3Stephan Bosch /* we're in the middle of sending a request, so the connection
50d1446e71cfbdc5b6d7bafcf91b7bff453989d3Stephan Bosch will also have to be aborted */
dde71564d306d07cba63bdf0f40996ffb90ca47aTimo Sirainen *error_r = t_strdup_printf("read(%s) failed: %s",
50d1446e71cfbdc5b6d7bafcf91b7bff453989d3Stephan Bosch /* failed to send request */
dde71564d306d07cba63bdf0f40996ffb90ca47aTimo Sirainen *error_r = t_strdup_printf("write(%s) failed: %s",
5777eef991bdb9dc487e9b8e8da8a4579fc67f6cTimo Sirainen req->payload_input->v_offset - req->payload_offset != req->payload_size) {
b7540564b9d7b69ce8f6e5a80011ccd5f8b86005Timo Sirainen *error_r = t_strdup_printf("BUG: stream '%s' input size changed: "
b7540564b9d7b69ce8f6e5a80011ccd5f8b86005Timo Sirainen req->payload_input->v_offset, req->payload_offset, req->payload_size);
a4e186e3ef267fc7a6b592788067c8c9c87d0785Stephan Bosch } else if (i_stream_get_data_size(req->payload_input) > 0) {
a4e186e3ef267fc7a6b592788067c8c9c87d0785Stephan Bosch /* output is blocking */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_debug(req, "Partially sent payload");
a4e186e3ef267fc7a6b592788067c8c9c87d0785Stephan Bosch /* input is blocking */
37703e8d00a3a486aafba6a276fef35b38eab948Timo Sirainen conn->io_req_payload = io_add_istream(req->payload_input,
ccd968b44a40b9c2cf6278fabfa2a80cc5d9e46bTimo Sirainenstatic int http_client_request_send_real(struct http_client_request *req,
ccd968b44a40b9c2cf6278fabfa2a80cc5d9e46bTimo Sirainen const char **error_r)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_client_connection *conn = req->conn;
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch /* create request line */
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch /* create special headers implicitly if not set explicitly using
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch http_client_request_add_header() */
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch str_append(rtext, http_date_create(req->date));
b99130e4cf4af4e6b103b949456222f3a2dff424Timo Sirainen if (!req->have_hdr_user_agent && req->client->set.user_agent != NULL) {
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch if (!req->have_hdr_expect && req->payload_sync) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch str_append(rtext, "Expect: 100-continue\r\n");
35df1d3e03ffb05ee21077018f5154a4b1e47e37Timo Sirainen // FIXME: can't do this for a HTTP/1.0 server
35df1d3e03ffb05ee21077018f5154a4b1e47e37Timo Sirainen str_append(rtext, "Transfer-Encoding: chunked\r\n");
35df1d3e03ffb05ee21077018f5154a4b1e47e37Timo Sirainen /* send Content-Length if we have specified a payload,
35df1d3e03ffb05ee21077018f5154a4b1e47e37Timo Sirainen even if it's 0 bytes. */
35df1d3e03ffb05ee21077018f5154a4b1e47e37Timo Sirainen str_printfa(rtext, "Content-Length: %"PRIuUOFF_T"\r\n",
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch if (!req->have_hdr_connection && req->host_url == &req->origin_url) {
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch Section 19.7.1:
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch A client MUST NOT send the Keep-Alive connection token to a proxy
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch server as HTTP/1.0 proxy servers do not obey the rules of HTTP/1.1
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch for parsing the Connection header field.
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch str_append(rtext, "Connection: Keep-Alive\r\n");
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch /* request line + implicit headers */
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch /* explicit headers */
87c121a4c05b9cee46f1f757ec6999d441519abfStephan Bosch /* end of header */
99feb6521535a7dc59d8dda89981ceac084b3e88Timo Sirainen if (o_stream_sendv(output, iov, N_ELEMENTS(iov)) < 0) {
dde71564d306d07cba63bdf0f40996ffb90ca47aTimo Sirainen *error_r = t_strdup_printf("write(%s) failed: %s",
22215c2d7538f4367c93e2d8b6ec4722463ac757Stephan Bosch http_client_request_debug(req, "Sent header");
22215c2d7538f4367c93e2d8b6ec4722463ac757Stephan Bosch if (http_client_request_send_more(req, error_r) < 0)
22215c2d7538f4367c93e2d8b6ec4722463ac757Stephan Bosch http_client_request_debug(req, "Waiting for 100-continue");
22215c2d7538f4367c93e2d8b6ec4722463ac757Stephan Bosch *error_r = t_strdup_printf("flush(%s) failed: %s",
ccd968b44a40b9c2cf6278fabfa2a80cc5d9e46bTimo Sirainenint http_client_request_send(struct http_client_request *req,
ccd968b44a40b9c2cf6278fabfa2a80cc5d9e46bTimo Sirainen const char **error_r)
ccd968b44a40b9c2cf6278fabfa2a80cc5d9e46bTimo Sirainen ret = http_client_request_send_real(req, error_r);
6c768e0e1ca2da178e79f7435c32ced01f6bcb24Timo Sirainenbool http_client_request_callback(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_callback_t *callback = req->callback;
6c768e0e1ca2da178e79f7435c32ced01f6bcb24Timo Sirainen /* retrying */
6c6915f4d75c352c63156df202fa51cd97524babStephan Bosch /* release payload early (prevents server/client deadlock in proxy) */
7384b4e78eaab44693c985192276e31322155e32Stephan Boschhttp_client_request_send_error(struct http_client_request *req,
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch bool sending = (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT);
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch /* release payload early (prevents server/client deadlock in proxy) */
129596c93692b21d6c6b1313b389774af24c2983Stephan Boschvoid http_client_request_error_delayed(struct http_client_request **_req)
9145c8b5eda526d05bd4a7ced20f6f6f2ff8df03Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_ABORTED);
129596c93692b21d6c6b1313b389774af24c2983Stephan Bosch i_assert(req->delayed_error != NULL && req->delayed_error_status != 0);
6bc9fb43cc1ac24693d030a6cbfa43bc7cbc82cbTimo Sirainen http_client_request_send_error(req, req->delayed_error_status,
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch http_client_queue_drop_request(req->queue, req);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_error(struct http_client_request *req,
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch if (req->state >= HTTP_REQUEST_STATE_FINISHED)
840abb812d2e8edc42b5e2a4e3838b8d5e759e6aStephan Bosch http_client_queue_drop_request(req->queue, req);
5e4cdaaf560cfa94bfc014ce8e1a52e4d0a85b48Stephan Bosch req->state == HTTP_REQUEST_STATE_GOT_RESPONSE) {
5e4cdaaf560cfa94bfc014ce8e1a52e4d0a85b48Stephan Bosch /* we're still in http_client_request_submit() or in the callback
5e4cdaaf560cfa94bfc014ce8e1a52e4d0a85b48Stephan Bosch during a retry attempt. delay reporting the error, so the caller
5e4cdaaf560cfa94bfc014ce8e1a52e4d0a85b48Stephan Bosch doesn't have to handle immediate or nested callbacks. */
6bc9fb43cc1ac24693d030a6cbfa43bc7cbc82cbTimo Sirainen req->delayed_error = p_strdup(req->pool, error);
fca68889b287d8eed4babe72a231bd6079da012dStephan Bosch http_client_delay_request_error(req->client, req);
6bc9fb43cc1ac24693d030a6cbfa43bc7cbc82cbTimo Sirainen http_client_request_send_error(req, status, error);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_abort(struct http_client_request **_req)
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch bool sending = (req->state == HTTP_REQUEST_STATE_PAYLOAD_OUT);
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (req->state >= HTTP_REQUEST_STATE_FINISHED)
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch /* release payload early (prevents server/client deadlock in proxy) */
de96afeeaa5242cffe89f1966457e935806b5746Stephan Bosch http_client_queue_drop_request(req->queue, req);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_finish(struct http_client_request **_req)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (req->state >= HTTP_REQUEST_STATE_FINISHED)
b66def5dadd3e7c250313a938d26ad113663f86bStephan Bosch http_client_queue_drop_request(req->queue, req);
eb325a5a90c1d2655e74972bde0de6a699d2c864Stephan Bosch if (req->payload_wait && req->client->ioloop != NULL)
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_redirect(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* parse URL */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_error(req, HTTP_CLIENT_REQUEST_ERROR_INVALID_REDIRECT,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch t_strdup_printf("Invalid redirect location: %s", error));
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (++req->redirects > req->client->set.max_redirects) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch t_strdup_printf("Redirected more than %d times",
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch "Redirect refused");
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen /* rewind payload stream */
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (req->payload_input != NULL && req->payload_size > 0 && status != 303) {
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (req->payload_input->v_offset != req->payload_offset &&
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen "Redirect failed: Cannot resend payload; stream is not seekable");
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen i_stream_seek(req->payload_input, req->payload_offset);
27421074812b84d144b68388e597f4700f4f1c1bStephan Bosch /* drop payload output stream from previous attempt */
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch http_url_copy(req->pool, &req->origin_url, url);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch p_strdup(req->pool, http_url_create_authority(req->host_url));
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch origin_url = http_url_create(&req->origin_url);
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch http_client_request_debug(req, "Redirecting to %s%s",
4219de12b28f1936219e27501b9c4b27a4f8d53cStephan Bosch req->label = p_strdup_printf(req->pool, "[%s %s%s]",
6ee9ce5ed955a1283dc22ad28980bf9cc23d4c4eStephan Bosch /* RFC 7231, Section 6.4.4:
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen -> A 303 `See Other' redirect status response is handled a bit differently.
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen Basically, the response content is located elsewhere, but the original
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen (POST) request is handled already.
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (status == 303 && strcasecmp(req->method, "HEAD") != 0 &&
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen // FIXME: should we provide the means to skip this step? The original
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen // request was already handled at this point.
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen /* drop payload */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* resubmit */
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_resubmit(struct http_client_request *req)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_debug(req, "Resubmitting request");
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch /* rewind payload stream */
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (req->payload_input != NULL && req->payload_size > 0) {
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (req->payload_input->v_offset != req->payload_offset &&
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen "Resubmission failed: Cannot resend payload; stream is not seekable");
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen i_stream_seek(req->payload_input, req->payload_offset);
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen /* rewind payload stream */
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (req->payload_input != NULL && req->payload_size > 0) {
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen if (req->payload_input->v_offset != req->payload_offset &&
50a6d26bd9041f44b4cad0c0357c0c604c132cc8Stephan Bosch "Resubmission failed: Cannot resend payload; stream is not seekable");
65c0e43da8cfc730eeb4634f8aa384081bbfa4e7Timo Sirainen i_stream_seek(req->payload_input, req->payload_offset);
27421074812b84d144b68388e597f4700f4f1c1bStephan Bosch /* drop payload output stream from previous attempt */
93cc87bb22386e020cee1093b6bd59295e0b33f0Stephan Bosch http_client_host_submit_request(req->host, req);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_client_request_retry(struct http_client_request *req,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch http_client_request_error(req, status, error);
6c768e0e1ca2da178e79f7435c32ced01f6bcb24Timo Sirainenbool http_client_request_try_retry(struct http_client_request *req)
d3d941cc89a8ef5fe0de16bd89e50030e5d22f5bStephan Bosch /* don't ever retry if we're sending data in small blocks via
d3d941cc89a8ef5fe0de16bd89e50030e5d22f5bStephan Bosch http_client_request_send_payload() and we're not waiting for a
d3d941cc89a8ef5fe0de16bd89e50030e5d22f5bStephan Bosch 100 continue (there's no way to rewind the payload for a retry)
d3d941cc89a8ef5fe0de16bd89e50030e5d22f5bStephan Bosch (!req->payload_sync || req->conn->payload_continue))
6c768e0e1ca2da178e79f7435c32ced01f6bcb24Timo Sirainen /* limit the number of attempts for each request */
6c768e0e1ca2da178e79f7435c32ced01f6bcb24Timo Sirainen if (req->attempts+1 >= req->client->set.max_attempts)
6c768e0e1ca2da178e79f7435c32ced01f6bcb24Timo Sirainen http_client_request_debug(req, "Retrying (attempts=%d)", req->attempts);
4124bebe6daab2cd05acb0416096fc47cb9abd92Timo Sirainenvoid http_client_request_set_destroy_callback(struct http_client_request *req,
4124bebe6daab2cd05acb0416096fc47cb9abd92Timo Sirainen void (*callback)(void *),
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Boschvoid http_client_request_start_tunnel(struct http_client_request *req,
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch i_assert(req->state == HTTP_REQUEST_STATE_GOT_RESPONSE);