// 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; }
// 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(); }
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; }
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; }