// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/tools/quic/quic_simple_server_stream.h" #include #include #include "net/quic/core/quic_spdy_stream.h" #include "net/quic/core/spdy_utils.h" #include "net/quic/platform/api/quic_bug_tracker.h" #include "net/quic/platform/api/quic_flags.h" #include "net/quic/platform/api/quic_logging.h" #include "net/quic/platform/api/quic_map_util.h" #include "net/quic/platform/api/quic_text_utils.h" #include "net/spdy/core/spdy_protocol.h" #include "net/tools/quic/quic_http_response_cache.h" #include "net/tools/quic/quic_simple_server_session.h" using std::string; namespace net { QuicSimpleServerStream::QuicSimpleServerStream( QuicStreamId id, QuicSpdySession* session, QuicHttpResponseCache* response_cache) : QuicSpdyServerStreamBase(id, session), content_length_(-1), response_cache_(response_cache) {} QuicSimpleServerStream::~QuicSimpleServerStream() {} void QuicSimpleServerStream::OnInitialHeadersComplete( bool fin, size_t frame_len, const QuicHeaderList& header_list) { QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); if (!SpdyUtils::CopyAndValidateHeaders(header_list, &content_length_, &request_headers_)) { QUIC_DVLOG(1) << "Invalid headers"; SendErrorResponse(); } ConsumeHeaderList(); } void QuicSimpleServerStream::OnTrailingHeadersComplete( bool fin, size_t frame_len, const QuicHeaderList& header_list) { QUIC_BUG << "Server does not support receiving Trailers."; SendErrorResponse(); } void QuicSimpleServerStream::OnDataAvailable() { while (HasBytesToRead()) { struct iovec iov; if (GetReadableRegions(&iov, 1) == 0) { // No more data to read. break; } QUIC_DVLOG(1) << "Stream " << id() << " processed " << iov.iov_len << " bytes."; body_.append(static_cast(iov.iov_base), iov.iov_len); if (content_length_ >= 0 && body_.size() > static_cast(content_length_)) { QUIC_DVLOG(1) << "Body size (" << body_.size() << ") > content length (" << content_length_ << ")."; SendErrorResponse(); return; } MarkConsumed(iov.iov_len); } if (!sequencer()->IsClosed()) { sequencer()->SetUnblocked(); return; } // If the sequencer is closed, then all the body, including the fin, has been // consumed. OnFinRead(); if (write_side_closed() || fin_buffered()) { return; } SendResponse(); } void QuicSimpleServerStream::PushResponse( SpdyHeaderBlock push_request_headers) { if (id() % 2 != 0) { QUIC_BUG << "Client initiated stream shouldn't be used as promised stream."; return; } // Change the stream state to emulate a client request. request_headers_ = std::move(push_request_headers); content_length_ = 0; QUIC_DVLOG(1) << "Stream " << id() << " ready to receive server push response."; // Set as if stream decompresed the headers and received fin. QuicSpdyStream::OnInitialHeadersComplete(/*fin=*/true, 0, QuicHeaderList()); } void QuicSimpleServerStream::SendResponse() { if (request_headers_.empty()) { QUIC_DVLOG(1) << "Request headers empty."; SendErrorResponse(); return; } if (content_length_ > 0 && static_cast(content_length_) != body_.size()) { QUIC_DVLOG(1) << "Content length (" << content_length_ << ") != body size (" << body_.size() << ")."; SendErrorResponse(); return; } if (!QuicContainsKey(request_headers_, ":authority") || !QuicContainsKey(request_headers_, ":path")) { QUIC_DVLOG(1) << "Request headers do not contain :authority or :path."; SendErrorResponse(); return; } // Find response in cache. If not found, send error response. const QuicHttpResponseCache::Response* response = nullptr; auto authority = request_headers_.find(":authority"); auto path = request_headers_.find(":path"); if (authority != request_headers_.end() && path != request_headers_.end()) { response = response_cache_->GetResponse(authority->second, path->second); } if (response == nullptr) { QUIC_DVLOG(1) << "Response not found in cache."; SendNotFoundResponse(); return; } if (response->response_type() == QuicHttpResponseCache::CLOSE_CONNECTION) { QUIC_DVLOG(1) << "Special response: closing connection."; CloseConnectionWithDetails(QUIC_NO_ERROR, "Toy server forcing close"); return; } if (response->response_type() == QuicHttpResponseCache::IGNORE_REQUEST) { QUIC_DVLOG(1) << "Special response: ignoring request."; return; } // Examing response status, if it was not pure integer as typical h2 // response status, send error response. Notice that // QuicHttpResponseCache push urls are strictly authority + path only, // scheme is not included (see |QuicHttpResponseCache::GetKey()|). string request_url = request_headers_[":authority"].as_string() + request_headers_[":path"].as_string(); int response_code; const SpdyHeaderBlock& response_headers = response->headers(); if (!ParseHeaderStatusCode(response_headers, &response_code)) { auto status = response_headers.find(":status"); if (status == response_headers.end()) { QUIC_LOG(WARNING) << ":status not present in response from cache for request " << request_url; } else { QUIC_LOG(WARNING) << "Illegal (non-integer) response :status from cache: " << status->second << " for request " << request_url; } SendErrorResponse(); return; } if (id() % 2 == 0) { // A server initiated stream is only used for a server push response, // and only 200 and 30X response codes are supported for server push. // This behavior mirrors the HTTP/2 implementation. bool is_redirection = response_code / 100 == 3; if (response_code != 200 && !is_redirection) { QUIC_LOG(WARNING) << "Response to server push request " << request_url << " result in response code " << response_code; Reset(QUIC_STREAM_CANCELLED); return; } } std::list resources = response_cache_->GetServerPushResources(request_url); QUIC_DVLOG(1) << "Stream " << id() << " found " << resources.size() << " push resources."; if (!resources.empty()) { QuicSimpleServerSession* session = static_cast(spdy_session()); session->PromisePushResources(request_url, resources, id(), request_headers_); } QUIC_DVLOG(1) << "Stream " << id() << " sending response."; SendHeadersAndBodyAndTrailers(response->headers().Clone(), response->body(), response->trailers().Clone()); } void QuicSimpleServerStream::SendNotFoundResponse() { QUIC_DVLOG(1) << "Stream " << id() << " sending not found response."; SpdyHeaderBlock headers; headers[":status"] = "404"; headers["content-length"] = QuicTextUtils::Uint64ToString(strlen(kNotFoundResponseBody)); SendHeadersAndBody(std::move(headers), kNotFoundResponseBody); } void QuicSimpleServerStream::SendErrorResponse() { QUIC_DVLOG(1) << "Stream " << id() << " sending error response."; SpdyHeaderBlock headers; headers[":status"] = "500"; headers["content-length"] = QuicTextUtils::Uint64ToString(strlen(kErrorResponseBody)); SendHeadersAndBody(std::move(headers), kErrorResponseBody); } void QuicSimpleServerStream::SendHeadersAndBody( SpdyHeaderBlock response_headers, QuicStringPiece body) { SendHeadersAndBodyAndTrailers(std::move(response_headers), body, SpdyHeaderBlock()); } void QuicSimpleServerStream::SendHeadersAndBodyAndTrailers( SpdyHeaderBlock response_headers, QuicStringPiece body, SpdyHeaderBlock response_trailers) { // Send the headers, with a FIN if there's nothing else to send. bool send_fin = (body.empty() && response_trailers.empty()); QUIC_DLOG(INFO) << "Stream " << id() << " writing headers (fin = " << send_fin << ") : " << response_headers.DebugString(); WriteHeaders(std::move(response_headers), send_fin, nullptr); if (send_fin) { // Nothing else to send. return; } // Send the body, with a FIN if there's no trailers to send. send_fin = response_trailers.empty(); QUIC_DLOG(INFO) << "Stream " << id() << " writing body (fin = " << send_fin << ") with size: " << body.size(); if (!body.empty() || send_fin) { WriteOrBufferData(body, send_fin, nullptr); } if (send_fin) { // Nothing else to send. return; } // Send the trailers. A FIN is always sent with trailers. QUIC_DLOG(INFO) << "Stream " << id() << " writing trailers (fin = true): " << response_trailers.DebugString(); WriteTrailers(std::move(response_trailers), nullptr); } const char* const QuicSimpleServerStream::kErrorResponseBody = "bad"; const char* const QuicSimpleServerStream::kNotFoundResponseBody = "file not found"; } // namespace net