// Copyright 2010 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "mod_spdy/common/spdy_to_http_converter.h"

#include "base/logging.h"
#include "base/string_number_conversions.h"  // for Int64ToString
#include "base/string_piece.h"
#include "mod_spdy/common/http_request_visitor_interface.h"
#include "mod_spdy/common/protocol_util.h"
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_framer.h"
#include "net/spdy/spdy_protocol.h"

namespace mod_spdy {

namespace {

// Functions to test for FLAG_FIN.  Using these functions instead of testing
// flags() directly helps guard against mixing up & with && or mixing up
// CONTROL_FLAG_FIN with DATA_FLAG_FIN.
bool HasControlFlagFinSet(const net::SpdyControlFrame& frame) {
  return bool(frame.flags() & net::CONTROL_FLAG_FIN);
}
bool HasDataFlagFinSet(const net::SpdyDataFrame& frame) {
  return bool(frame.flags() & net::DATA_FLAG_FIN);
}

// Generate an HTTP request line from the given SPDY header block by calling
// the OnStatusLine() method of the given visitor, and return true.  If there's
// an error, this will return false without calling any methods on the visitor.
bool GenerateRequestLine(int spdy_version,
                         const net::SpdyHeaderBlock& block,
                         HttpRequestVisitorInterface* visitor) {
  net::SpdyHeaderBlock::const_iterator method = block.find(
      spdy_version < 3 ? spdy::kSpdy2Method : spdy::kSpdy3Method);
  net::SpdyHeaderBlock::const_iterator scheme = block.find(
      spdy_version < 3 ? spdy::kSpdy2Scheme : spdy::kSpdy3Scheme);
  net::SpdyHeaderBlock::const_iterator host = block.find(
      spdy_version < 3 ? http::kHost : spdy::kSpdy3Host);
  net::SpdyHeaderBlock::const_iterator path = block.find(
      spdy_version < 3 ? spdy::kSpdy2Url : spdy::kSpdy3Path);
  net::SpdyHeaderBlock::const_iterator version = block.find(
      spdy_version < 3 ? spdy::kSpdy2Version : spdy::kSpdy3Version);

  if (method == block.end() ||
      scheme == block.end() ||
      host == block.end() ||
      path == block.end() ||
      version == block.end()) {
    return false;
  }

  visitor->OnRequestLine(method->second, path->second, version->second);
  return true;
}

// Convert the given SPDY header into HTTP header(s) by splitting on NUL bytes
// calling the specified method (either OnLeadingHeader or OnTrailingHeader) of
// the given visitor.
template <void(HttpRequestVisitorInterface::*OnHeader)(
    const base::StringPiece& key, const base::StringPiece& value)>
void InsertHeader(const base::StringPiece key,
                  const base::StringPiece value,
                  HttpRequestVisitorInterface* visitor) {
  // Split header values on null characters, emitting a separate
  // header key-value pair for each substring. Logic from
  // net/spdy/spdy_session.cc
  for (size_t start = 0, end = 0; end != value.npos; start = end) {
    start = value.find_first_not_of('\0', start);
    if (start == value.npos) {
      break;
    }
    end = value.find('\0', start);
    (visitor->*OnHeader)(key, (end != value.npos ?
                               value.substr(start, (end - start)) :
                               value.substr(start)));
  }
}

}  // namespace

SpdyToHttpConverter::SpdyToHttpConverter(int spdy_version,
                                         HttpRequestVisitorInterface* visitor)
    : visitor_(visitor),
      framer_(spdy_version),
      state_(NO_FRAMES_YET),
      use_chunking_(true),
      seen_accept_encoding_(false) {
  CHECK(visitor);
}

SpdyToHttpConverter::~SpdyToHttpConverter() {}

// static
const char* SpdyToHttpConverter::StatusString(Status status) {
  switch (status) {
    case SPDY_CONVERTER_SUCCESS:  return "SPDY_CONVERTER_SUCCESS";
    case FRAME_BEFORE_SYN_STREAM: return "FRAME_BEFORE_SYN_STREAM";
    case FRAME_AFTER_FIN:         return "FRAME_AFTER_FIN";
    case EXTRA_SYN_STREAM:        return "EXTRA_SYN_STREAM";
    case INVALID_HEADER_BLOCK:    return "INVALID_HEADER_BLOCK";
    case BAD_REQUEST:             return "BAD_REQUEST";
    default:
      LOG(DFATAL) << "Invalid status value: " << status;
      return "???";
  }
}

SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertSynStreamFrame(
    const net::SpdySynStreamControlFrame& frame) {
  if (state_ != NO_FRAMES_YET) {
    return EXTRA_SYN_STREAM;
  }
  state_ = RECEIVED_SYN_STREAM;

  net::SpdyHeaderBlock block;
  if (!framer_.ParseHeaderBlockInBuffer(
          frame.header_block(), frame.header_block_len(), &block)) {
    return INVALID_HEADER_BLOCK;
  }

  if (!GenerateRequestLine(spdy_version(), block, visitor_)) {
    return BAD_REQUEST;
  }

  // Translate the headers to HTTP.
  GenerateLeadingHeaders(block);

  // If this is the last (i.e. only) frame on this stream, finish off the HTTP
  // request.
  if (HasControlFlagFinSet(frame)) {
    FinishRequest();
  }

  return SPDY_CONVERTER_SUCCESS;
}

SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertHeadersFrame(
    const net::SpdyHeadersControlFrame& frame) {
  if (state_ == RECEIVED_FLAG_FIN) {
    return FRAME_AFTER_FIN;
  } else if (state_ == NO_FRAMES_YET) {
    return FRAME_BEFORE_SYN_STREAM;
  }

  // Parse the headers from the HEADERS frame.  If there have already been any
  // data frames, then we need to save these headers for later and send them as
  // trailing headers.  Otherwise, we can send them immediately.
  if (state_ == RECEIVED_DATA) {
    if (use_chunking_) {
      if (!framer_.ParseHeaderBlockInBuffer(
              frame.header_block(), frame.header_block_len(),
              &trailing_headers_)) {
        return INVALID_HEADER_BLOCK;
      }
    } else {
      LOG(WARNING) << "Client sent trailing headers, "
                   << "but we had to ignore them.";
    }
  } else {
    DCHECK(state_ == RECEIVED_SYN_STREAM);
    DCHECK(trailing_headers_.empty());
    net::SpdyHeaderBlock block;
    if (!framer_.ParseHeaderBlockInBuffer(
            frame.header_block(), frame.header_block_len(), &block)) {
      return INVALID_HEADER_BLOCK;
    }

    // Translate the headers to HTTP.
    GenerateLeadingHeaders(block);
  }

  // If this is the last frame on this stream, finish off the HTTP request.
  if (HasControlFlagFinSet(frame)) {
    FinishRequest();
  }

  return SPDY_CONVERTER_SUCCESS;
}

SpdyToHttpConverter::Status SpdyToHttpConverter::ConvertDataFrame(
    const net::SpdyDataFrame& frame) {
  if (state_ == RECEIVED_FLAG_FIN) {
    return FRAME_AFTER_FIN;
  } else if (state_ == NO_FRAMES_YET) {
    return FRAME_BEFORE_SYN_STREAM;
  }

  // If this is the first data frame in the stream, we need to close the HTTP
  // headers section (for streams where there are never any data frames, we
  // close the headers section in FinishRequest instead).  Just before we do,
  // we may need to add some last-minute headers.
  if (state_ == RECEIVED_SYN_STREAM) {
    state_ = RECEIVED_DATA;

    // Unless we're not using chunked encoding (due to having received a
    // Content-Length headers), set Transfer-Encoding: chunked now.
    if (use_chunking_) {
      visitor_->OnLeadingHeader(http::kTransferEncoding, http::kChunked);
    }

    // Add any other last minute headers we need, and close the leading headers
    // section.
    EndOfLeadingHeaders();
  }
  DCHECK(state_ == RECEIVED_DATA);

  // Translate the SPDY data frame into an HTTP data chunk.  However, we must
  // not emit a zero-length chunk, as that would be interpreted as the
  // data-chunks-complete marker.
  if (frame.length() > 0) {
    const base::StringPiece data(frame.payload(), frame.length());
    if (use_chunking_) {
      visitor_->OnDataChunk(data);
    } else {
      visitor_->OnRawData(data);
    }
  }

  // If this is the last frame on this stream, finish off the HTTP request.
  if (HasDataFlagFinSet(frame)) {
    FinishRequest();
  }

  return SPDY_CONVERTER_SUCCESS;
}

// Convert the given SPDY header block (e.g. from a SYN_STREAM or HEADERS
// frame) into HTTP headers by calling OnLeadingHeader on the given visitor.
void SpdyToHttpConverter::GenerateLeadingHeaders(
    const net::SpdyHeaderBlock& block) {
  for (net::SpdyHeaderBlock::const_iterator it = block.begin();
       it != block.end(); ++it) {
    base::StringPiece key = it->first;
    const base::StringPiece value = it->second;

    // Skip SPDY-specific (i.e. non-HTTP) headers.
    if (spdy_version() < 3) {
      if (key == spdy::kSpdy2Method || key == spdy::kSpdy2Scheme ||
          key == spdy::kSpdy2Url || key == spdy::kSpdy2Version) {
        continue;
      }
    } else {
      if (key == spdy::kSpdy3Method || key == spdy::kSpdy3Scheme ||
          key == spdy::kSpdy3Path || key == spdy::kSpdy3Version) {
        continue;
      }
    }

    // Skip headers that are ignored by SPDY.
    if (key == http::kConnection || key == http::kKeepAlive) {
      continue;
    }

    // If the client sent a Content-Length header, take note, so that we'll
    // know not to used chunked encoding.
    if (key == http::kContentLength) {
      use_chunking_ = false;
    }

    // The client shouldn't be sending us a Transfer-Encoding header; it's
    // pretty pointless over SPDY.  If they do send one, just ignore it; we may
    // be overriding it later anyway.
    if (key == http::kTransferEncoding) {
      LOG(WARNING) << "Client sent \"transfer-encoding: " << value
                   << "\" header over SPDY.  Why would they do that?";
      continue;
    }

    // For SPDY v3 and later, we need to convert the SPDY ":host" header to an
    // HTTP "host" header.
    if (spdy_version() >= 3 && key == spdy::kSpdy3Host) {
      key = http::kHost;
    }

    // Take note of whether the client has sent an explicit Accept-Encoding
    // header; if they never do, we'll insert on for them later on.
    if (key == http::kAcceptEncoding) {
      // TODO(mdsteele): Ideally, if the client sends something like
      //   "Accept-Encoding: lzma", we should change it to "Accept-Encoding:
      //   lzma, gzip".  However, that's more work (we might need to parse the
      //   syntax, to make sure we don't naively break it), and isn't
      //   (currently) likely to come up in practice.
      seen_accept_encoding_ = true;
    }

    InsertHeader<&HttpRequestVisitorInterface::OnLeadingHeader>(
        key, value, visitor_);
  }
}

void SpdyToHttpConverter::EndOfLeadingHeaders() {
  // All SPDY clients should be assumed to support both gzip and deflate, even
  // if they don't say so (SPDY draft 2 section 3; SPDY draft 3 section 3.2.1),
  // and indeed some SPDY clients omit the Accept-Encoding header.  So if we
  // didn't see that header yet, add one now so that Apache knows it can use
  // gzip/deflate.
  if (!seen_accept_encoding_) {
    visitor_->OnLeadingHeader(http::kAcceptEncoding, http::kGzipDeflate);
  }

  visitor_->OnLeadingHeadersComplete();
}

void SpdyToHttpConverter::FinishRequest() {
  if (state_ == RECEIVED_DATA) {
    if (use_chunking_) {
      // Indicate that there is no more data coming.
      visitor_->OnDataChunksComplete();

      // Append whatever trailing headers we've buffered, if any.
      if (!trailing_headers_.empty()) {
        for (net::SpdyHeaderBlock::const_iterator it =
                 trailing_headers_.begin();
             it != trailing_headers_.end(); ++it) {
          InsertHeader<&HttpRequestVisitorInterface::OnTrailingHeader>(
              it->first, it->second, visitor_);
        }
        trailing_headers_.clear();
        visitor_->OnTrailingHeadersComplete();
      }
    } else {
      // We don't add to trailing_headers_ if we're in no-chunk mode (we simply
      // ignore trailing HEADERS frames), so trailing_headers_ should still be
      // empty.
      DCHECK(trailing_headers_.empty());
    }
  } else {
    DCHECK(state_ == RECEIVED_SYN_STREAM);
    // We only ever add to trailing_headers_ after receiving at least one data
    // frame, so if we haven't received any data frames then trailing_headers_
    // should still be empty.
    DCHECK(trailing_headers_.empty());

    // There were no data frames in this stream, so we haven't closed the
    // normal (non-trailing) headers yet (if there had been any data frames, we
    // would have closed the normal headers in ConvertDataFrame instead).  Do
    // so now.
    EndOfLeadingHeaders();
  }

  // Indicate that this request is finished.
  visitor_->OnComplete();
  state_ = RECEIVED_FLAG_FIN;
}

}  // namespace mod_spdy
