// Copyright (c) 2009 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/ocsp/nss_ocsp.h"

#include <certt.h>
#include <certdb.h>
#include <ocsp.h>
#include <nspr.h>
#include <nss.h>
#include <secerr.h>

#include <string>

#include "base/compiler_specific.h"
#include "base/condition_variable.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/singleton.h"
#include "base/thread.h"
#include "base/time.h"
#include "googleurl/src/gurl.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"

namespace {

const int kRecvBufferSize = 4096;

// All OCSP handlers should be called in the context of
// CertVerifier's thread (i.e. worker pool, not on the I/O thread).
// It supports blocking mode only.

SECStatus OCSPCreateSession(const char* host, PRUint16 portnum,
                            SEC_HTTP_SERVER_SESSION* pSession);
SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session,
                               PRPollDesc **pPollDesc);
SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session);

SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session,
                     const char* http_protocol_variant,
                     const char* path_and_query_string,
                     const char* http_request_method,
                     const PRIntervalTime timeout,
                     SEC_HTTP_REQUEST_SESSION* pRequest);
SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request,
                          const char* http_data,
                          const PRUint32 http_data_len,
                          const char* http_content_type);
SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request,
                        const char* http_header_name,
                        const char* http_header_value);
SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request,
                                PRPollDesc** pPollDesc,
                                PRUint16* http_response_code,
                                const char** http_response_content_type,
                                const char** http_response_headers,
                                const char** http_response_data,
                                PRUint32* http_response_data_len);
SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request);

class OCSPInitSingleton : public MessageLoop::DestructionObserver {
 public:
  // Called on IO thread.
  virtual void WillDestroyCurrentMessageLoop() {
    AutoLock autolock(lock_);
    DCHECK_EQ(MessageLoopForIO::current(), io_loop_);
    io_loop_ = NULL;
    request_context_ = NULL;
  };

  // Called from worker thread.
  void PostTaskToIOLoop(
      const tracked_objects::Location& from_here, Task* task) {
    AutoLock autolock(lock_);
    if (io_loop_)
      io_loop_->PostTask(from_here, task);
  }

  // This is static method because it is called before NSS initialization,
  // that is, before OCSPInitSingleton is initialized.
  static void set_url_request_context(URLRequestContext* request_context) {
    request_context_ = request_context;
  }
  static URLRequestContext* url_request_context() {
    return request_context_;
  }

 private:
  friend struct DefaultSingletonTraits<OCSPInitSingleton>;

  OCSPInitSingleton()
      : io_loop_(MessageLoopForIO::current()) {
    DCHECK(io_loop_);
    io_loop_->AddDestructionObserver(this);
    client_fcn_.version = 1;
    SEC_HttpClientFcnV1Struct *ft = &client_fcn_.fcnTable.ftable1;
    ft->createSessionFcn = OCSPCreateSession;
    ft->keepAliveSessionFcn = OCSPKeepAliveSession;
    ft->freeSessionFcn = OCSPFreeSession;
    ft->createFcn = OCSPCreate;
    ft->setPostDataFcn = OCSPSetPostData;
    ft->addHeaderFcn = OCSPAddHeader;
    ft->trySendAndReceiveFcn = OCSPTrySendAndReceive;
    ft->cancelFcn = NULL;
    ft->freeFcn = OCSPFree;
    SECStatus status = SEC_RegisterDefaultHttpClient(&client_fcn_);
    if (status != SECSuccess) {
      NOTREACHED() << "Error initializing OCSP: " << PR_GetError();
    }
  }

  virtual ~OCSPInitSingleton() {
    // IO thread was already deleted before the singleton is deleted
    // in AtExitManager.
    AutoLock autolock(lock_);
    DCHECK(!io_loop_);
    DCHECK(!request_context_);
  }

  SEC_HttpClientFcn client_fcn_;

  // |lock_| protects |io_loop_|.
  Lock lock_;
  // I/O thread.
  MessageLoop* io_loop_;  // I/O thread
  // URLRequestContext for OCSP handlers.
  static URLRequestContext* request_context_;

  DISALLOW_COPY_AND_ASSIGN(OCSPInitSingleton);
};

URLRequestContext* OCSPInitSingleton::request_context_ = NULL;

// Concrete class for SEC_HTTP_REQUEST_SESSION.
// Public methods except virtual methods of URLRequest::Delegate (On* methods)
// run on certificate verifier thread (worker thread).
// Virtual methods of URLRequest::Delegate and private methods run
// on IO thread.
class OCSPRequestSession
    : public base::RefCountedThreadSafe<OCSPRequestSession>,
      public URLRequest::Delegate,
      public MessageLoop::DestructionObserver {
 public:
  OCSPRequestSession(const GURL& url,
                     const char* http_request_method,
                     base::TimeDelta timeout)
      : url_(url),
        http_request_method_(http_request_method),
        timeout_(timeout),
        request_(NULL),
        buffer_(new net::IOBuffer(kRecvBufferSize)),
        response_code_(-1),
        cv_(&lock_),
        io_loop_(NULL),
        finished_(false) {}

  void SetPostData(const char* http_data, PRUint32 http_data_len,
                   const char* http_content_type) {
    upload_content_.assign(http_data, http_data_len);
    upload_content_type_.assign(http_content_type);
  }

  void AddHeader(const char* http_header_name, const char* http_header_value) {
    if (!extra_request_headers_.empty())
      extra_request_headers_ += "\r\n";
    StringAppendF(&extra_request_headers_,
                  "%s: %s", http_header_name, http_header_value);
  }

  void Start() {
    // At this point, it runs on worker thread.
    // |io_loop_| was initialized to be NULL in constructor, and
    // set only in StartURLRequest, so no need to lock |lock_| here.
    DCHECK(!io_loop_);
    Singleton<OCSPInitSingleton>()->PostTaskToIOLoop(
        FROM_HERE,
        NewRunnableMethod(this, &OCSPRequestSession::StartURLRequest));
  }

  bool Started() const {
    return request_ != NULL;
  }

  void Cancel() {
    // IO thread may set |io_loop_| to NULL, so protect by |lock_|.
    AutoLock autolock(lock_);
    CancelLocked();
  }

  bool Finished() const {
    AutoLock autolock(lock_);
    return finished_;
  }

  bool Wait() {
    base::TimeDelta timeout = timeout_;
    AutoLock autolock(lock_);
    while (!finished_) {
      base::TimeTicks last_time = base::TimeTicks::Now();
      cv_.TimedWait(timeout);
      // Check elapsed time
      base::TimeDelta elapsed_time = base::TimeTicks::Now() - last_time;
      timeout -= elapsed_time;
      if (timeout < base::TimeDelta()) {
        LOG(INFO) << "OCSP Timed out";
        if (!finished_)
          CancelLocked();
        break;
      }
    }
    return finished_;
  }

  const GURL& url() const {
    return url_;
  }

  const std::string& http_request_method() const {
    return http_request_method_;
  }

  base::TimeDelta timeout() const {
    return timeout_;
  }

  PRUint16 http_response_code() const {
    DCHECK(finished_);
    return response_code_;
  }

  const std::string& http_response_content_type() const {
    DCHECK(finished_);
    return response_content_type_;
  }

  const std::string& http_response_headers() const {
    DCHECK(finished_);
    return response_headers_->raw_headers();
  }

  const std::string& http_response_data() const {
    DCHECK(finished_);
    return data_;
  }

  virtual void OnResponseStarted(URLRequest* request) {
    DCHECK_EQ(request, request_);
    DCHECK_EQ(MessageLoopForIO::current(), io_loop_);

    int bytes_read = 0;
    if (request->status().is_success()) {
      response_code_ = request_->GetResponseCode();
      response_headers_ = request_->response_headers();
      response_headers_->GetMimeType(&response_content_type_);
      request_->Read(buffer_, kRecvBufferSize, &bytes_read);
    }
    OnReadCompleted(request_, bytes_read);
  }

  virtual void OnReadCompleted(URLRequest* request, int bytes_read) {
    DCHECK_EQ(request, request_);
    DCHECK_EQ(MessageLoopForIO::current(), io_loop_);

    do {
      if (!request_->status().is_success() || bytes_read <= 0)
        break;
      data_.append(buffer_->data(), bytes_read);
    } while (request_->Read(buffer_, kRecvBufferSize, &bytes_read));

    if (!request_->status().is_io_pending()) {
      delete request_;
      request_ = NULL;
      io_loop_->RemoveDestructionObserver(this);
      {
        AutoLock autolock(lock_);
        finished_ = true;
        io_loop_ = NULL;
      }
      cv_.Signal();
      Release();  // Balanced with StartURLRequest().
    }
  }

  virtual void WillDestroyCurrentMessageLoop() {
    DCHECK_EQ(MessageLoopForIO::current(), io_loop_);
    {
      AutoLock autolock(lock_);
      io_loop_ = NULL;
    }
    CancelURLRequest();
  }

 private:
  friend class base::RefCountedThreadSafe<OCSPRequestSession>;

  virtual ~OCSPRequestSession() {
    // When this destructor is called, there should be only one thread that has
    // a reference to this object, and so that thread doesn't need to lock
    // |lock_| here.
    DCHECK(!request_);
    DCHECK(!io_loop_);
  }

  // Must call this method while holding |lock_|.
  void CancelLocked() {
    lock_.AssertAcquired();
    if (io_loop_) {
      io_loop_->PostTask(
          FROM_HERE,
          NewRunnableMethod(this, &OCSPRequestSession::CancelURLRequest));
    }
  }

  void StartURLRequest() {
    DCHECK(!request_);

    URLRequestContext* url_request_context =
        OCSPInitSingleton::url_request_context();
    if (url_request_context == NULL)
      return;

    {
      AutoLock autolock(lock_);
      DCHECK(!io_loop_);
      io_loop_ = MessageLoopForIO::current();
      io_loop_->AddDestructionObserver(this);
    }

    request_ = new URLRequest(url_, this);
    request_->set_context(url_request_context);
    // To meet the privacy requirements of off-the-record mode.
    request_->set_load_flags(
        net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SAVE_COOKIES |
        net::LOAD_DO_NOT_SEND_COOKIES);

    if (http_request_method_ == "POST") {
      DCHECK(!upload_content_.empty());
      DCHECK(!upload_content_type_.empty());

      request_->set_method("POST");
      if (!extra_request_headers_.empty())
        extra_request_headers_ += "\r\n";
      StringAppendF(&extra_request_headers_,
                    "Content-Type: %s", upload_content_type_.c_str());
      request_->AppendBytesToUpload(upload_content_.data(),
                                    static_cast<int>(upload_content_.size()));
    }
    if (!extra_request_headers_.empty())
      request_->SetExtraRequestHeaders(extra_request_headers_);

    request_->Start();
    AddRef();  // Release after |request_| deleted.
  }

  void CancelURLRequest() {
#ifndef NDEBUG
    {
      AutoLock autolock(lock_);
      if (io_loop_)
        DCHECK_EQ(MessageLoopForIO::current(), io_loop_);
    }
#endif
    if (request_) {
      request_->Cancel();
      delete request_;
      request_ = NULL;
      // |io_loop_| may be NULL here if it called from
      // WillDestroyCurrentMessageLoop().
      if (io_loop_)
        io_loop_->RemoveDestructionObserver(this);
      {
        AutoLock autolock(lock_);
        finished_ = true;
        io_loop_ = NULL;
      }
      cv_.Signal();
      Release();  // Balanced with StartURLRequest().
    }
  }

  GURL url_;                      // The URL we eventually wound up at
  std::string http_request_method_;
  base::TimeDelta timeout_;       // The timeout for OCSP
  URLRequest* request_;           // The actual request this wraps
  scoped_refptr<net::IOBuffer> buffer_;  // Read buffer
  std::string extra_request_headers_;  // Extra headers for the request, if any
  std::string upload_content_;    // HTTP POST payload
  std::string upload_content_type_;  // MIME type of POST payload

  int response_code_;             // HTTP status code for the request
  std::string response_content_type_;
  scoped_refptr<net::HttpResponseHeaders> response_headers_;
  std::string data_;              // Results of the requst

  // |lock_| protects |finished_| and |io_loop_|.
  mutable Lock lock_;
  ConditionVariable cv_;

  MessageLoop* io_loop_;          // Message loop of the IO thread
  bool finished_;

  DISALLOW_COPY_AND_ASSIGN(OCSPRequestSession);
};

// Concrete class for SEC_HTTP_SERVER_SESSION.
class OCSPServerSession {
 public:
  OCSPServerSession(const char* host, PRUint16 port)
      : host_(host), port_(port) {}
  ~OCSPServerSession() {}

  OCSPRequestSession* CreateRequest(const char* http_protocol_variant,
                                    const char* path_and_query_string,
                                    const char* http_request_method,
                                    const PRIntervalTime timeout) {
    // We dont' support "https" because we haven't thought about
    // whether it's safe to re-enter this code from talking to an OCSP
    // responder over SSL.
    if (strcmp(http_protocol_variant, "http") != 0)
      return NULL;

    // TODO(ukai): If |host| is an IPv6 literal, we need to quote it with
    //  square brackets [].
    std::string url_string(StringPrintf("%s://%s:%d%s",
                                        http_protocol_variant,
                                        host_.c_str(),
                                        port_,
                                        path_and_query_string));
    LOG(INFO) << "URL [" << url_string << "]";
    GURL url(url_string);
    return new OCSPRequestSession(
        url, http_request_method,
        base::TimeDelta::FromMilliseconds(PR_IntervalToMilliseconds(timeout)));
  }


 private:
  std::string host_;
  int port_;

  DISALLOW_COPY_AND_ASSIGN(OCSPServerSession);
};


// OCSP Http Client functions.
// Our Http Client functions operate in blocking mode.
SECStatus OCSPCreateSession(const char* host, PRUint16 portnum,
                            SEC_HTTP_SERVER_SESSION* pSession) {
  LOG(INFO) << "OCSP create session: host=" << host << " port=" << portnum;
  DCHECK(!MessageLoop::current());
  if (OCSPInitSingleton::url_request_context() == NULL) {
    LOG(ERROR) << "No URLRequestContext for OCSP handler.";
    return SECFailure;
  }
  *pSession = new OCSPServerSession(host, portnum);
  return SECSuccess;
}

SECStatus OCSPKeepAliveSession(SEC_HTTP_SERVER_SESSION session,
                               PRPollDesc **pPollDesc) {
  LOG(INFO) << "OCSP keep alive";
  DCHECK(!MessageLoop::current());
  if (pPollDesc)
    *pPollDesc = NULL;
  return SECSuccess;
}

SECStatus OCSPFreeSession(SEC_HTTP_SERVER_SESSION session) {
  LOG(INFO) << "OCSP free session";
  DCHECK(!MessageLoop::current());
  delete reinterpret_cast<OCSPServerSession*>(session);
  return SECSuccess;
}

SECStatus OCSPCreate(SEC_HTTP_SERVER_SESSION session,
                     const char* http_protocol_variant,
                     const char* path_and_query_string,
                     const char* http_request_method,
                     const PRIntervalTime timeout,
                     SEC_HTTP_REQUEST_SESSION* pRequest) {
  LOG(INFO) << "OCSP create protocol=" << http_protocol_variant
            << " path_and_query=" << path_and_query_string
            << " http_request_method=" << http_request_method
            << " timeout=" << timeout;
  DCHECK(!MessageLoop::current());
  OCSPServerSession* ocsp_session =
      reinterpret_cast<OCSPServerSession*>(session);

  OCSPRequestSession* req = ocsp_session->CreateRequest(http_protocol_variant,
                                                        path_and_query_string,
                                                        http_request_method,
                                                        timeout);
  SECStatus rv = SECFailure;
  if (req) {
    req->AddRef();  // Release in OCSPFree().
    rv = SECSuccess;
  }
  *pRequest = req;
  return rv;
}

SECStatus OCSPSetPostData(SEC_HTTP_REQUEST_SESSION request,
                          const char* http_data,
                          const PRUint32 http_data_len,
                          const char* http_content_type) {
  LOG(INFO) << "OCSP set post data len=" << http_data_len;
  DCHECK(!MessageLoop::current());
  OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);

  req->SetPostData(http_data, http_data_len, http_content_type);
  return SECSuccess;
}

SECStatus OCSPAddHeader(SEC_HTTP_REQUEST_SESSION request,
                        const char* http_header_name,
                        const char* http_header_value) {
  LOG(INFO) << "OCSP add header name=" << http_header_name
            << " value=" << http_header_value;
  DCHECK(!MessageLoop::current());
  OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);

  req->AddHeader(http_header_name, http_header_value);
  return SECSuccess;
}

// Sets response of |req| in the output parameters.
// It is helper routine for OCSP trySendAndReceiveFcn.
// |http_response_data_len| could be used as input parameter.  If it has
// non-zero value, it is considered as maximum size of |http_response_data|.
bool OCSPSetResponse(OCSPRequestSession* req,
                     PRUint16* http_response_code,
                     const char** http_response_content_type,
                     const char** http_response_headers,
                     const char** http_response_data,
                     PRUint32* http_response_data_len) {
  DCHECK(req->Finished());
  const std::string& data = req->http_response_data();
  if (http_response_data_len && *http_response_data_len) {
    if (*http_response_data_len < data.size()) {
      LOG(ERROR) << "data size too large: " << *http_response_data_len
                 << " < " << data.size();
      *http_response_data_len = data.size();
      return false;
    }
  }
  LOG(INFO) << "OCSP response "
            << " response_code=" << req->http_response_code()
            << " content_type=" << req->http_response_content_type()
            << " header=" << req->http_response_headers()
            << " data_len=" << data.size();
  if (http_response_code)
    *http_response_code = req->http_response_code();
  if (http_response_content_type)
    *http_response_content_type = req->http_response_content_type().c_str();
  if (http_response_headers)
    *http_response_headers = req->http_response_headers().c_str();
  if (http_response_data)
    *http_response_data = data.data();
  if (http_response_data_len)
    *http_response_data_len = data.size();
  return true;
}

SECStatus OCSPTrySendAndReceive(SEC_HTTP_REQUEST_SESSION request,
                                PRPollDesc** pPollDesc,
                                PRUint16* http_response_code,
                                const char** http_response_content_type,
                                const char** http_response_headers,
                                const char** http_response_data,
                                PRUint32* http_response_data_len) {
  LOG(INFO) << "OCSP try send and receive";
  DCHECK(!MessageLoop::current());
  OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
  // We support blocking mode only.
  if (pPollDesc)
    *pPollDesc = NULL;

  if (req->Started() || req->Finished()) {
    // We support blocking mode only, so this function shouldn't be called
    // again when req has stareted or finished.
    NOTREACHED();
    goto failed;
  }
  req->Start();
  if (!req->Wait())
    goto failed;

  // If the response code is -1, the request failed and there is no response.
  if (req->http_response_code() == static_cast<PRUint16>(-1))
    goto failed;

  return OCSPSetResponse(
      req, http_response_code,
      http_response_content_type,
      http_response_headers,
      http_response_data,
      http_response_data_len) ? SECSuccess : SECFailure;

 failed:
  if (http_response_data_len) {
    // We must always set an output value, even on failure.  The output value 0
    // means the failure was unrelated to the acceptable response data length.
    *http_response_data_len = 0;
  }
  return SECFailure;
}

SECStatus OCSPFree(SEC_HTTP_REQUEST_SESSION request) {
  LOG(INFO) << "OCSP free";
  DCHECK(!MessageLoop::current());
  OCSPRequestSession* req = reinterpret_cast<OCSPRequestSession*>(request);
  req->Cancel();
  req->Release();
  return SECSuccess;
}

}  // anonymous namespace

namespace net {

void EnsureOCSPInit() {
  Singleton<OCSPInitSingleton>::get();
}

// This function would be called before NSS initialization.
void SetURLRequestContextForOCSP(URLRequestContext* request_context) {
  OCSPInitSingleton::set_url_request_context(request_context);
}

URLRequestContext* GetURLRequestContextForOCSP() {
  return OCSPInitSingleton::url_request_context();
}

}  // namespace net
