Beispiel #1
0
// static
nsresult
FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
{
  nsAutoCString upperCaseMethod(aMethod);
  ToUpperCase(upperCaseMethod);
  if (!NS_IsValidHTTPToken(aMethod)) {
    outMethod.SetIsVoid(true);
    return NS_ERROR_DOM_SYNTAX_ERR;
  }

  if (upperCaseMethod.EqualsLiteral("CONNECT") ||
      upperCaseMethod.EqualsLiteral("TRACE") ||
      upperCaseMethod.EqualsLiteral("TRACK")) {
    outMethod.SetIsVoid(true);
    return NS_ERROR_DOM_SECURITY_ERR;
  }

  if (upperCaseMethod.EqualsLiteral("DELETE") ||
      upperCaseMethod.EqualsLiteral("GET") ||
      upperCaseMethod.EqualsLiteral("HEAD") ||
      upperCaseMethod.EqualsLiteral("OPTIONS") ||
      upperCaseMethod.EqualsLiteral("POST") ||
      upperCaseMethod.EqualsLiteral("PUT")) {
    outMethod = upperCaseMethod;
  }
  else {
    outMethod = aMethod; // Case unchanged for non-standard methods
  }
  return NS_OK;
}
//static
bool
InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv)
{
  if (!NS_IsValidHTTPToken(aName)) {
    NS_ConvertUTF8toUTF16 label(aName);
    aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(label);
    return true;
  }

  return false;
}
Beispiel #3
0
// static
bool
FetchUtil::ExtractHeader(nsACString::const_iterator& aStart,
                         nsACString::const_iterator& aEnd,
                         nsCString& aHeaderName,
                         nsCString& aHeaderValue,
                         bool* aWasEmptyHeader)
{
  MOZ_ASSERT(aWasEmptyHeader);
  // Set it to a valid value here so we don't forget later.
  *aWasEmptyHeader = false;

  const char* beginning = aStart.get();
  nsACString::const_iterator end(aEnd);
  if (!FindCRLF(aStart, end)) {
    return false;
  }

  if (aStart.get() == beginning) {
    *aWasEmptyHeader = true;
    return true;
  }

  nsAutoCString header(beginning, aStart.get() - beginning);

  nsACString::const_iterator headerStart, iter, headerEnd;
  header.BeginReading(headerStart);
  header.EndReading(headerEnd);
  iter = headerStart;
  if (!FindCharInReadable(':', iter, headerEnd)) {
    return false;
  }

  aHeaderName.Assign(StringHead(header, iter - headerStart));
  aHeaderName.CompressWhitespace();
  if (!NS_IsValidHTTPToken(aHeaderName)) {
    return false;
  }

  aHeaderValue.Assign(Substring(++iter, headerEnd));
  if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) {
    return false;
  }
  aHeaderValue.CompressWhitespace();

  return PushOverLine(aStart, aEnd);
}
// static
already_AddRefed<InternalHeaders>
InternalHeaders::CORSHeaders(InternalHeaders* aHeaders)
{
  RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard);
  ErrorResult result;

  nsAutoCString acExposedNames;
  aHeaders->Get(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), acExposedNames, result);
  MOZ_ASSERT(!result.Failed());

  AutoTArray<nsCString, 5> exposeNamesArray;
  nsCCharSeparatedTokenizer exposeTokens(acExposedNames, ',');
  while (exposeTokens.hasMoreTokens()) {
    const nsDependentCSubstring& token = exposeTokens.nextToken();
    if (token.IsEmpty()) {
      continue;
    }

    if (!NS_IsValidHTTPToken(token)) {
      NS_WARNING("Got invalid HTTP token in Access-Control-Expose-Headers. Header value is:");
      NS_WARNING(acExposedNames.get());
      exposeNamesArray.Clear();
      break;
    }

    exposeNamesArray.AppendElement(token);
  }

  nsCaseInsensitiveCStringArrayComparator comp;
  for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) {
    const Entry& entry = aHeaders->mList[i];
    if (entry.mName.EqualsASCII("cache-control") ||
        entry.mName.EqualsASCII("content-language") ||
        entry.mName.EqualsASCII("content-type") ||
        entry.mName.EqualsASCII("expires") ||
        entry.mName.EqualsASCII("last-modified") ||
        entry.mName.EqualsASCII("pragma") ||
        exposeNamesArray.Contains(entry.mName, comp)) {
      cors->Append(entry.mName, entry.mValue, result);
      MOZ_ASSERT(!result.Failed());
    }
  }

  return cors.forget();
}
Beispiel #5
0
  bool
  ParseHeader(nsACString::const_iterator& aStart,
              nsACString::const_iterator& aEnd,
              bool* aWasEmptyHeader)
  {
    MOZ_ASSERT(aWasEmptyHeader);
    // Set it to a valid value here so we don't forget later.
    *aWasEmptyHeader = false;

    const char* beginning = aStart.get();
    nsACString::const_iterator end(aEnd);
    if (!FindCRLF(aStart, end)) {
      return false;
    }

    if (aStart.get() == beginning) {
      *aWasEmptyHeader = true;
      return true;
    }

    nsAutoCString header(beginning, aStart.get() - beginning);

    nsACString::const_iterator headerStart, headerEnd;
    header.BeginReading(headerStart);
    header.EndReading(headerEnd);
    if (!FindCharInReadable(':', headerStart, headerEnd)) {
      return false;
    }

    nsAutoCString headerName(StringHead(header, headerStart.size_backward()));
    headerName.CompressWhitespace();
    if (!NS_IsValidHTTPToken(headerName)) {
      return false;
    }

    nsAutoCString headerValue(Substring(++headerStart, headerEnd));
    if (!NS_IsReasonableHTTPHeaderValue(headerValue)) {
      return false;
    }
    headerValue.CompressWhitespace();

    if (headerName.LowerCaseEqualsLiteral("content-disposition")) {
      nsCCharSeparatedTokenizer tokenizer(headerValue, ';');
      bool seenFormData = false;
      while (tokenizer.hasMoreTokens()) {
        const nsDependentCSubstring& token = tokenizer.nextToken();
        if (token.IsEmpty()) {
          continue;
        }

        if (token.EqualsLiteral("form-data")) {
          seenFormData = true;
          continue;
        }

        if (seenFormData &&
            StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) {
          mName = StringTail(token, token.Length() - 5);
          mName.Trim(" \"");
          continue;
        }

        if (seenFormData &&
            StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) {
          mFilename = StringTail(token, token.Length() - 9);
          mFilename.Trim(" \"");
          continue;
        }
      }

      if (mName.IsVoid()) {
        // Could not parse a valid entry name.
        return false;
      }
    } else if (headerName.LowerCaseEqualsLiteral("content-type")) {
      mContentType = headerValue;
    }

    return true;
  }
Beispiel #6
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;
}