/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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/. */ /* The TestProtocols tests the basic protocols architecture and can be used to test individual protocols as well. If this grows too big then we should split it to individual protocols. -Gagan Saksena 04/29/99 */ #include "TestCommon.h" #include #include #ifdef WIN32 #include #endif #ifdef XP_UNIX #include #endif #include "nspr.h" #include "nscore.h" #include "nsCOMPtr.h" #include "nsIIOService.h" #include "nsIServiceManager.h" #include "nsIStreamListener.h" #include "nsIInputStream.h" #include "nsIInputStream.h" #include "nsCRT.h" #include "nsIChannel.h" #include "nsIResumableChannel.h" #include "nsIURL.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIHttpHeaderVisitor.h" #include "nsIChannelEventSink.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIDNSService.h" #include "nsIAuthPrompt.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsIPropertyBag2.h" #include "nsIWritablePropertyBag2.h" #include "nsITimedChannel.h" #include "mozilla/Attributes.h" #include "mozilla/Unused.h" #include "nsIScriptSecurityManager.h" #include "nsISimpleEnumerator.h" #include "nsStringAPI.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "NetwerkTestLogging.h" using namespace mozilla; namespace TestProtocols { // // set NSPR_LOG_MODULES=Test:5 // static PRLogModuleInfo *gTestLog = nullptr; #define LOG(args) MOZ_LOG(gTestLog, mozilla::LogLevel::Debug, args) static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID); //static PRTime gElapsedTime; // enable when we time it... static int gKeepRunning = 0; static bool gVerbose = false; static bool gAskUserForInput = false; static bool gResume = false; static uint64_t gStartAt = 0; static const char* gEntityID; //----------------------------------------------------------------------------- // Set proxy preferences for testing //----------------------------------------------------------------------------- static nsresult SetHttpProxy(const char *proxy) { const char *colon = strchr(proxy, ':'); if (!colon) { NS_WARNING("invalid proxy token; use host:port"); return NS_ERROR_UNEXPECTED; } int port = atoi(colon + 1); if (port == 0) { NS_WARNING("invalid proxy port; must be an integer"); return NS_ERROR_UNEXPECTED; } nsAutoCString proxyHost; proxyHost = Substring(proxy, colon); nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) { prefs->SetCharPref("network.proxy.http", proxyHost.get()); prefs->SetIntPref("network.proxy.http_port", port); prefs->SetIntPref("network.proxy.type", 1); // manual proxy config } LOG(("connecting via proxy=%s:%d\n", proxyHost.get(), port)); return NS_OK; } static nsresult SetPACFile(const char* pacURL) { nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) { prefs->SetCharPref("network.proxy.autoconfig_url", pacURL); prefs->SetIntPref("network.proxy.type", 2); // PAC file } LOG(("connecting using PAC file %s\n", pacURL)); return NS_OK; } //----------------------------------------------------------------------------- // Timing information //----------------------------------------------------------------------------- void PrintTimingInformation(nsITimedChannel* channel) { #define PRINT_VALUE(property) \ { \ PRTime value; \ channel->Get##property(&value); \ if (value) { \ PRExplodedTime exploded; \ PR_ExplodeTime(value, PR_LocalTimeParameters, &exploded); \ char buf[256]; \ PR_FormatTime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &exploded); \ LOG((" " #property ":\t%s (%i usec)", buf, exploded.tm_usec)); \ } else { \ LOG((" " #property ":\t0")); \ } \ } LOG(("Timing data:")); PRINT_VALUE(ChannelCreationTime) PRINT_VALUE(AsyncOpenTime) PRINT_VALUE(DomainLookupStartTime) PRINT_VALUE(DomainLookupEndTime) PRINT_VALUE(ConnectStartTime) PRINT_VALUE(ConnectEndTime) PRINT_VALUE(RequestStartTime) PRINT_VALUE(ResponseStartTime) PRINT_VALUE(ResponseEndTime) PRINT_VALUE(CacheReadStartTime) PRINT_VALUE(CacheReadEndTime) } //----------------------------------------------------------------------------- // HeaderVisitor //----------------------------------------------------------------------------- class HeaderVisitor : public nsIHttpHeaderVisitor { virtual ~HeaderVisitor() = default; public: NS_DECL_ISUPPORTS NS_DECL_NSIHTTPHEADERVISITOR HeaderVisitor() { } }; NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor) NS_IMETHODIMP HeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value) { LOG((" %s: %s\n", PromiseFlatCString(header).get(), PromiseFlatCString(value).get())); return NS_OK; } //----------------------------------------------------------------------------- // URLLoadInfo //----------------------------------------------------------------------------- class URLLoadInfo : public nsISupports { virtual ~URLLoadInfo(); public: explicit URLLoadInfo(const char* aUrl); // ISupports interface... NS_DECL_THREADSAFE_ISUPPORTS const char* Name() { return mURLString.get(); } int64_t mBytesRead; PRTime mTotalTime; PRTime mConnectTime; nsCString mURLString; }; URLLoadInfo::URLLoadInfo(const char *aUrl) : mURLString(aUrl) { mBytesRead = 0; mConnectTime = mTotalTime = PR_Now(); } URLLoadInfo::~URLLoadInfo() = default; NS_IMPL_ISUPPORTS0(URLLoadInfo) //----------------------------------------------------------------------------- // TestChannelEventSink //----------------------------------------------------------------------------- class TestChannelEventSink : public nsIChannelEventSink { virtual ~TestChannelEventSink(); public: NS_DECL_ISUPPORTS NS_DECL_NSICHANNELEVENTSINK TestChannelEventSink(); }; TestChannelEventSink::TestChannelEventSink() { } TestChannelEventSink::~TestChannelEventSink() = default; NS_IMPL_ISUPPORTS(TestChannelEventSink, nsIChannelEventSink) NS_IMETHODIMP TestChannelEventSink::AsyncOnChannelRedirect(nsIChannel *channel, nsIChannel *newChannel, uint32_t flags, nsIAsyncVerifyRedirectCallback *callback) { LOG(("\n+++ TestChannelEventSink::OnChannelRedirect (with flags %x) +++\n", flags)); callback->OnRedirectVerifyCallback(NS_OK); return NS_OK; } //----------------------------------------------------------------------------- // TestAuthPrompt //----------------------------------------------------------------------------- class TestAuthPrompt : public nsIAuthPrompt { virtual ~TestAuthPrompt(); public: NS_DECL_ISUPPORTS NS_DECL_NSIAUTHPROMPT TestAuthPrompt(); }; NS_IMPL_ISUPPORTS(TestAuthPrompt, nsIAuthPrompt) TestAuthPrompt::TestAuthPrompt() { } TestAuthPrompt::~TestAuthPrompt() = default; NS_IMETHODIMP TestAuthPrompt::Prompt(const char16_t *dialogTitle, const char16_t *text, const char16_t *passwordRealm, uint32_t savePassword, const char16_t *defaultText, char16_t **result, bool *_retval) { *_retval = false; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP TestAuthPrompt::PromptUsernameAndPassword(const char16_t *dialogTitle, const char16_t *dialogText, const char16_t *passwordRealm, uint32_t savePassword, char16_t **user, char16_t **pwd, bool *_retval) { NS_ConvertUTF16toUTF8 text(passwordRealm); printf("* --------------------------------------------------------------------------- *\n"); printf("* Authentication Required [%s]\n", text.get()); printf("* --------------------------------------------------------------------------- *\n"); char buf[256]; int n; printf("Enter username: "); Unused << fgets(buf, sizeof(buf), stdin); n = strlen(buf); buf[n-1] = '\0'; // trim trailing newline *user = NS_StringCloneData(NS_ConvertUTF8toUTF16(buf)); const char *p; #if defined(XP_UNIX) && !defined(ANDROID) p = getpass("Enter password: "); #else printf("Enter password: "); fgets(buf, sizeof(buf), stdin); n = strlen(buf); buf[n-1] = '\0'; // trim trailing newline p = buf; #endif *pwd = NS_StringCloneData(NS_ConvertUTF8toUTF16(p)); // zap buf memset(buf, 0, sizeof(buf)); *_retval = true; return NS_OK; } NS_IMETHODIMP TestAuthPrompt::PromptPassword(const char16_t *dialogTitle, const char16_t *text, const char16_t *passwordRealm, uint32_t savePassword, char16_t **pwd, bool *_retval) { *_retval = false; return NS_ERROR_NOT_IMPLEMENTED; } //----------------------------------------------------------------------------- // InputTestConsumer //----------------------------------------------------------------------------- class InputTestConsumer : public nsIStreamListener { virtual ~InputTestConsumer(); public: explicit InputTestConsumer(URLLoadInfo* aURLLoadInfo); NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER private: URLLoadInfo* mURLLoadInfo; }; InputTestConsumer::InputTestConsumer(URLLoadInfo* aURLLoadInfo) : mURLLoadInfo(aURLLoadInfo) { NS_IF_ADDREF(mURLLoadInfo); } InputTestConsumer::~InputTestConsumer() { NS_RELEASE(mURLLoadInfo); } NS_IMPL_ISUPPORTS(InputTestConsumer, nsIStreamListener, nsIRequestObserver) NS_IMETHODIMP InputTestConsumer::OnStartRequest(nsIRequest *request, nsISupports* context) { LOG(("InputTestConsumer::OnStartRequest\n")); NS_ASSERTION(!context, "context needs to be null when calling asyncOpen2"); if (mURLLoadInfo) mURLLoadInfo->mConnectTime = PR_Now() - mURLLoadInfo->mConnectTime; if (gVerbose) { LOG(("\nStarted loading: %s\n", mURLLoadInfo ? mURLLoadInfo->Name() : "UNKNOWN URL")); } nsAutoCString value; nsCOMPtr channel = do_QueryInterface(request); if (channel) { nsresult status; channel->GetStatus(&status); LOG(("Channel Status: %08x\n", status)); if (NS_SUCCEEDED(status)) { LOG(("Channel Info:\n")); channel->GetName(value); LOG(("\tName: %s\n", value.get())); channel->GetContentType(value); LOG(("\tContent-Type: %s\n", value.get())); channel->GetContentCharset(value); LOG(("\tContent-Charset: %s\n", value.get())); int64_t length = -1; if (NS_SUCCEEDED(channel->GetContentLength(&length))) { LOG(("\tContent-Length: %lld\n", length)); } else { LOG(("\tContent-Length: Unknown\n")); } } nsCOMPtr owner; channel->GetOwner(getter_AddRefs(owner)); LOG(("\tChannel Owner: %x\n", owner.get())); } nsCOMPtr props = do_QueryInterface(request); if (props) { nsCOMPtr foo; props->GetPropertyAsInterface(NS_LITERAL_STRING("test.foo"), NS_GET_IID(nsIURI), getter_AddRefs(foo)); if (foo) { LOG(("\ttest.foo: %s\n", foo->GetSpecOrDefault().get())); } } nsCOMPtr httpChannelInt(do_QueryInterface(request)); if (httpChannelInt) { uint32_t majorVer, minorVer; nsresult rv = httpChannelInt->GetResponseVersion(&majorVer, &minorVer); if (NS_SUCCEEDED(rv)) { LOG(("HTTP Response version: %u.%u\n", majorVer, minorVer)); } } nsCOMPtr httpChannel(do_QueryInterface(request)); if (httpChannel) { auto *visitor = new HeaderVisitor(); if (!visitor) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(visitor); LOG(("HTTP request headers:\n")); httpChannel->VisitRequestHeaders(visitor); LOG(("HTTP response headers:\n")); httpChannel->VisitResponseHeaders(visitor); NS_RELEASE(visitor); } nsCOMPtr resChannel = do_QueryInterface(request); if (resChannel) { LOG(("Resumable entity identification:\n")); nsAutoCString entityID; nsresult rv = resChannel->GetEntityID(entityID); if (NS_SUCCEEDED(rv)) { LOG(("\t|%s|\n", entityID.get())); } else { LOG(("\t\n")); } } return NS_OK; } NS_IMETHODIMP InputTestConsumer::OnDataAvailable(nsIRequest *request, nsISupports* context, nsIInputStream *aIStream, uint64_t aSourceOffset, uint32_t aLength) { NS_ASSERTION(!context, "context needs to be null when calling asyncOpen2"); char buf[1025]; uint32_t amt, size; nsresult rv; while (aLength) { size = std::min(aLength, sizeof(buf)); rv = aIStream->Read(buf, size, &amt); if (NS_FAILED(rv)) { NS_ASSERTION((NS_BASE_STREAM_WOULD_BLOCK != rv), "The stream should never block."); return rv; } if (gVerbose) { buf[amt] = '\0'; puts(buf); } if (mURLLoadInfo) { mURLLoadInfo->mBytesRead += amt; } aLength -= amt; } return NS_OK; } NS_IMETHODIMP InputTestConsumer::OnStopRequest(nsIRequest *request, nsISupports* context, nsresult aStatus) { LOG(("InputTestConsumer::OnStopRequest [status=%x]\n", aStatus)); if (mURLLoadInfo) { uint32_t httpStatus; bool bHTTPURL = false; mURLLoadInfo->mTotalTime = PR_Now() - mURLLoadInfo->mTotalTime; double readTime = ((mURLLoadInfo->mTotalTime-mURLLoadInfo->mConnectTime)/1000.0)/1000.0; nsCOMPtr pHTTPCon(do_QueryInterface(request)); if (pHTTPCon) { pHTTPCon->GetResponseStatus(&httpStatus); bHTTPURL = true; } LOG(("\nFinished loading: %s Status Code: %x\n", mURLLoadInfo->Name(), aStatus)); if (bHTTPURL) { LOG(("\tHTTP Status: %u\n", httpStatus)); } if (NS_ERROR_UNKNOWN_HOST == aStatus || NS_ERROR_UNKNOWN_PROXY_HOST == aStatus) { LOG(("\tDNS lookup failed.\n")); } LOG(("\tTime to connect: %.3f seconds\n", (mURLLoadInfo->mConnectTime/1000.0)/1000.0)); LOG(("\tTime to read: %.3f seconds.\n", readTime)); LOG(("\tRead: %lld bytes.\n", mURLLoadInfo->mBytesRead)); if (mURLLoadInfo->mBytesRead == int64_t(0)) { } else if (readTime > 0.0) { LOG(("\tThroughput: %.0f bps.\n", (double)(mURLLoadInfo->mBytesRead*int64_t(8))/readTime)); } else { LOG(("\tThroughput: REAL FAST!!\n")); } nsCOMPtr timed(do_QueryInterface(request)); if (timed) PrintTimingInformation(timed); } else { LOG(("\nFinished loading: UNKNOWN URL. Status Code: %x\n", aStatus)); } if (--gKeepRunning == 0) QuitPumpingEvents(); return NS_OK; } //----------------------------------------------------------------------------- // NotificationCallbacks //----------------------------------------------------------------------------- class NotificationCallbacks final : public nsIInterfaceRequestor { ~NotificationCallbacks() = default; public: NS_DECL_ISUPPORTS NotificationCallbacks() { } NS_IMETHOD GetInterface(const nsIID& iid, void* *result) override { nsresult rv = NS_ERROR_FAILURE; if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { TestChannelEventSink *sink; sink = new TestChannelEventSink(); if (sink == nullptr) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(sink); rv = sink->QueryInterface(iid, result); NS_RELEASE(sink); } if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) { TestAuthPrompt *prompt; prompt = new TestAuthPrompt(); if (prompt == nullptr) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(prompt); rv = prompt->QueryInterface(iid, result); NS_RELEASE(prompt); } return rv; } }; NS_IMPL_ISUPPORTS(NotificationCallbacks, nsIInterfaceRequestor) //----------------------------------------------------------------------------- // helpers... //----------------------------------------------------------------------------- nsresult StartLoadingURL(const char* aUrlString) { nsresult rv; nsCOMPtr pService(do_GetService(kIOServiceCID, &rv)); if (pService) { nsCOMPtr pURL; rv = pService->NewURI(nsDependentCString(aUrlString), nullptr, nullptr, getter_AddRefs(pURL)); if (NS_FAILED(rv)) { LOG(("ERROR: NewURI failed for %s [rv=%x]\n", aUrlString)); return rv; } nsCOMPtr pChannel; auto* callbacks = new NotificationCallbacks(); if (!callbacks) { LOG(("Failed to create a new consumer!")); return NS_ERROR_OUT_OF_MEMORY;; } NS_ADDREF(callbacks); nsCOMPtr secman = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr systemPrincipal; rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal)); NS_ENSURE_SUCCESS(rv, rv); // Async reading thru the calls of the event sink interface rv = NS_NewChannel(getter_AddRefs(pChannel), pURL, systemPrincipal, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER, nullptr, // loadGroup callbacks, nsIRequest::LOAD_NORMAL, pService); NS_RELEASE(callbacks); if (NS_FAILED(rv)) { LOG(("ERROR: NS_NewChannel failed for %s [rv=%x]\n", aUrlString, rv)); return rv; } nsCOMPtr timed(do_QueryInterface(pChannel)); if (timed) timed->SetTimingEnabled(true); nsCOMPtr props = do_QueryInterface(pChannel); if (props) { rv = props->SetPropertyAsInterface(NS_LITERAL_STRING("test.foo"), pURL); if (NS_SUCCEEDED(rv)) { LOG(("set prop 'test.foo'\n")); } } /* You may optionally add/set other headers on this request object. This is done by QI for the specific protocolConnection. */ nsCOMPtr pHTTPCon(do_QueryInterface(pChannel)); if (pHTTPCon) { // Setting a sample header. rv = pHTTPCon->SetRequestHeader(NS_LITERAL_CSTRING("sample-header"), NS_LITERAL_CSTRING("Sample-Value"), false); if (NS_FAILED(rv)) return rv; } auto* info = new URLLoadInfo(aUrlString); if (!info) { NS_ERROR("Failed to create a load info!"); return NS_ERROR_OUT_OF_MEMORY; } auto* listener = new InputTestConsumer(info); NS_IF_ADDREF(listener); if (!listener) { NS_ERROR("Failed to create a new stream listener!"); return NS_ERROR_OUT_OF_MEMORY;; } if (gResume) { nsCOMPtr res = do_QueryInterface(pChannel); if (!res) { NS_ERROR("Channel is not resumable!"); return NS_ERROR_UNEXPECTED; } nsAutoCString id; if (gEntityID) id = gEntityID; LOG(("* resuming at %llu bytes, with entity id |%s|\n", gStartAt, id.get())); res->ResumeAt(gStartAt, id); } rv = pChannel->AsyncOpen2(listener); if (NS_SUCCEEDED(rv)) { gKeepRunning++; } else { LOG(("ERROR: AsyncOpen failed [rv=%x]\n", rv)); } NS_RELEASE(listener); } return rv; } static int32_t FindChar(nsCString& buffer, char c) { const char *b; int32_t len = NS_CStringGetData(buffer, &b); for (int32_t offset = 0; offset < len; ++offset) { if (b[offset] == c) return offset; } return -1; } static void StripChar(nsCString& buffer, char c) { const char *b; uint32_t len = NS_CStringGetData(buffer, &b) - 1; for (; len > 0; --len) { if (b[len] == c) { buffer.Cut(len, 1); NS_CStringGetData(buffer, &b); } } } nsresult LoadURLsFromFile(char *aFileName) { nsresult rv = NS_OK; int32_t len, offset; PRFileDesc* fd; char buffer[1024]; nsCString fileBuffer; nsCString urlString; fd = PR_Open(aFileName, PR_RDONLY, 777); if (!fd) { return NS_ERROR_FAILURE; } // Keep reading the file until EOF (or an error) is reached... do { len = PR_Read(fd, buffer, sizeof(buffer)); if (len>0) { fileBuffer.Append(buffer, len); // Treat each line as a URL... while ((offset = FindChar(fileBuffer, '\n')) != -1) { urlString = StringHead(fileBuffer, offset); fileBuffer.Cut(0, offset+1); StripChar(urlString, '\r'); if (urlString.Length()) { LOG(("\t%s\n", urlString.get())); rv = StartLoadingURL(urlString.get()); if (NS_FAILED(rv)) { // No need to log an error -- StartLoadingURL already // did that for us, probably. PR_Close(fd); return rv; } } } } } while (len>0); // If anything is left in the fileBuffer, treat it as a URL... StripChar(fileBuffer, '\r'); if (fileBuffer.Length()) { LOG(("\t%s\n", fileBuffer.get())); StartLoadingURL(fileBuffer.get()); } PR_Close(fd); return NS_OK; } nsresult LoadURLFromConsole() { char buffer[1024]; printf(R"(Enter URL ("q" to start): )"); Unused << scanf("%s", buffer); if (buffer[0]=='q') gAskUserForInput = false; else StartLoadingURL(buffer); return NS_OK; } } // namespace TestProtocols using namespace TestProtocols; int main(int argc, char* argv[]) { if (test_common_init(&argc, &argv) != 0) return -1; nsresult rv= (nsresult)-1; if (argc < 2) { printf("usage: %s [-verbose] [-file ] [-resume " "[-entityid ]] [-proxy ] [-pac ]" "[-console] ... \n", argv[0]); return -1; } gTestLog = PR_NewLogModule("Test"); /* The following code only deals with XPCOM registration stuff. and setting up the event queues. Copied from TestSocketIO.cpp */ rv = NS_InitXPCOM2(nullptr, nullptr, nullptr); if (NS_FAILED(rv)) return -1; { int i; LOG(("Trying to load:\n")); for (i=1; i