/* -*- Mode: C++; tab-width: 8; 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/. */ /* A directory viewer object. Parses "application/http-index-format" per Lou Montulli's original spec: http://www.mozilla.org/projects/netlib/dirindexformat.html One added change is for a description entry, for when the target does not match the filename */ #include "nsDirectoryViewer.h" #include "nsArray.h" #include "nsArrayUtils.h" #include "nsIDirIndex.h" #include "nsIDocShell.h" #include "jsapi.h" #include "nsCOMPtr.h" #include "nsEnumeratorUtils.h" #include "nsEscape.h" #include "nsIRDFService.h" #include "nsRDFCID.h" #include "rdf.h" #include "nsIServiceManager.h" #include "nsIXPConnect.h" #include "nsEnumeratorUtils.h" #include "nsString.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsITextToSubURI.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIFTPChannel.h" #include "nsIWindowWatcher.h" #include "nsIPrompt.h" #include "nsIAuthPrompt.h" #include "nsIProgressEventSink.h" #include "nsIDOMWindow.h" #include "nsIDOMElement.h" #include "nsIStreamConverterService.h" #include "nsICategoryManager.h" #include "nsXPCOMCID.h" #include "nsIDocument.h" #include "mozilla/Preferences.h" #include "mozilla/dom/ScriptSettings.h" #include "nsContentUtils.h" #include "nsIURI.h" #include "nsNetUtil.h" using namespace mozilla; static const int FORMAT_XUL = 3; //---------------------------------------------------------------------- // // Common CIDs // static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); // Various protocols we have to special case static const char kFTPProtocol[] = "ftp://"; //---------------------------------------------------------------------- // // nsHTTPIndex // NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHTTPIndex) NS_INTERFACE_MAP_ENTRY(nsIHTTPIndex) NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIDirIndexListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIFTPEventSink) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHTTPIndex) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION(nsHTTPIndex, mInner) NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHTTPIndex) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHTTPIndex) NS_IMETHODIMP nsHTTPIndex::GetInterface(const nsIID &anIID, void **aResult ) { if (anIID.Equals(NS_GET_IID(nsIFTPEventSink))) { // If we don't have a container to store the logged data // then don't report ourselves back to the caller if (!mRequestor) return NS_ERROR_NO_INTERFACE; *aResult = static_cast(this); NS_ADDREF(this); return NS_OK; } if (anIID.Equals(NS_GET_IID(nsIPrompt))) { if (!mRequestor) return NS_ERROR_NO_INTERFACE; nsCOMPtr aDOMWindow = do_GetInterface(mRequestor); if (!aDOMWindow) return NS_ERROR_NO_INTERFACE; nsCOMPtr wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); return wwatch->GetNewPrompter(aDOMWindow, (nsIPrompt**)aResult); } if (anIID.Equals(NS_GET_IID(nsIAuthPrompt))) { if (!mRequestor) return NS_ERROR_NO_INTERFACE; nsCOMPtr aDOMWindow = do_GetInterface(mRequestor); if (!aDOMWindow) return NS_ERROR_NO_INTERFACE; nsCOMPtr wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); return wwatch->GetNewAuthPrompter(aDOMWindow, (nsIAuthPrompt**)aResult); } if (anIID.Equals(NS_GET_IID(nsIProgressEventSink))) { if (!mRequestor) return NS_ERROR_NO_INTERFACE; nsCOMPtr sink = do_GetInterface(mRequestor); if (!sink) return NS_ERROR_NO_INTERFACE; *aResult = sink; NS_ADDREF((nsISupports*)*aResult); return NS_OK; } return NS_ERROR_NO_INTERFACE; } NS_IMETHODIMP nsHTTPIndex::OnFTPControlLog(bool server, const char *msg) { NS_ENSURE_TRUE(mRequestor, NS_OK); nsCOMPtr globalObject = do_GetInterface(mRequestor); NS_ENSURE_TRUE(globalObject, NS_OK); // We're going to run script via JS_CallFunctionName, so we need an // AutoEntryScript. This is Gecko specific and not in any spec. dom::AutoEntryScript aes(globalObject, "nsHTTPIndex OnFTPControlLog"); JSContext* cx = aes.cx(); JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); NS_ENSURE_TRUE(global, NS_OK); nsString unicodeMsg; unicodeMsg.AssignWithConversion(msg); JSString* jsMsgStr = JS_NewUCStringCopyZ(cx, unicodeMsg.get()); NS_ENSURE_TRUE(jsMsgStr, NS_ERROR_OUT_OF_MEMORY); JS::AutoValueArray<2> params(cx); params[0].setBoolean(server); params[1].setString(jsMsgStr); JS::Rooted val(cx); JS_CallFunctionName(cx, global, "OnFTPControlLog", params, &val); return NS_OK; } NS_IMETHODIMP nsHTTPIndex::SetEncoding(const char *encoding) { mEncoding = encoding; return(NS_OK); } NS_IMETHODIMP nsHTTPIndex::GetEncoding(char **encoding) { NS_PRECONDITION(encoding, "null ptr"); if (! encoding) return(NS_ERROR_NULL_POINTER); *encoding = ToNewCString(mEncoding); if (!*encoding) return(NS_ERROR_OUT_OF_MEMORY); return(NS_OK); } NS_IMETHODIMP nsHTTPIndex::OnStartRequest(nsIRequest *request, nsISupports* aContext) { nsresult rv; mParser = do_CreateInstance(NS_DIRINDEXPARSER_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; rv = mParser->SetEncoding(mEncoding.get()); if (NS_FAILED(rv)) return rv; rv = mParser->SetListener(this); if (NS_FAILED(rv)) return rv; rv = mParser->OnStartRequest(request,aContext); if (NS_FAILED(rv)) return rv; // This should only run once... // Unless we don't have a container to start with // (ie called from bookmarks as an rdf datasource) if (mBindToGlobalObject && mRequestor) { mBindToGlobalObject = false; nsCOMPtr globalObject = do_GetInterface(mRequestor); NS_ENSURE_TRUE(globalObject, NS_ERROR_FAILURE); // We might run script via JS_SetProperty, so we need an AutoEntryScript. // This is Gecko specific and not in any spec. dom::AutoEntryScript aes(globalObject, "nsHTTPIndex set HTTPIndex property"); JSContext* cx = aes.cx(); JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); // Using XPConnect, wrap the HTTP index object... static NS_DEFINE_CID(kXPConnectCID, NS_XPCONNECT_CID); nsCOMPtr xpc(do_GetService(kXPConnectCID, &rv)); if (NS_FAILED(rv)) return rv; JS::Rooted jsobj(cx); rv = xpc->WrapNative(cx, global, static_cast(this), NS_GET_IID(nsIHTTPIndex), jsobj.address()); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to xpconnect-wrap http-index"); if (NS_FAILED(rv)) return rv; NS_ASSERTION(jsobj, "unable to get jsobj from xpconnect wrapper"); if (!jsobj) return NS_ERROR_UNEXPECTED; JS::Rooted jslistener(cx, JS::ObjectValue(*jsobj)); // ...and stuff it into the global context bool ok = JS_SetProperty(cx, global, "HTTPIndex", jslistener); NS_ASSERTION(ok, "unable to set Listener property"); if (!ok) return NS_ERROR_FAILURE; } if (!aContext) { nsCOMPtr channel(do_QueryInterface(request)); NS_ASSERTION(channel, "request should be a channel"); // lets hijack the notifications: channel->SetNotificationCallbacks(this); // now create the top most resource nsCOMPtr uri; channel->GetURI(getter_AddRefs(uri)); nsAutoCString entryuriC; rv = uri->GetSpec(entryuriC); if (NS_FAILED(rv)) return rv; nsCOMPtr entry; rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry)); NS_ConvertUTF8toUTF16 uriUnicode(entryuriC); nsCOMPtr URLVal; rv = mDirRDF->GetLiteral(uriUnicode.get(), getter_AddRefs(URLVal)); Assert(entry, kNC_URL, URLVal, true); mDirectory = do_QueryInterface(entry); } else { // Get the directory from the context mDirectory = do_QueryInterface(aContext); } if (!mDirectory) { request->Cancel(NS_BINDING_ABORTED); return NS_BINDING_ABORTED; } // Mark the directory as "loading" rv = Assert(mDirectory, kNC_Loading, kTrueLiteral, true); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_IMETHODIMP nsHTTPIndex::OnStopRequest(nsIRequest *request, nsISupports* aContext, nsresult aStatus) { // If mDirectory isn't set, then we should just bail. Either an // error occurred and OnStartRequest() never got called, or // something exploded in OnStartRequest(). if (! mDirectory) return NS_BINDING_ABORTED; mParser->OnStopRequest(request,aContext,aStatus); nsresult rv; nsXPIDLCString commentStr; mParser->GetComment(getter_Copies(commentStr)); nsCOMPtr comment; rv = mDirRDF->GetLiteral(NS_ConvertASCIItoUTF16(commentStr).get(), getter_AddRefs(comment)); if (NS_FAILED(rv)) return rv; rv = Assert(mDirectory, kNC_Comment, comment, true); if (NS_FAILED(rv)) return rv; // hack: Remove the 'loading' annotation (ignore errors) AddElement(mDirectory, kNC_Loading, kTrueLiteral); return NS_OK; } NS_IMETHODIMP nsHTTPIndex::OnDataAvailable(nsIRequest *request, nsISupports* aContext, nsIInputStream* aStream, uint64_t aSourceOffset, uint32_t aCount) { // If mDirectory isn't set, then we should just bail. Either an // error occurred and OnStartRequest() never got called, or // something exploded in OnStartRequest(). if (! mDirectory) return NS_BINDING_ABORTED; return mParser->OnDataAvailable(request, mDirectory, aStream, aSourceOffset, aCount); } nsresult nsHTTPIndex::OnIndexAvailable(nsIRequest* aRequest, nsISupports *aContext, nsIDirIndex* aIndex) { nsCOMPtr parentRes = do_QueryInterface(aContext); if (!parentRes) { NS_ERROR("Could not obtain parent resource"); return(NS_ERROR_UNEXPECTED); } const char* baseStr; parentRes->GetValueConst(&baseStr); if (! baseStr) { NS_ERROR("Could not reconstruct base uri"); return NS_ERROR_UNEXPECTED; } // we found the filename; construct a resource for its entry nsAutoCString entryuriC(baseStr); nsXPIDLCString filename; nsresult rv = aIndex->GetLocation(getter_Copies(filename)); if (NS_FAILED(rv)) return rv; entryuriC.Append(filename); // if its a directory, make sure it ends with a trailing slash. uint32_t type; rv = aIndex->GetType(&type); if (NS_FAILED(rv)) return rv; bool isDirType = (type == nsIDirIndex::TYPE_DIRECTORY); if (isDirType && entryuriC.Last() != '/') { entryuriC.Append('/'); } nsCOMPtr entry; rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry)); // At this point, we'll (hopefully) have found the filename and // constructed a resource for it, stored in entry. So now take a // second pass through the values and add as statements to the RDF // datasource. if (entry && NS_SUCCEEDED(rv)) { nsCOMPtr lit; nsString str; str.AssignWithConversion(entryuriC.get()); rv = mDirRDF->GetLiteral(str.get(), getter_AddRefs(lit)); if (NS_SUCCEEDED(rv)) { rv = Assert(entry, kNC_URL, lit, true); if (NS_FAILED(rv)) return rv; nsXPIDLString xpstr; // description rv = aIndex->GetDescription(getter_Copies(xpstr)); if (NS_FAILED(rv)) return rv; if (xpstr.Last() == '/') xpstr.Truncate(xpstr.Length() - 1); rv = mDirRDF->GetLiteral(xpstr.get(), getter_AddRefs(lit)); if (NS_FAILED(rv)) return rv; rv = Assert(entry, kNC_Description, lit, true); if (NS_FAILED(rv)) return rv; // contentlength int64_t size; rv = aIndex->GetSize(&size); if (NS_FAILED(rv)) return rv; int64_t minus1 = UINT64_MAX; if (size != minus1) { int32_t intSize = int32_t(size); // XXX RDF should support 64 bit integers (bug 240160) nsCOMPtr val; rv = mDirRDF->GetIntLiteral(intSize, getter_AddRefs(val)); if (NS_FAILED(rv)) return rv; rv = Assert(entry, kNC_ContentLength, val, true); if (NS_FAILED(rv)) return rv; } // lastmodified PRTime tm; rv = aIndex->GetLastModified(&tm); if (NS_FAILED(rv)) return rv; if (tm != -1) { nsCOMPtr val; rv = mDirRDF->GetDateLiteral(tm, getter_AddRefs(val)); if (NS_FAILED(rv)) return rv; rv = Assert(entry, kNC_LastModified, val, true); } // filetype uint32_t type; rv = aIndex->GetType(&type); switch (type) { case nsIDirIndex::TYPE_UNKNOWN: rv = mDirRDF->GetLiteral(u"UNKNOWN", getter_AddRefs(lit)); break; case nsIDirIndex::TYPE_DIRECTORY: rv = mDirRDF->GetLiteral(u"DIRECTORY", getter_AddRefs(lit)); break; case nsIDirIndex::TYPE_FILE: rv = mDirRDF->GetLiteral(u"FILE", getter_AddRefs(lit)); break; case nsIDirIndex::TYPE_SYMLINK: rv = mDirRDF->GetLiteral(u"SYMLINK", getter_AddRefs(lit)); break; } if (NS_FAILED(rv)) return rv; rv = Assert(entry, kNC_FileType, lit, true); if (NS_FAILED(rv)) return rv; } // Since the definition of a directory depends on the protocol, we would have // to do string comparisons all the time. // But we're told if we're a container right here - so save that fact if (isDirType) Assert(entry, kNC_IsContainer, kTrueLiteral, true); else Assert(entry, kNC_IsContainer, kFalseLiteral, true); // instead of // rv = Assert(parentRes, kNC_Child, entry, true); // if (NS_FAILED(rv)) return rv; // defer insertion onto a timer so that the UI isn't starved AddElement(parentRes, kNC_Child, entry); } return rv; } nsresult nsHTTPIndex::OnInformationAvailable(nsIRequest *aRequest, nsISupports *aCtxt, const nsAString& aInfo) { return NS_ERROR_NOT_IMPLEMENTED; } //---------------------------------------------------------------------- // // nsHTTPIndex implementation // nsHTTPIndex::nsHTTPIndex() : mBindToGlobalObject(true), mRequestor(nullptr) { } nsHTTPIndex::nsHTTPIndex(nsIInterfaceRequestor* aRequestor) : mBindToGlobalObject(true), mRequestor(aRequestor) { } nsHTTPIndex::~nsHTTPIndex() { // note: these are NOT statics due to the native of nsHTTPIndex // where it may or may not be treated as a singleton if (mTimer) { // be sure to cancel the timer, as it holds a // weak reference back to nsHTTPIndex mTimer->Cancel(); mTimer = nullptr; } mConnectionList = nullptr; mNodeList = nullptr; if (mDirRDF) { // UnregisterDataSource() may fail; just ignore errors mDirRDF->UnregisterDataSource(this); } } nsresult nsHTTPIndex::CommonInit() { nsresult rv = NS_OK; // set initial/default encoding to ISO-8859-1 (not UTF-8) mEncoding = "ISO-8859-1"; mDirRDF = do_GetService(kRDFServiceCID, &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); if (NS_FAILED(rv)) { return(rv); } mInner = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource", &rv); if (NS_FAILED(rv)) return rv; mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"), getter_AddRefs(kNC_Child)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "loading"), getter_AddRefs(kNC_Loading)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Comment"), getter_AddRefs(kNC_Comment)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"), getter_AddRefs(kNC_URL)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"), getter_AddRefs(kNC_Description)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Length"), getter_AddRefs(kNC_ContentLength)); mDirRDF->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastModifiedDate"), getter_AddRefs(kNC_LastModified)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Type"), getter_AddRefs(kNC_ContentType)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "File-Type"), getter_AddRefs(kNC_FileType)); mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IsContainer"), getter_AddRefs(kNC_IsContainer)); rv = mDirRDF->GetLiteral(u"true", getter_AddRefs(kTrueLiteral)); if (NS_FAILED(rv)) return(rv); rv = mDirRDF->GetLiteral(u"false", getter_AddRefs(kFalseLiteral)); if (NS_FAILED(rv)) return(rv); mConnectionList = nsArray::Create(); // note: don't register DS here return rv; } nsresult nsHTTPIndex::Init() { nsresult rv; // set initial/default encoding to ISO-8859-1 (not UTF-8) mEncoding = "ISO-8859-1"; rv = CommonInit(); if (NS_FAILED(rv)) return(rv); // (do this last) register this as a named data source with the RDF service rv = mDirRDF->RegisterDataSource(this, false); if (NS_FAILED(rv)) return(rv); return(NS_OK); } nsresult nsHTTPIndex::Init(nsIURI* aBaseURL) { NS_PRECONDITION(aBaseURL != nullptr, "null ptr"); if (! aBaseURL) return NS_ERROR_NULL_POINTER; nsresult rv; rv = CommonInit(); if (NS_FAILED(rv)) return(rv); // note: don't register DS here (singleton case) rv = aBaseURL->GetSpec(mBaseURL); if (NS_FAILED(rv)) return rv; // Mark the base url as a container nsCOMPtr baseRes; mDirRDF->GetResource(mBaseURL, getter_AddRefs(baseRes)); Assert(baseRes, kNC_IsContainer, kTrueLiteral, true); return NS_OK; } nsresult nsHTTPIndex::Create(nsIURI* aBaseURL, nsIInterfaceRequestor* aRequestor, nsIHTTPIndex** aResult) { *aResult = nullptr; nsHTTPIndex* result = new nsHTTPIndex(aRequestor); nsresult rv = result->Init(aBaseURL); if (NS_SUCCEEDED(rv)) { NS_ADDREF(result); *aResult = result; } else { delete result; } return rv; } NS_IMETHODIMP nsHTTPIndex::GetBaseURL(char** _result) { *_result = ToNewCString(mBaseURL); if (! *_result) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } NS_IMETHODIMP nsHTTPIndex::GetDataSource(nsIRDFDataSource** _result) { NS_ADDREF(*_result = this); return NS_OK; } // This function finds the destination when following a given nsIRDFResource // If the resource has a URL attribute, we use that. If not, just use // the uri. // // Do NOT try to get the destination of a uri in any other way void nsHTTPIndex::GetDestination(nsIRDFResource* r, nsXPIDLCString& dest) { // First try the URL attribute nsCOMPtr node; GetTarget(r, kNC_URL, true, getter_AddRefs(node)); nsCOMPtr url; if (node) url = do_QueryInterface(node); if (!url) { const char* temp; r->GetValueConst(&temp); dest.Adopt(temp ? strdup(temp) : 0); } else { const char16_t* uri; url->GetValueConst(&uri); dest.Adopt(ToNewUTF8String(nsDependentString(uri))); } } // rjc: isWellknownContainerURI() decides whether a URI is a container for which, // when asked (say, by the template builder), we'll make a network connection // to get its contents. For the moment, all we speak is ftp:// URLs, even though // a) we can get "http-index" mimetypes for really anything // b) we could easily handle file:// URLs here // Q: Why don't we? // A: The file system datasource ("rdf:file"); at some point, the two // should be perhaps united. Until then, we can't aggregate both // "rdf:file" and "http-index" (such as with bookmarks) because we'd // get double the # of answers we really want... also, "rdf:file" is // less expensive in terms of both memory usage as well as speed // We use an rdf attribute to mark if this is a container or not. // Note that we still have to do string comparisons as a fallback // because stuff like the personal toolbar and bookmarks check whether // a URL is a container, and we have no attribute in that case. bool nsHTTPIndex::isWellknownContainerURI(nsIRDFResource *r) { nsCOMPtr node; GetTarget(r, kNC_IsContainer, true, getter_AddRefs(node)); if (node) { bool isContainerFlag; if (NS_SUCCEEDED(node->EqualsNode(kTrueLiteral, &isContainerFlag))) return isContainerFlag; } nsXPIDLCString uri; GetDestination(r, uri); return uri.get() && !strncmp(uri, kFTPProtocol, sizeof(kFTPProtocol) - 1) && (uri.Last() == '/'); } NS_IMETHODIMP nsHTTPIndex::GetURI(char * *uri) { NS_PRECONDITION(uri != nullptr, "null ptr"); if (! uri) return(NS_ERROR_NULL_POINTER); if ((*uri = strdup("rdf:httpindex")) == nullptr) return(NS_ERROR_OUT_OF_MEMORY); return(NS_OK); } NS_IMETHODIMP nsHTTPIndex::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsIRDFResource **_retval) { nsresult rv = NS_ERROR_UNEXPECTED; *_retval = nullptr; if (mInner) { rv = mInner->GetSource(aProperty, aTarget, aTruthValue, _retval); } return(rv); } NS_IMETHODIMP nsHTTPIndex::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->GetSources(aProperty, aTarget, aTruthValue, _retval); } else { rv = NS_NewEmptyEnumerator(_retval); } return(rv); } NS_IMETHODIMP nsHTTPIndex::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsIRDFNode **_retval) { nsresult rv = NS_ERROR_UNEXPECTED; *_retval = nullptr; if ((aTruthValue) && (aProperty == kNC_Child) && isWellknownContainerURI(aSource)) { // fake out the generic builder (i.e. return anything in this case) // so that search containers never appear to be empty NS_IF_ADDREF(aSource); *_retval = aSource; return(NS_OK); } if (mInner) { rv = mInner->GetTarget(aSource, aProperty, aTruthValue, _retval); } return(rv); } NS_IMETHODIMP nsHTTPIndex::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsISimpleEnumerator **_retval) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->GetTargets(aSource, aProperty, aTruthValue, _retval); } else { rv = NS_NewEmptyEnumerator(_retval); } if ((aProperty == kNC_Child) && isWellknownContainerURI(aSource)) { bool doNetworkRequest = true; if (NS_SUCCEEDED(rv) && (_retval)) { // check and see if we already have data for the search in question; // if we do, don't bother doing the search again bool hasResults; if (NS_SUCCEEDED((*_retval)->HasMoreElements(&hasResults)) && hasResults) doNetworkRequest = false; } // Note: if we need to do a network request, do it out-of-band // (because the XUL template builder isn't re-entrant) // by using a global connection list and an immediately-firing timer if (doNetworkRequest && mConnectionList) { uint32_t connectionIndex; nsresult idx_rv = mConnectionList->IndexOf(0, aSource, &connectionIndex); if (NS_FAILED(idx_rv)) { // add aSource into list of connections to make mConnectionList->AppendElement(aSource, /*weak =*/ false); // if we don't have a timer about to fire, create one // which should fire as soon as possible (out-of-band) if (!mTimer) { mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer"); if (NS_SUCCEEDED(rv)) { mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1, nsITimer::TYPE_ONE_SHOT); // Note: don't addref "this" as we'll cancel the // timer in the httpIndex destructor } } } } } return(rv); } nsresult nsHTTPIndex::AddElement(nsIRDFResource *parent, nsIRDFResource *prop, nsIRDFNode *child) { nsresult rv; if (!mNodeList) { mNodeList = nsArray::Create(); } // order required: parent, prop, then child mNodeList->AppendElement(parent, /*weak =*/ false); mNodeList->AppendElement(prop, /*weak =*/ false); mNodeList->AppendElement(child, /*weak = */ false); if (!mTimer) { mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer"); if (NS_FAILED(rv)) return(rv); mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1, nsITimer::TYPE_ONE_SHOT); // Note: don't addref "this" as we'll cancel the // timer in the httpIndex destructor } return(NS_OK); } void nsHTTPIndex::FireTimer(nsITimer* aTimer, void* aClosure) { nsHTTPIndex *httpIndex = static_cast(aClosure); if (!httpIndex) return; // don't return out of this loop as mTimer may need to be cancelled afterwards uint32_t numItems = 0; if (httpIndex->mConnectionList) { httpIndex->mConnectionList->GetLength(&numItems); if (numItems > 0) { nsCOMPtr source = do_QueryElementAt(httpIndex->mConnectionList, 0); httpIndex->mConnectionList->RemoveElementAt(0); nsXPIDLCString uri; if (source) { httpIndex->GetDestination(source, uri); } if (!uri) { NS_ERROR("Could not reconstruct uri"); return; } nsresult rv = NS_OK; nsCOMPtr url; rv = NS_NewURI(getter_AddRefs(url), uri.get()); nsCOMPtr channel; if (NS_SUCCEEDED(rv) && (url)) { rv = NS_NewChannel(getter_AddRefs(channel), url, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER); } if (NS_SUCCEEDED(rv) && (channel)) { channel->SetNotificationCallbacks(httpIndex); rv = channel->AsyncOpen2(httpIndex); } } } if (httpIndex->mNodeList) { httpIndex->mNodeList->GetLength(&numItems); if (numItems > 0) { // account for order required: src, prop, then target numItems /=3; if (numItems > 10) numItems = 10; int32_t loop; for (loop=0; loop<(int32_t)numItems; loop++) { nsCOMPtr src = do_QueryElementAt(httpIndex->mNodeList, 0); httpIndex->mNodeList->RemoveElementAt(0); nsCOMPtr prop = do_QueryElementAt(httpIndex->mNodeList, 0); httpIndex->mNodeList->RemoveElementAt(0); nsCOMPtr target = do_QueryElementAt(httpIndex->mNodeList, 0); httpIndex->mNodeList->RemoveElementAt(0); if (src && prop && target) { if (prop.get() == httpIndex->kNC_Loading) { httpIndex->Unassert(src, prop, target); } else { httpIndex->Assert(src, prop, target, true); } } } } } bool refireTimer = false; // check both lists to see if the timer needs to continue firing if (httpIndex->mConnectionList) { httpIndex->mConnectionList->GetLength(&numItems); if (numItems > 0) { refireTimer = true; } else { httpIndex->mConnectionList->Clear(); } } if (httpIndex->mNodeList) { httpIndex->mNodeList->GetLength(&numItems); if (numItems > 0) { refireTimer = true; } else { httpIndex->mNodeList->Clear(); } } // be sure to cancel the timer, as it holds a // weak reference back to nsHTTPIndex httpIndex->mTimer->Cancel(); httpIndex->mTimer = nullptr; // after firing off any/all of the connections be sure // to cancel the timer if we don't need to refire it if (refireTimer) { httpIndex->mTimer = do_CreateInstance("@mozilla.org/timer;1"); if (httpIndex->mTimer) { httpIndex->mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, aClosure, 10, nsITimer::TYPE_ONE_SHOT); // Note: don't addref "this" as we'll cancel the // timer in the httpIndex destructor } } } NS_IMETHODIMP nsHTTPIndex::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue); } return(rv); } NS_IMETHODIMP nsHTTPIndex::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->Unassert(aSource, aProperty, aTarget); } return(rv); } NS_IMETHODIMP nsHTTPIndex::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->Change(aSource, aProperty, aOldTarget, aNewTarget); } return(rv); } NS_IMETHODIMP nsHTTPIndex::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->Move(aOldSource, aNewSource, aProperty, aTarget); } return(rv); } NS_IMETHODIMP nsHTTPIndex::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, bool *_retval) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval); } return(rv); } NS_IMETHODIMP nsHTTPIndex::AddObserver(nsIRDFObserver *aObserver) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->AddObserver(aObserver); } return(rv); } NS_IMETHODIMP nsHTTPIndex::RemoveObserver(nsIRDFObserver *aObserver) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->RemoveObserver(aObserver); } return(rv); } NS_IMETHODIMP nsHTTPIndex::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result) { if (!mInner) { *result = false; return NS_OK; } return mInner->HasArcIn(aNode, aArc, result); } NS_IMETHODIMP nsHTTPIndex::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result) { if (aArc == kNC_Child && isWellknownContainerURI(aSource)) { *result = true; return NS_OK; } if (mInner) { return mInner->HasArcOut(aSource, aArc, result); } *result = false; return NS_OK; } NS_IMETHODIMP nsHTTPIndex::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->ArcLabelsIn(aNode, _retval); } return(rv); } NS_IMETHODIMP nsHTTPIndex::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) { *_retval = nullptr; nsCOMPtr child, anonArcs; if (isWellknownContainerURI(aSource)) { NS_NewSingletonEnumerator(getter_AddRefs(child), kNC_Child); } if (mInner) { mInner->ArcLabelsOut(aSource, getter_AddRefs(anonArcs)); } return NS_NewUnionEnumerator(_retval, child, anonArcs); } NS_IMETHODIMP nsHTTPIndex::GetAllResources(nsISimpleEnumerator **_retval) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->GetAllResources(_retval); } return(rv); } NS_IMETHODIMP nsHTTPIndex::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments, bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsHTTPIndex::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsHTTPIndex::BeginUpdateBatch() { return mInner->BeginUpdateBatch(); } NS_IMETHODIMP nsHTTPIndex::EndUpdateBatch() { return mInner->EndUpdateBatch(); } NS_IMETHODIMP nsHTTPIndex::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) { nsresult rv = NS_ERROR_UNEXPECTED; if (mInner) { rv = mInner->GetAllCmds(aSource, _retval); } return(rv); } //---------------------------------------------------------------------- // // nsDirectoryViewerFactory // nsDirectoryViewerFactory::nsDirectoryViewerFactory() { } nsDirectoryViewerFactory::~nsDirectoryViewerFactory() { } NS_IMPL_ISUPPORTS(nsDirectoryViewerFactory, nsIDocumentLoaderFactory) NS_IMETHODIMP nsDirectoryViewerFactory::CreateInstance(const char *aCommand, nsIChannel* aChannel, nsILoadGroup* aLoadGroup, const nsACString& aContentType, nsIDocShell* aContainer, nsISupports* aExtraInfo, nsIStreamListener** aDocListenerResult, nsIContentViewer** aDocViewerResult) { nsresult rv; bool viewSource = FindInReadable(NS_LITERAL_CSTRING("view-source"), aContentType); if (!viewSource && Preferences::GetInt("network.dir.format", FORMAT_XUL) == FORMAT_XUL) { // ... and setup the original channel's content type (void)aChannel->SetContentType(NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml")); // This is where we shunt the HTTP/Index stream into our datasource, // and open the directory viewer XUL file as the content stream to // load in its place. // Create a dummy loader that will load a stub XUL document. nsCOMPtr catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; nsXPIDLCString contractID; rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "application/vnd.mozilla.xul+xml", getter_Copies(contractID)); if (NS_FAILED(rv)) return rv; nsCOMPtr factory(do_GetService(contractID, &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), "chrome://communicator/content/directory/directory.xul"); if (NS_FAILED(rv)) return rv; nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), uri, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER, aLoadGroup); if (NS_FAILED(rv)) return rv; nsCOMPtr listener; rv = factory->CreateInstance(aCommand, channel, aLoadGroup, NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml"), aContainer, aExtraInfo, getter_AddRefs(listener), aDocViewerResult); if (NS_FAILED(rv)) return rv; rv = channel->AsyncOpen2(listener); if (NS_FAILED(rv)) return rv; // Create an HTTPIndex object so that we can stuff it into the script context nsCOMPtr baseuri; rv = aChannel->GetURI(getter_AddRefs(baseuri)); if (NS_FAILED(rv)) return rv; nsCOMPtr requestor = do_QueryInterface(aContainer,&rv); if (NS_FAILED(rv)) return rv; nsCOMPtr httpindex; rv = nsHTTPIndex::Create(baseuri, requestor, getter_AddRefs(httpindex)); if (NS_FAILED(rv)) return rv; // Now shanghai the stream into our http-index parsing datasource // wrapper beastie. listener = do_QueryInterface(httpindex,&rv); *aDocListenerResult = listener.get(); NS_ADDREF(*aDocListenerResult); return NS_OK; } // setup the original channel's content type (void)aChannel->SetContentType(NS_LITERAL_CSTRING("text/html")); // Otherwise, lets use the html listing nsCOMPtr catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; nsXPIDLCString contractID; rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "text/html", getter_Copies(contractID)); if (NS_FAILED(rv)) return rv; nsCOMPtr factory(do_GetService(contractID, &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr listener; if (viewSource) { rv = factory->CreateInstance("view-source", aChannel, aLoadGroup, NS_LITERAL_CSTRING("text/html; x-view-type=view-source"), aContainer, aExtraInfo, getter_AddRefs(listener), aDocViewerResult); } else { rv = factory->CreateInstance("view", aChannel, aLoadGroup, NS_LITERAL_CSTRING("text/html"), aContainer, aExtraInfo, getter_AddRefs(listener), aDocViewerResult); } if (NS_FAILED(rv)) return rv; nsCOMPtr scs = do_GetService("@mozilla.org/streamConverters;1", &rv); if (NS_FAILED(rv)) return rv; rv = scs->AsyncConvertData("application/http-index-format", "text/html", listener, nullptr, aDocListenerResult); if (NS_FAILED(rv)) return rv; return NS_OK; } NS_IMETHODIMP nsDirectoryViewerFactory::CreateInstanceForDocument(nsISupports* aContainer, nsIDocument* aDocument, const char *aCommand, nsIContentViewer** aDocViewerResult) { NS_NOTYETIMPLEMENTED("didn't expect to get here"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDirectoryViewerFactory::CreateBlankDocument(nsILoadGroup *aLoadGroup, nsIPrincipal *aPrincipal, nsIDocument **_retval) { NS_NOTYETIMPLEMENTED("didn't expect to get here"); return NS_ERROR_NOT_IMPLEMENTED; }