/*
 * libjingle
 * Copyright 2004--2005, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products 
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef OSX
#include <errno.h>
#endif

#ifdef WIN32
#include "talk/base/win32.h"
#else  // !WIN32
#define SEC_E_CERT_EXPIRED (-2146893016)
#endif  // !WIN32

#include "talk/base/common.h"
#include "talk/base/httpbase.h"
#include "talk/base/logging.h"
#include "talk/base/socket.h"
#include "talk/base/stringutils.h"

namespace talk_base {

//////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////

bool MatchHeader(const char* str, size_t len, HttpHeader header) {
  const char* const header_str = ToString(header);
  const size_t header_len = strlen(header_str);
  return (len == header_len) && (_strnicmp(str, header_str, header_len) == 0);
}

//////////////////////////////////////////////////////////////////////
// HttpParser
//////////////////////////////////////////////////////////////////////

HttpParser::HttpParser() {
  reset();
}

HttpParser::~HttpParser() {
}

void 
HttpParser::reset() {
  state_ = ST_LEADER;
  chunked_ = false;
  data_size_ = SIZE_UNKNOWN;
}

bool
HttpParser::process(const char* buffer, size_t len, size_t& processed,
                    HttpError& err) {
  processed = 0;
  err = HE_NONE;

  if (state_ >= ST_COMPLETE) {
    ASSERT(false);
    return false;
  }

  //printf("chobits: HttpParser::process: %d\n", len);
  total_size = len; //add by chobits
  
  while (true) {
    if (state_ < ST_DATA) {
      size_t pos = processed;
      while ((pos < len) && (buffer[pos] != '\n')) {
        pos += 1;
      }
      if (pos >= len) {
        break; // don't have a full header
      }
      const char* line = buffer + processed;
      size_t len = (pos - processed);
      processed = pos + 1;
      while ((len > 0) && isspace(static_cast<unsigned char>(line[len-1]))) {
        len -= 1;
      }
      if (!process_line(line, len, err)) {
        return false; // no more processing
      }
    } else if (data_size_ == 0) {
      if (chunked_) {
        state_ = ST_CHUNKTERM;
      } else {
        return false;
      }
    } else {
      size_t available = len - processed;
      if (available <= 0) {
        break; // no more data
      }
      if ((data_size_ != SIZE_UNKNOWN) && (available > data_size_)) {
        available = data_size_;
      }
      size_t read = 0;
      err = onHttpRecvData(buffer + processed, available, read);
      if (err != HE_NONE) {
        return false; // error occurred
      }
      processed += read;
      if (data_size_ != SIZE_UNKNOWN) {
        data_size_ -= read;
      }
    }
  }

  return true;
}

bool
HttpParser::process_line(const char* line, size_t len, HttpError& err) {
  switch (state_) {
  case ST_LEADER:
    state_ = ST_HEADERS;
    err = onHttpRecvLeader(line, len);
    break;

  case ST_HEADERS:
	//printf("<HEADER %d>\n", len);
	total_size -= len; //add by chobits
    if (len > 0) {
      const char* value = strchrn(line, len, ':');
      if (!value) {
        err = HE_PROTOCOL;
        break;
      }
      size_t nlen = (value - line);
      const char* eol = line + len;
      do {
        value += 1;
      } while ((value < eol) && isspace(static_cast<unsigned char>(*value)));
      size_t vlen = eol - value;	  
      if (MatchHeader(line, nlen, HH_CONTENT_LENGTH)) {		  
        //if (sscanf(value, "%d", &data_size_) != 1) {
		unsigned int data_size_temp;
        if (sscanf(value, "%d", &data_size_temp) != 1) {
          err = HE_PROTOCOL;
          break;
        }
		data_size_ = data_size_temp;
		//printf("data_size_ %d\n", data_size_);
      } else if (MatchHeader(line, nlen, HH_TRANSFER_ENCODING)) {		  
        if ((vlen == 7) && (_strnicmp(value, "chunked", 7) == 0)) {
          chunked_ = true;
        } else if ((vlen == 8) && (_strnicmp(value, "identity", 8) == 0)) {
          chunked_ = false;
        } else {
          err = HE_PROTOCOL;
          break;
        }
      }
      err = onHttpRecvHeader(line, nlen, value, vlen);
    } else {
      state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
	  //fix: http response header does not contain content-length
	  //add by chobits
	  if (data_size_ == SIZE_UNKNOWN) {
		//printf("chobits: assign data_size -> %d\n", total_size);
		data_size_ = total_size;
	  }
      err = onHttpRecvHeaderComplete(chunked_, data_size_);
    }
    break;

  case ST_CHUNKSIZE:
	//printf("<CHUNKSIZE>%s\n", line);
    if (len > 0) {
      char* ptr = NULL;
      data_size_ = strtoul(line, &ptr, 16);
      if (ptr != line + len) {
        err = HE_PROTOCOL;
        break;
      }
      state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA;
    } else {
      err = HE_PROTOCOL;
    }
    break;

  case ST_CHUNKTERM:
    if (len > 0) {
      err = HE_PROTOCOL;
    } else {
      state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
    }
    break;

  case ST_TRAILERS:
    if (len == 0) {
      return false;
    }
    // err = onHttpRecvTrailer();
    break;

  default:
    break;
  }

  return (err == HE_NONE);
}
 
void 
HttpParser::end_of_input() {
  if ((state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN)) {
    complete(HE_NONE);
  } else {
    complete(HE_DISCONNECTED);
  }
}

void 
HttpParser::complete(HttpError err) {
  if (state_ < ST_COMPLETE) {
    state_ = ST_COMPLETE;
    onHttpRecvComplete(err);
  }
}

//////////////////////////////////////////////////////////////////////
// HttpBase
//////////////////////////////////////////////////////////////////////

HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL),
                       stream_(NULL) {
}

HttpBase::~HttpBase() {
}

bool
HttpBase::isConnected() const {
  return (stream_ != NULL) && (stream_->GetState() == SS_OPEN);
}

bool
HttpBase::attach(StreamInterface* stream) {
  if ((mode_ != HM_NONE) || (stream_ != NULL) || (stream == NULL)) {
    ASSERT(false);
    return false;
  }
  stream_ = stream;
  stream_->SignalEvent.connect(this, &HttpBase::OnEvent);
  mode_ = (stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE;
  return true;
}

StreamInterface*
HttpBase::detach() {
  if (mode_ != HM_NONE) {
    ASSERT(false);
    return NULL;
  }
  StreamInterface* stream = stream_;
  stream_ = NULL;
  if (stream) {
    stream->SignalEvent.disconnect(this);
  }
  return stream;
}

/*
bool
HttpBase::accept(PNSocket& socket) {
  if (mode_ != HM_NONE) {
    ASSERT(false);
    return false;
  }
  
  return socket.accept(stream_);
}

void
HttpBase::connect(const SocketAddress& addr) {
  if (mode_ != HM_NONE) {
    ASSERT(false);
    return;
  }

  mode_ = HM_CONNECT;

  SocketAddress local;
  if (!stream_.connect(local, addr)  && !stream_.isBlocking()) {
    onSocketConnect(&stream_, stream_.getError());
  }
}
*/
void
HttpBase::send(HttpData* data) {
  if (mode_ != HM_NONE) {
    ASSERT(false);
    return;
  } else if (!isConnected()) {
    OnEvent(stream_, SE_CLOSE, HE_DISCONNECTED);
    return;
  }
  
  mode_ = HM_SEND;
  data_ = data;
  len_ = 0;
  ignore_data_ = chunk_data_ = false;

  std::string encoding;
  if (data_->hasHeader(HH_TRANSFER_ENCODING, &encoding)
      && (encoding == "chunked")) {
    chunk_data_ = true;
  }
  
  len_ = data_->formatLeader(buffer_, sizeof(buffer_));
  len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
  header_ = data_->begin();
  queue_headers();

  OnEvent(stream_, SE_WRITE, 0);
}

void
HttpBase::recv(HttpData* data) {
  if (mode_ != HM_NONE) {
    ASSERT(false);
    return;
  } else if (!isConnected()) {
    OnEvent(stream_, SE_CLOSE, HE_DISCONNECTED);
    return;
  }
  
  mode_ = HM_RECV;
  data_ = data;
  len_ = 0;
  ignore_data_ = chunk_data_ = false;
  
  reset();
  OnEvent(stream_, SE_READ, 0);
}

void
HttpBase::abort(HttpError err) {
  if (mode_ != HM_NONE) {
    if (stream_ != NULL) {
      stream_->Close();
    }
    do_complete(err);
  }
}

void
HttpBase::flush_data() {
  while (true) {
    for (size_t start = 0; start < len_; ) {
      size_t written;
      int error;
      StreamResult result = stream_->Write(buffer_ + start, len_ - start,
                                           &written, &error);
      if (result == SR_SUCCESS) {
        //LOG_F(LS_INFO) << "wrote " << res << " bytes";
        start += written;
        continue;
      } else if (result == SR_BLOCK) {
        //LOG_F(LS_INFO) << "blocking";
        len_ -= start;
        memmove(buffer_, buffer_ + start, len_);
        return;
      } else {
        ASSERT(result == SR_ERROR);
        LOG_F(LS_ERROR) << "error";
        OnEvent(stream_, SE_CLOSE, error);
        return;
      }
    }
    len_ = 0;

    // Check for more headers
    if (header_ != data_->end()) {
      queue_headers();
      continue;
    }

    // Check for document data
    if (!data_->document.get())
      break;

    size_t offset = 0, reserve = 0;
    if (chunk_data_) {
      // Reserve 10 characters at the start for 8-byte hex value and \r\n
      offset = 10;
      // ... and 2 characters at the end for \r\n
      reserve = offset + 2;
      ASSERT(reserve < sizeof(buffer_));
    }

    int error = 0;
    StreamResult result = data_->document->Read(buffer_ + offset,
                                                sizeof(buffer_) - reserve,
                                                &len_, &error);
    if (result == SR_SUCCESS) {
      if (!chunk_data_)
        continue;

      // Prepend the length and append \r\n
      sprintfn(buffer_, offset, "%.*x", (offset - 2), len_);
      memcpy(buffer_ + offset - 2, "\r\n", 2);
      memcpy(buffer_ + offset + len_, "\r\n", 2);
      ASSERT(len_ + reserve <= sizeof(buffer_));
      len_ += reserve;
    } else if (result == SR_EOS) {
      if (!chunk_data_)
        break;

      // Append the empty chunk and empty trailers, then turn off chunking.
      len_ = sprintfn(buffer_, sizeof(buffer_), "0\r\n\r\n");
      chunk_data_ = false;
    } else {
      LOG_F(LS_ERROR) << "Read error: " << error;
      do_complete(HE_STREAM);
      return;
    }
  }

  do_complete();
}

void
HttpBase::queue_headers() {
  while (header_ != data_->end()) {
    size_t len = sprintfn(buffer_ + len_, sizeof(buffer_) - len_,
                          "%.*s: %.*s\r\n",
                          header_->first.size(), header_->first.data(),
                          header_->second.size(), header_->second.data());
    if (len_ + len < sizeof(buffer_) - 3) {
      len_ += len;
      ++header_;
    } else if (len_ == 0) {
      LOG(WARNING) << "discarding header that is too long: " << header_->first;
      ++header_;
    } else {
      break;
    }
  }
  if (header_ == data_->end()) {
    len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
  }
}

void
HttpBase::do_complete(HttpError err) {
  ASSERT(mode_ != HM_NONE);
  HttpMode mode = mode_;
  mode_ = HM_NONE;
  data_ = NULL;
  if (notify_) {
	  notify_->onHttpComplete(mode, err);
  }
}

void
HttpBase::OnEvent(StreamInterface* stream, int events, int error) {
  if ((events & SE_OPEN) && (mode_ == HM_CONNECT)) {
    do_complete();
    return;
  }

  if ((events & SE_WRITE) && (mode_ == HM_SEND)) {
    flush_data();
    return;
  }

  if ((events & SE_READ) && (mode_ == HM_RECV)) {
    // Do to the latency between receiving read notifications from
    // pseudotcpchannel, we rely on repeated calls to read in order to acheive
    // ideal throughput.  The number of reads is limited to prevent starving
    // the caller.
    size_t loop_count = 0;
    const size_t kMaxReadCount = 20;
    while (true) {
      if (len_ >= sizeof(buffer_)) {
        do_complete(HE_OVERFLOW);
        return;
      }
      size_t read;
      int error;
      StreamResult result = stream_->Read(buffer_ + len_,
                                          sizeof(buffer_) - len_,
                                          &read, &error);
      if ((result == SR_BLOCK) || (result == SR_EOS))
        return;
      if (result == SR_ERROR) {
        OnEvent(stream_, SE_CLOSE, error);
        return;
      }
      ASSERT(result == SR_SUCCESS);
      //LOG(INFO) << "HttpBase << " << std::string(buffer_ + len_, res);
      len_ += read;
      HttpError herr;
      bool more = process(buffer_, len_, read, herr);
      len_ -= read;
      memcpy(buffer_, buffer_ + read, len_);
      if (!more) {
        complete(herr);
        return;
      }
      if (++loop_count > kMaxReadCount) {
        LOG_F(LS_WARNING) << "danger of starvation";
        break;
      }
    }
    return;
  }

  if ((events & SE_CLOSE) == 0)
    return;

  if (stream_ != NULL) {
    stream_->Close();
  }
  HttpError herr;
  // TODO: Pass through errors instead of translating them?
  if (error == 0) {
    herr = HE_DISCONNECTED;
  } else if (error == SOCKET_EACCES) {
    herr = HE_AUTH;
  } else if (error == SEC_E_CERT_EXPIRED) {
    herr = HE_CERTIFICATE_EXPIRED;
  } else {
    LOG_F(LS_ERROR) << "SE_CLOSE error: " << error;
    herr = HE_SOCKET;
  }
  if ((mode_ == HM_RECV) && (error == HE_NONE)) {
    end_of_input();
  } else if (mode_ != HM_NONE) {
    do_complete(mkerr(herr, HE_DISCONNECTED));
  } else if (notify_) {
    notify_->onHttpClosed(mkerr(herr, HE_DISCONNECTED));
  }
}

//
// HttpParser Implementation
//

HttpError
HttpBase::onHttpRecvLeader(const char* line, size_t len) {
  return data_->parseLeader(line, len);
}

HttpError
HttpBase::onHttpRecvHeader(const char* name, size_t nlen, const char* value,
                           size_t vlen) {
  std::string sname(name, nlen), svalue(value, vlen);
  data_->addHeader(sname, svalue);
  //LOG(INFO) << sname << ": " << svalue;
  return HE_NONE;
}
 
HttpError
HttpBase::onHttpRecvHeaderComplete(bool chunked, size_t& data_size) {
  return notify_ ? notify_->onHttpHeaderComplete(chunked, data_size) : HE_NONE;
}

HttpError
HttpBase::onHttpRecvData(const char* data, size_t len, size_t& read) {
  if (ignore_data_ || !data_->document.get()) {
    read = len;
    return HE_NONE;  
  }
  int error = 0;
  switch (data_->document->Write(data, len, &read, &error)) {
    case SR_SUCCESS:
      return HE_NONE;
    case SR_EOS:
    case SR_BLOCK:
      LOG_F(LS_ERROR) << "Write EOS or block";
      return HE_STREAM;
  }
  LOG_F(LS_ERROR) << "Write error: " << error;
  return HE_STREAM;
}

void
HttpBase::onHttpRecvComplete(HttpError err) {
  do_complete(err);
}

} // namespace talk_base
