Exemple #1
0
already_AddRefed<nsITransportProvider>
HttpServer::Connection::HandleAcceptWebSocket(const Optional<nsAString>& aProtocol,
                                              ErrorResult& aRv)
{
  MOZ_ASSERT(mPendingWebSocketRequest);

  RefPtr<InternalResponse> response =
    new InternalResponse(101, NS_LITERAL_CSTRING("Switching Protocols"));

  InternalHeaders* headers = response->Headers();
  headers->Set(NS_LITERAL_CSTRING("Upgrade"),
               NS_LITERAL_CSTRING("websocket"),
               aRv);
  headers->Set(NS_LITERAL_CSTRING("Connection"),
               NS_LITERAL_CSTRING("Upgrade"),
               aRv);
  if (aProtocol.WasPassed()) {
    NS_ConvertUTF16toUTF8 protocol(aProtocol.Value());
    nsAutoCString reqProtocols;
    mPendingWebSocketRequest->Headers()->
      GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), reqProtocols, aRv);
    if (!ContainsToken(reqProtocols, protocol)) {
      // Should throw a better error here
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }

    headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
                 protocol, aRv);
  }

  nsAutoCString key, hash;
  mPendingWebSocketRequest->Headers()->
    GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Key"), key, aRv);
  nsresult rv = mozilla::net::CalculateWebSocketHashedSecret(key, hash);
  if (NS_FAILED(rv)) {
    aRv.Throw(rv);
    return nullptr;
  }
  headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Accept"), hash, aRv);

  nsAutoCString extensions, negotiatedExtensions;
  mPendingWebSocketRequest->Headers()->
    GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions, aRv);
  mozilla::net::ProcessServerWebSocketExtensions(extensions,
                                                 negotiatedExtensions);
  if (!negotiatedExtensions.IsEmpty()) {
    headers->Set(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
                 negotiatedExtensions, aRv);
  }

  RefPtr<TransportProvider> result = new TransportProvider();
  mWebSocketTransportProvider = result;

  QueueResponse(response);

  return result.forget();
}
Exemple #2
0
static bool
IsWebSocketRequest(InternalRequest* aRequest, uint32_t aHttpVersion)
{
  if (aHttpVersion < 1) {
    return false;
  }

  nsAutoCString str;
  aRequest->GetMethod(str);
  if (!str.EqualsLiteral("GET")) {
    return false;
  }

  InternalHeaders* headers = aRequest->Headers();
  ErrorResult res;

  headers->GetFirst(NS_LITERAL_CSTRING("upgrade"), str, res);
  MOZ_ASSERT(!res.Failed());
  if (!str.EqualsLiteral("websocket")) {
    return false;
  }

  headers->GetFirst(NS_LITERAL_CSTRING("connection"), str, res);
  MOZ_ASSERT(!res.Failed());
  if (!ContainsToken(str, NS_LITERAL_CSTRING("Upgrade"))) {
    return false;
  }

  headers->GetFirst(NS_LITERAL_CSTRING("sec-websocket-key"), str, res);
  MOZ_ASSERT(!res.Failed());
  nsAutoCString binary;
  if (NS_FAILED(Base64Decode(str, binary)) || binary.Length() != 16) {
    return false;
  }

  nsresult rv;
  headers->GetFirst(NS_LITERAL_CSTRING("sec-websocket-version"), str, res);
  MOZ_ASSERT(!res.Failed());
  if (str.ToInteger(&rv) != 13 || NS_FAILED(rv)) {
    return false;
  }

  return true;
}
already_AddRefed<nsITransportProvider>
FlyWebPublishedServerChild::OnWebSocketAcceptInternal(InternalRequest* aRequest,
                                                      const Optional<nsAString>& aProtocol,
                                                      ErrorResult& aRv)
{
  LOG_I("FlyWebPublishedServerChild::OnWebSocketAcceptInternal(%p)", this);

  if (!mActorExists) {
    LOG_I("FlyWebPublishedServerChild::OnWebSocketAcceptInternal(%p) - No actor!", this);
    return nullptr;
  }

  uint64_t id = mPendingRequests.Get(aRequest);
  MOZ_ASSERT(id);
  mPendingRequests.Remove(aRequest);

  RefPtr<TransportProviderChild> provider;
  mPendingTransportProviders.Remove(id, getter_AddRefs(provider));

  nsString protocol;
  if (aProtocol.WasPassed()) {
    protocol = aProtocol.Value();

    nsAutoCString reqProtocols;
    aRequest->Headers()->
      GetFirst(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), reqProtocols, aRv);
    if (!ContainsToken(reqProtocols, NS_ConvertUTF16toUTF8(protocol))) {
      // Should throw a better error here
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }
  } else {
    protocol.SetIsVoid(true);
  }

  Unused << SendWebSocketAccept(protocol, id);

  return provider.forget();
}
Exemple #4
0
nsresult
HttpServer::Connection::ConsumeLine(const char* aBuffer,
                                    size_t aLength)
{
  MOZ_ASSERT(mState == eRequestLine ||
             mState == eHeaders);

  if (MOZ_LOG_TEST(gHttpServerLog, mozilla::LogLevel::Verbose)) {
    nsCString line(aBuffer, aLength);
    LOG_V("HttpServer::Connection::ConsumeLine(%p) - \"%s\"", this, line.get());
  }

  if (mState == eRequestLine) {
    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsing request line", this);
    NS_ENSURE_FALSE(mCloseAfterRequest, NS_ERROR_UNEXPECTED);

    if (aLength == 0) {
      // Ignore empty lines before the request line
      return NS_OK;
    }
    MOZ_ASSERT(!mPendingReq);

    // Process request line
    nsCWhitespaceTokenizer tokens(Substring(aBuffer, aLength));

    NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
    nsDependentCSubstring method = tokens.nextToken();
    NS_ENSURE_TRUE(NS_IsValidHTTPToken(method), NS_ERROR_UNEXPECTED);
    NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
    nsDependentCSubstring url = tokens.nextToken();
    // Seems like it's also allowed to pass full urls with scheme+host+port.
    // May need to support that.
    NS_ENSURE_TRUE(url.First() == '/', NS_ERROR_UNEXPECTED);
    mPendingReq = new InternalRequest(url, /* aURLFragment */ EmptyCString());
    mPendingReq->SetMethod(method);
    NS_ENSURE_TRUE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);
    nsDependentCSubstring version = tokens.nextToken();
    NS_ENSURE_TRUE(StringBeginsWith(version, NS_LITERAL_CSTRING("HTTP/1.")),
                   NS_ERROR_UNEXPECTED);
    nsresult rv;
    // This integer parsing is likely not strict enough.
    nsCString reqVersion;
    reqVersion = Substring(version, MOZ_ARRAY_LENGTH("HTTP/1.") - 1);
    mPendingReqVersion = reqVersion.ToInteger(&rv);
    NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);

    NS_ENSURE_FALSE(tokens.hasMoreTokens(), NS_ERROR_UNEXPECTED);

    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsed request line", this);

    mState = eHeaders;

    return NS_OK;
  }

  if (aLength == 0) {
    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Found end of headers", this);

    MaybeAddPendingHeader();

    ErrorResult res;
    mPendingReq->Headers()->SetGuard(HeadersGuardEnum::Immutable, res);

    // Check for WebSocket
    if (IsWebSocketRequest(mPendingReq, mPendingReqVersion)) {
      LOG_V("HttpServer::Connection::ConsumeLine(%p) - Fire OnWebSocket", this);

      mState = ePause;
      mPendingWebSocketRequest = mPendingReq.forget();
      mPendingReqVersion = 0;

      RefPtr<HttpServerListener> listener = mServer->mListener;
      RefPtr<InternalRequest> request = mPendingWebSocketRequest;
      nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction(
        "dom::HttpServer::Connection::ConsumeLine",
        [listener, request]() { listener->OnWebSocket(request); });
      NS_DispatchToCurrentThread(event);

      return NS_OK;
    }

    nsAutoCString header;
    mPendingReq->Headers()->GetFirst(NS_LITERAL_CSTRING("connection"),
                                     header,
                                     res);
    MOZ_ASSERT(!res.Failed());
    // 1.0 defaults to closing connections.
    // 1.1 and higher defaults to keep-alive.
    if (ContainsToken(header, NS_LITERAL_CSTRING("close")) ||
        (mPendingReqVersion == 0 &&
         !ContainsToken(header, NS_LITERAL_CSTRING("keep-alive")))) {
      mCloseAfterRequest = true;
    }

    mPendingReq->Headers()->GetFirst(NS_LITERAL_CSTRING("content-length"),
                                     header,
                                     res);
    MOZ_ASSERT(!res.Failed());

    LOG_V("HttpServer::Connection::ConsumeLine(%p) - content-length is \"%s\"",
          this, header.get());

    if (!header.IsEmpty()) {
      nsresult rv;
      mRemainingBodySize = header.ToInteger(&rv);
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      mRemainingBodySize = 0;
    }

    if (mRemainingBodySize) {
      LOG_V("HttpServer::Connection::ConsumeLine(%p) - Starting consume body", this);
      mState = eBody;

      // We use an unlimited buffer size here to ensure
      // that we get to the next request even if the webpage hangs on
      // to the request indefinitely without consuming the body.
      nsCOMPtr<nsIInputStream> input;
      nsCOMPtr<nsIOutputStream> output;
      nsresult rv = NS_NewPipe(getter_AddRefs(input),
                               getter_AddRefs(output),
                               0,          // Segment size
                               UINT32_MAX, // Unlimited buffer size
                               false,      // not nonBlockingInput
                               true);      // nonBlockingOutput
      NS_ENSURE_SUCCESS(rv, rv);

      mCurrentRequestBody = do_QueryInterface(output);
      mPendingReq->SetBody(input);
    } else {
      LOG_V("HttpServer::Connection::ConsumeLine(%p) - No body", this);
      mState = eRequestLine;
    }

    mPendingRequests.AppendElement(PendingRequest(mPendingReq, nullptr));

    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Fire OnRequest", this);

    RefPtr<HttpServerListener> listener = mServer->mListener;
    RefPtr<InternalRequest> request = mPendingReq.forget();
    nsCOMPtr<nsIRunnable> event = NS_NewRunnableFunction(
      "dom::HttpServer::Connection::ConsumeLine",
      [listener, request]() { listener->OnRequest(request); });
    NS_DispatchToCurrentThread(event);

    mPendingReqVersion = 0;

    return NS_OK;
  }

  // Parse header line
  if (aBuffer[0] == ' ' || aBuffer[0] == '\t') {
    LOG_V("HttpServer::Connection::ConsumeLine(%p) - Add to header %s",
          this,
          mPendingHeaderName.get());

    NS_ENSURE_FALSE(mPendingHeaderName.IsEmpty(),
                    NS_ERROR_UNEXPECTED);

    // We might need to do whitespace trimming/compression here.
    mPendingHeaderValue.Append(aBuffer, aLength);
    return NS_OK;
  }

  MaybeAddPendingHeader();

  const char* colon = static_cast<const char*>(memchr(aBuffer, ':', aLength));
  NS_ENSURE_TRUE(colon, NS_ERROR_UNEXPECTED);

  ToLowerCase(Substring(aBuffer, colon - aBuffer), mPendingHeaderName);
  mPendingHeaderValue.Assign(colon + 1, aLength - (colon - aBuffer) - 1);

  NS_ENSURE_TRUE(NS_IsValidHTTPToken(mPendingHeaderName),
                 NS_ERROR_UNEXPECTED);

  LOG_V("HttpServer::Connection::ConsumeLine(%p) - Parsed header %s",
        this,
        mPendingHeaderName.get());

  return NS_OK;
}