http-response-parser.c revision 7af70f7646264a2f52b361f9ca78f08681acc4e2
5a580c3a38ced62d4bcc95b8ac7c4f2935b5d294Timo Sirainen/* Copyright (c) 2013 Dovecot authors, see the included COPYING file */
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Boschhttp_response_parser_init(struct istream *input,
feba5e502b2131c9a1c766b7ef9ff041dbf71d1dStephan Bosch /* FIXME: implement status line limit */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch parser = i_new(struct http_response_parser, 1);
1175415b88ff168e367c77df23901eada13225b9Stephan Bosch http_message_parser_init(&parser->parser, input, hdr_limits, 0);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschvoid http_response_parser_deinit(struct http_response_parser **_parser)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch struct http_response_parser *parser = *_parser;
7384b4e78eaab44693c985192276e31322155e32Stephan Boschhttp_response_parser_restart(struct http_response_parser *parser)
6dad0888fcec8372f230941c70d8940b8c203b32Stephan Bosch http_message_parser_restart(&parser->parser, NULL);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int http_response_parse_status(struct http_response_parser *parser)
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen const size_t size = parser->parser.end - parser->parser.cur;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* status-code = 3DIGIT
1bc12a53ddc6696bb209fb79d7cc66262d2ea621Timo Sirainen if (!i_isdigit(p[0]) || !i_isdigit(p[1]) || !i_isdigit(p[2]))
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch (p[0] - '0')*100 + (p[1] - '0')*10 + (p[2] - '0');
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic int http_response_parse_reason(struct http_response_parser *parser)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* reason-phrase = *( HTAB / SP / VCHAR / obs-text )
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch // FIXME: limit length
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen while (p < parser->parser.end && http_char_is_text(*p))
6dad0888fcec8372f230941c70d8940b8c203b32Stephan Bosch p_strdup_until(parser->parser.msg.pool, parser->parser.cur, p);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschstatic inline const char *_chr_sanitize(unsigned char c)
101e78ae4d5705f196fba22db3261db042bfb3c2Stephan Bosch if (c == 0x0a)
101e78ae4d5705f196fba22db3261db042bfb3c2Stephan Bosch return "<LF>";
101e78ae4d5705f196fba22db3261db042bfb3c2Stephan Bosch if (c == 0x0d)
101e78ae4d5705f196fba22db3261db042bfb3c2Stephan Bosch return "<CR>";
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Boschstatic int http_response_parse(struct http_response_parser *parser)
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen struct http_message_parser *_parser = &parser->parser;
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* status-line = HTTP-version SP status-code SP reason-phrase CRLF
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch status-code = 3DIGIT
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch reason-phrase = *( HTAB / SP / VCHAR / obs-text )
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_VERSION;
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch /* fall through */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch if ((ret=http_message_parse_version(_parser)) <= 0) {
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch _parser->error = "Invalid HTTP version in response";
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_SP1;
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch /* fall through */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch ("Expected ' ' after response version, but found %s",
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_STATUS;
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch /* fall through */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch if ((ret=http_response_parse_status(parser)) <= 0) {
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch _parser->error = "Invalid HTTP status code in response";
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_SP2;
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch /* fall through */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch ("Expected ' ' after response status code, but found %s",
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_REASON;
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch /* fall through */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch if ((ret=http_response_parse_reason(parser)) <= 0) {
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch /* fall through */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch /* fall through */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch ("Expected line end after response, but found %s",
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_HEADER;
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Boschhttp_response_parse_status_line(struct http_response_parser *parser)
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen struct http_message_parser *_parser = &parser->parser;
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen const unsigned char *begin;
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen while ((ret = i_stream_read_data(_parser->input, &begin, &size,
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen i_stream_skip(_parser->input, _parser->cur - begin);
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen old_bytes = i_stream_get_data_size(_parser->input);
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch _parser->error = "HTTP status line is too long";
b72c3363092b73cab1da2de4a9d75592e7d8fd6bTimo Sirainen parser->state == HTTP_RESPONSE_PARSE_STATE_INIT)
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Boschhttp_response_parse_retry_after(const char *hdrval, time_t resp_time,
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch /* http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-23
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch Section 7.1.3:
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch The value of this field can be either an HTTP-date or an integer
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch number of seconds (in decimal) after the time of the response.
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch Time spans are non-negative decimal integers, representing time in
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch Retry-After = HTTP-date / delta-seconds
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch delta-seconds = 1*DIGIT
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch ((unsigned char *)hdrval, strlen(hdrval), retry_after_r) ? 0 : -1);
7384b4e78eaab44693c985192276e31322155e32Stephan Boschint http_response_parse_next(struct http_response_parser *parser,
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch struct http_response *response, const char **error_r)
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* make sure we finished streaming payload from previous response
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch before we continue. */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch if ((ret = http_message_parse_finish_payload(&parser->parser)) <= 0) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* HTTP-message = start-line
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch *( header-field CRLF )
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch [ message-body ]
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch if (parser->state != HTTP_RESPONSE_PARSE_STATE_HEADER) {
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch if ((ret = http_response_parse_status_line(parser)) <= 0) {
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch if ((ret = http_message_parse_headers(&parser->parser)) <= 0) {
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch Section 3.3.2:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch A server MUST NOT send a Content-Length header field in any response
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch with a status code of 1xx (Informational) or 204 (No Content). [...]
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch if ((parser->response_status / 100 == 1 || parser->response_status == 204) &&
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch "Unexpected Content-Length header field for %u response "
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch "(length=%"PRIuUOFF_T")", parser->response_status,
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-21
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch Section 3.3.3:
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch Any response to a HEAD request and any response with a 1xx
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch (Informational), 204 (No Content), or 304 (Not Modified) status
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch code is always terminated by the first empty line after the
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch header fields, regardless of the header fields present in the
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch message, and thus cannot contain a message body.
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch if (parser->response_status / 100 == 1 || parser->response_status == 204
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch || parser->response_status == 304) { // HEAD is handled in caller
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch payload_type = HTTP_RESPONSE_PAYLOAD_TYPE_NOT_PRESENT;
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch if ((payload_type == HTTP_RESPONSE_PAYLOAD_TYPE_ALLOWED) ||
a62fe4b300e2f591e939993aec4cac1e7ae30ad1Stephan Bosch (payload_type == HTTP_RESPONSE_PAYLOAD_TYPE_ONLY_UNSUCCESSFUL &&
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch /* [ message-body ] */
208dcaf62332b80b220c8c66e776f7cc0c39253bStephan Bosch if (http_message_parse_body(&parser->parser, FALSE) < 0) {
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch /* http://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-23
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch Section 7.1.3:
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch Servers send the "Retry-After" header field to indicate how long the
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch user agent ought to wait before making a follow-up request. When
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch sent with a 503 (Service Unavailable) response, Retry-After indicates
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch how long the service is expected to be unavailable to the client.
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch When sent with any 3xx (Redirection) response, Retry-After indicates
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch the minimum time that the user agent is asked to wait before issuing
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch the redirected request.
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch if (parser->response_status == 503 || (parser->response_status / 100) == 3) {
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch hdrval = http_header_field_get(parser->parser.msg.header, "Retry-After");
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch (hdrval, parser->parser.msg.date, &retry_after);
7af70f7646264a2f52b361f9ca78f08681acc4e2Stephan Bosch /* broken Retry-After header is ignored */
7384b4e78eaab44693c985192276e31322155e32Stephan Bosch parser->state = HTTP_RESPONSE_PARSE_STATE_INIT;
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch response->version_major = parser->parser.msg.version_major;
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch response->version_minor = parser->parser.msg.version_minor;
6a90041707f1290c8970a3bacb0f8f928aeaaba6Stephan Bosch response->location = parser->parser.msg.location;
e8f1e510df3ab051a816715c2056f0d10aee929eStephan Bosch response->headers = *http_header_get_fields(response->header); /* FIXME: remove in v2.3 */
9e7bf91667639a2390207ab4d90bf88e2afcec2aStephan Bosch response->connection_options = parser->parser.msg.connection_options;