/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_dom_HttpServer_h
#define mozilla_dom_HttpServer_h

#include "nsISupportsImpl.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "nsITLSServerSocket.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
#include "mozilla/Variant.h"
#include "nsIRequestObserver.h"
#include "mozilla/MozPromise.h"
#include "nsITransportProvider.h"
#include "nsILocalCertService.h"

class nsIX509Cert;

namespace mozilla {
namespace dom {

extern bool
ContainsToken(const nsCString& aList, const nsCString& aToken);

class InternalRequest;
class InternalResponse;

class HttpServerListener
{
public:
  // switch to NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING when that lands
  NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
  NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;

  virtual void OnServerStarted(nsresult aStatus) = 0;
  virtual void OnRequest(InternalRequest* aRequest) = 0;
  virtual void OnWebSocket(InternalRequest* aConnectRequest) = 0;
  virtual void OnServerClose() = 0;
};

class HttpServer final : public nsIServerSocketListener,
                         public nsILocalCertGetCallback
{
public:
  HttpServer();

  NS_DECL_ISUPPORTS
  NS_DECL_NSISERVERSOCKETLISTENER
  NS_DECL_NSILOCALCERTGETCALLBACK

  void Init(int32_t aPort, bool aHttps, HttpServerListener* aListener);

  void SendResponse(InternalRequest* aRequest, InternalResponse* aResponse);
  already_AddRefed<nsITransportProvider>
    AcceptWebSocket(InternalRequest* aConnectRequest,
                    const Optional<nsAString>& aProtocol,
                    ErrorResult& aRv);
  void SendWebSocketResponse(InternalRequest* aConnectRequest,
                             InternalResponse* aResponse);

  void Close();

  void GetCertKey(nsACString& aKey);

  int32_t GetPort()
  {
    return mPort;
  }

private:
  ~HttpServer();

  nsresult StartServerSocket(nsIX509Cert* aCert);
  void NotifyStarted(nsresult aStatus);

  class TransportProvider final : public nsITransportProvider
  {
  public:
    NS_DECL_ISUPPORTS
    NS_DECL_NSITRANSPORTPROVIDER

    void SetTransport(nsISocketTransport* aTransport,
                      nsIAsyncInputStream* aInput,
                      nsIAsyncOutputStream* aOutput);

  private:
    virtual ~TransportProvider();
    void MaybeNotify();

    nsCOMPtr<nsIHttpUpgradeListener> mListener;
    nsCOMPtr<nsISocketTransport> mTransport;
    nsCOMPtr<nsIAsyncInputStream> mInput;
    nsCOMPtr<nsIAsyncOutputStream> mOutput;
  };

  class Connection final : public nsIInputStreamCallback
                         , public nsIOutputStreamCallback
                         , public nsITLSServerSecurityObserver
  {
  public:
    Connection(nsISocketTransport* aTransport,
               HttpServer* aServer,
               nsresult& rv);

    NS_DECL_ISUPPORTS
    NS_DECL_NSIINPUTSTREAMCALLBACK
    NS_DECL_NSIOUTPUTSTREAMCALLBACK
    NS_DECL_NSITLSSERVERSECURITYOBSERVER

    bool TryHandleResponse(InternalRequest* aRequest,
                           InternalResponse* aResponse);
    already_AddRefed<nsITransportProvider>
      HandleAcceptWebSocket(const Optional<nsAString>& aProtocol,
                            ErrorResult& aRv);
    void HandleWebSocketResponse(InternalResponse* aResponse);
    bool HasPendingWebSocketRequest(InternalRequest* aRequest)
    {
      return aRequest == mPendingWebSocketRequest;
    }

    void Close();

    private:
    ~Connection();

    void SetSecurityObserver(bool aListen);

    static nsresult ReadSegmentsFunc(nsIInputStream* aIn,
                                     void* aClosure,
                                     const char* aBuffer,
                                     uint32_t aToOffset,
                                     uint32_t aCount,
                                     uint32_t* aWriteCount);
    nsresult ConsumeInput(const char*& aBuffer,
                          const char* aEnd);
    nsresult ConsumeLine(const char* aBuffer,
                         size_t aLength);
    void MaybeAddPendingHeader();

    void QueueResponse(InternalResponse* aResponse);

    RefPtr<HttpServer> mServer;
    nsCOMPtr<nsISocketTransport> mTransport;
    nsCOMPtr<nsIAsyncInputStream> mInput;
    nsCOMPtr<nsIAsyncOutputStream> mOutput;

    enum { eRequestLine, eHeaders, eBody, ePause } mState;
    RefPtr<InternalRequest> mPendingReq;
    uint32_t mPendingReqVersion;
    nsCString mInputBuffer;
    nsCString mPendingHeaderName;
    nsCString mPendingHeaderValue;
    uint32_t mRemainingBodySize;
    nsCOMPtr<nsIAsyncOutputStream> mCurrentRequestBody;
    bool mCloseAfterRequest;

    typedef Pair<RefPtr<InternalRequest>,
                 RefPtr<InternalResponse>> PendingRequest;
    nsTArray<PendingRequest> mPendingRequests;
    RefPtr<MozPromise<nsresult, bool, false>> mOutputCopy;

    RefPtr<InternalRequest> mPendingWebSocketRequest;
    RefPtr<TransportProvider> mWebSocketTransportProvider;

    struct OutputBuffer {
      nsCString mString;
      nsCOMPtr<nsIInputStream> mStream;
      bool mChunked;
    };

    nsTArray<OutputBuffer> mOutputBuffers;
  };

  friend class Connection;

  RefPtr<HttpServerListener> mListener;
  nsCOMPtr<nsIServerSocket> mServerSocket;
  nsCOMPtr<nsIX509Cert> mCert;

  nsTArray<RefPtr<Connection>> mConnections;

  int32_t mPort;
  bool mHttps;
};

} // namespace dom
} // namespace mozilla

#endif // mozilla_dom_HttpServer_h