diff options
Diffstat (limited to 'rdf/base/nsRDFXMLDataSource.cpp')
-rw-r--r-- | rdf/base/nsRDFXMLDataSource.cpp | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/rdf/base/nsRDFXMLDataSource.cpp b/rdf/base/nsRDFXMLDataSource.cpp new file mode 100644 index 000000000..0e9127420 --- /dev/null +++ b/rdf/base/nsRDFXMLDataSource.cpp @@ -0,0 +1,1179 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 data source that can read itself from and write itself to an + RDF/XML stream. + + For more information on the RDF/XML syntax, + see http://www.w3.org/TR/REC-rdf-syntax/. + + This code is based on the final W3C Recommendation, + http://www.w3.org/TR/1999/REC-rdf-syntax-19990222. + + + TO DO + ----- + + 1) Right now, the only kind of stream data sources that are _really_ + writable are "file:" URIs. (In fact, _all_ "file:" URIs are + writable, modulo file system permissions; this may lead to some + surprising behavior.) Eventually, it'd be great if we could open + an arbitrary nsIOutputStream on *any* URL, and Netlib could just + do the magic. + + 2) Implement a more terse output for "typed" nodes; that is, instead + of "RDF:Description type='ns:foo'", just output "ns:foo". + + 3) When re-serializing, we "cheat" for Descriptions that talk about + inline resources (i.e.., using the `ID' attribute specified in + [6.21]). Instead of writing an `ID="foo"' for the first instance, + and then `about="#foo"' for each subsequent instance, we just + _always_ write `about="#foo"'. + + We do this so that we can handle the case where an RDF container + has been assigned arbitrary properties: the spec says we can't + dangle the attributes directly off the container, so we need to + refer to it. Of course, with a little cleverness, we could fix + this. But who cares? + + 4) When re-serializing containers. We have to cheat on some + containers, and use an illegal "about=" construct. We do this to + handle containers that have been assigned URIs outside of the + local document. + + + Logging + ------- + + To turn on logging for this module, set + + MOZ_LOG=nsRDFXMLDataSource:5 + + */ + +#include "nsIFileStreams.h" +#include "nsIOutputStream.h" +#include "nsIFile.h" +#include "nsIFileChannel.h" +#include "nsIDTD.h" +#include "nsIRDFPurgeableDataSource.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIRDFContainerUtils.h" +#include "nsIRDFNode.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsIRDFService.h" +#include "nsIRDFXMLParser.h" +#include "nsIRDFXMLSerializer.h" +#include "nsIRDFXMLSink.h" +#include "nsIRDFXMLSource.h" +#include "nsISafeOutputStream.h" +#include "nsIServiceManager.h" +#include "nsIStreamListener.h" +#include "nsIURL.h" +#include "nsIFileURL.h" +#include "nsISafeOutputStream.h" +#include "nsIChannel.h" +#include "nsRDFCID.h" +#include "nsRDFBaseDataSources.h" +#include "nsCOMArray.h" +#include "nsXPIDLString.h" +#include "plstr.h" +#include "prio.h" +#include "prthread.h" +#include "rdf.h" +#include "rdfutil.h" +#include "mozilla/Logging.h" +#include "nsNameSpaceMap.h" +#include "nsCRT.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIScriptSecurityManager.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsNetUtil.h" +#include "nsIContentPolicy.h" +#include "nsContentUtils.h" + +#include "rdfIDataSource.h" + +//---------------------------------------------------------------------- +// +// RDFXMLDataSourceImpl +// + +class RDFXMLDataSourceImpl : public nsIRDFDataSource, + public nsIRDFRemoteDataSource, + public nsIRDFXMLSink, + public nsIRDFXMLSource, + public nsIStreamListener, + public rdfIDataSource, + public nsIInterfaceRequestor, + public nsIChannelEventSink +{ +protected: + enum LoadState { + eLoadState_Unloaded, + eLoadState_Pending, + eLoadState_Loading, + eLoadState_Loaded + }; + + nsCOMPtr<nsIRDFDataSource> mInner; + bool mIsWritable; // true if the document can be written back + bool mIsDirty; // true if the document should be written back + LoadState mLoadState; // what we're doing now + nsCOMArray<nsIRDFXMLSinkObserver> mObservers; + nsCOMPtr<nsIURI> mURL; + nsCOMPtr<nsIStreamListener> mListener; + nsNameSpaceMap mNameSpaces; + + // pseudo-constants + static int32_t gRefCnt; + static nsIRDFService* gRDFService; + + static mozilla::LazyLogModule gLog; + + nsresult Init(); + RDFXMLDataSourceImpl(void); + virtual ~RDFXMLDataSourceImpl(void); + nsresult rdfXMLFlush(nsIURI *aURI); + + friend nsresult + NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult); + + inline bool IsLoading() { + return (mLoadState == eLoadState_Pending) || + (mLoadState == eLoadState_Loading); + } + +public: + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(RDFXMLDataSourceImpl, + nsIRDFDataSource) + + // nsIRDFDataSource + NS_IMETHOD GetURI(char* *uri) override; + + NS_IMETHOD GetSource(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsIRDFResource** source) override { + return mInner->GetSource(property, target, tv, source); + } + + NS_IMETHOD GetSources(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsISimpleEnumerator** sources) override { + return mInner->GetSources(property, target, tv, sources); + } + + NS_IMETHOD GetTarget(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsIRDFNode** target) override { + return mInner->GetTarget(source, property, tv, target); + } + + NS_IMETHOD GetTargets(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsISimpleEnumerator** targets) override { + return mInner->GetTargets(source, property, tv, targets); + } + + NS_IMETHOD Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool tv) override; + + NS_IMETHOD Unassert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target) override; + + NS_IMETHOD Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) override; + + NS_IMETHOD Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) override; + + NS_IMETHOD HasAssertion(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + bool* hasAssertion) override { + return mInner->HasAssertion(source, property, target, tv, hasAssertion); + } + + NS_IMETHOD AddObserver(nsIRDFObserver* aObserver) override { + return mInner->AddObserver(aObserver); + } + + NS_IMETHOD RemoveObserver(nsIRDFObserver* aObserver) override { + return mInner->RemoveObserver(aObserver); + } + + NS_IMETHOD HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *_retval) override { + return mInner->HasArcIn(aNode, aArc, _retval); + } + + NS_IMETHOD HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *_retval) override { + return mInner->HasArcOut(aSource, aArc, _retval); + } + + NS_IMETHOD ArcLabelsIn(nsIRDFNode* node, + nsISimpleEnumerator** labels) override { + return mInner->ArcLabelsIn(node, labels); + } + + NS_IMETHOD ArcLabelsOut(nsIRDFResource* source, + nsISimpleEnumerator** labels) override { + return mInner->ArcLabelsOut(source, labels); + } + + NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) override { + return mInner->GetAllResources(aResult); + } + + NS_IMETHOD GetAllCmds(nsIRDFResource* source, + nsISimpleEnumerator/*<nsIRDFResource>*/** commands) override { + return mInner->GetAllCmds(source, commands); + } + + NS_IMETHOD IsCommandEnabled(nsISupports* aSources, + nsIRDFResource* aCommand, + nsISupports* aArguments, + bool* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD DoCommand(nsISupports* aSources, + nsIRDFResource* aCommand, + nsISupports* aArguments) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD BeginUpdateBatch() override { + return mInner->BeginUpdateBatch(); + } + + NS_IMETHOD EndUpdateBatch() override { + return mInner->EndUpdateBatch(); + } + + // nsIRDFRemoteDataSource interface + NS_DECL_NSIRDFREMOTEDATASOURCE + + // nsIRDFXMLSink interface + NS_DECL_NSIRDFXMLSINK + + // nsIRDFXMLSource interface + NS_DECL_NSIRDFXMLSOURCE + + // nsIRequestObserver + NS_DECL_NSIREQUESTOBSERVER + + // nsIStreamListener + NS_DECL_NSISTREAMLISTENER + + // nsIInterfaceRequestor + NS_DECL_NSIINTERFACEREQUESTOR + + // nsIChannelEventSink + NS_DECL_NSICHANNELEVENTSINK + + // rdfIDataSource + NS_IMETHOD VisitAllSubjects(rdfITripleVisitor *aVisitor) override { + nsresult rv; + nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mInner, &rv); + if (NS_FAILED(rv)) return rv; + return rdfds->VisitAllSubjects(aVisitor); + } + + NS_IMETHOD VisitAllTriples(rdfITripleVisitor *aVisitor) override { + nsresult rv; + nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mInner, &rv); + if (NS_FAILED(rv)) return rv; + return rdfds->VisitAllTriples(aVisitor); + } + + // Implementation methods + bool + MakeQName(nsIRDFResource* aResource, + nsString& property, + nsString& nameSpacePrefix, + nsString& nameSpaceURI); + + nsresult + SerializeAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue); + + nsresult + SerializeProperty(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty); + + bool + IsContainerProperty(nsIRDFResource* aProperty); + + nsresult + SerializeDescription(nsIOutputStream* aStream, + nsIRDFResource* aResource); + + nsresult + SerializeMember(nsIOutputStream* aStream, + nsIRDFResource* aContainer, + nsIRDFNode* aMember); + + nsresult + SerializeContainer(nsIOutputStream* aStream, + nsIRDFResource* aContainer); + + nsresult + SerializePrologue(nsIOutputStream* aStream); + + nsresult + SerializeEpilogue(nsIOutputStream* aStream); + + bool + IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType); + +protected: + nsresult + BlockingParse(nsIURI* aURL, nsIStreamListener* aConsumer); +}; + +int32_t RDFXMLDataSourceImpl::gRefCnt = 0; +nsIRDFService* RDFXMLDataSourceImpl::gRDFService; + +mozilla::LazyLogModule RDFXMLDataSourceImpl::gLog("nsRDFXMLDataSource"); + +static const char kFileURIPrefix[] = "file:"; +static const char kResourceURIPrefix[] = "resource:"; + + +//---------------------------------------------------------------------- + +nsresult +NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + RDFXMLDataSourceImpl* datasource = new RDFXMLDataSourceImpl(); + if (! datasource) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + rv = datasource->Init(); + + if (NS_FAILED(rv)) { + delete datasource; + return rv; + } + + NS_ADDREF(datasource); + *aResult = datasource; + return NS_OK; +} + + +RDFXMLDataSourceImpl::RDFXMLDataSourceImpl(void) + : mIsWritable(true), + mIsDirty(false), + mLoadState(eLoadState_Unloaded) +{ +} + + +nsresult +RDFXMLDataSourceImpl::Init() +{ + nsresult rv; + NS_DEFINE_CID(kRDFInMemoryDataSourceCID, NS_RDFINMEMORYDATASOURCE_CID); + mInner = do_CreateInstance(kRDFInMemoryDataSourceCID, &rv); + if (NS_FAILED(rv)) return rv; + + if (gRefCnt++ == 0) { + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + rv = CallGetService(kRDFServiceCID, &gRDFService); + + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + + +RDFXMLDataSourceImpl::~RDFXMLDataSourceImpl(void) +{ + // Unregister first so that nobody else tries to get us. + (void) gRDFService->UnregisterDataSource(this); + + // Now flush contents + (void) Flush(); + + // Release RDF/XML sink observers + mObservers.Clear(); + + if (--gRefCnt == 0) + NS_IF_RELEASE(gRDFService); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(RDFXMLDataSourceImpl) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_0(RDFXMLDataSourceImpl) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RDFXMLDataSourceImpl) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RDFXMLDataSourceImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RDFXMLDataSourceImpl) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RDFXMLDataSourceImpl) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFRemoteDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFXMLSink) + NS_INTERFACE_MAP_ENTRY(nsIRDFXMLSource) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(rdfIDataSource) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRDFDataSource) +NS_INTERFACE_MAP_END + +// nsIInterfaceRequestor +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetInterface(const nsIID& aIID, void** aSink) +{ + return QueryInterface(aIID, aSink); +} + +nsresult +RDFXMLDataSourceImpl::BlockingParse(nsIURI* aURL, nsIStreamListener* aConsumer) +{ + nsresult rv; + + // XXX I really hate the way that we're spoon-feeding this stuff + // to the parser: it seems like this is something that netlib + // should be able to do by itself. + + nsCOMPtr<nsIChannel> channel; + + // Null LoadGroup ? + rv = NS_NewChannel(getter_AddRefs(channel), + aURL, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + + if (NS_FAILED(rv)) return rv; + nsCOMPtr<nsIInputStream> in; + rv = channel->Open2(getter_AddRefs(in)); + + // Report success if the file doesn't exist, but propagate other errors. + if (rv == NS_ERROR_FILE_NOT_FOUND) return NS_OK; + if (NS_FAILED(rv)) return rv; + + if (! in) { + NS_ERROR("no input stream"); + return NS_ERROR_FAILURE; + } + + // Wrap the channel's input stream in a buffered stream to ensure that + // ReadSegments is implemented (which OnDataAvailable expects). + nsCOMPtr<nsIInputStream> bufStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), in, + 4096 /* buffer size */); + if (NS_FAILED(rv)) return rv; + + // Notify load observers + int32_t i; + for (i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i]; + + if (obs) { + obs->OnBeginLoad(this); + } + } + + rv = aConsumer->OnStartRequest(channel, nullptr); + + uint64_t offset = 0; + while (NS_SUCCEEDED(rv)) { + // Skip ODA if the channel is canceled + channel->GetStatus(&rv); + if (NS_FAILED(rv)) + break; + + uint64_t avail; + if (NS_FAILED(rv = bufStream->Available(&avail))) + break; // error + + if (avail == 0) + break; // eof + + if (avail > UINT32_MAX) + avail = UINT32_MAX; + + rv = aConsumer->OnDataAvailable(channel, nullptr, bufStream, offset, (uint32_t)avail); + if (NS_SUCCEEDED(rv)) + offset += avail; + } + + if (NS_FAILED(rv)) + channel->Cancel(rv); + + channel->GetStatus(&rv); + aConsumer->OnStopRequest(channel, nullptr, rv); + + // Notify load observers + for (i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i]; + + if (obs) { + if (NS_FAILED(rv)) + obs->OnError(this, rv, nullptr); + + obs->OnEndLoad(this); + } + } + + return rv; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetLoaded(bool* _result) +{ + *_result = (mLoadState == eLoadState_Loaded); + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Init(const char* uri) +{ + NS_PRECONDITION(mInner != nullptr, "not initialized"); + if (! mInner) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + rv = NS_NewURI(getter_AddRefs(mURL), nsDependentCString(uri)); + if (NS_FAILED(rv)) return rv; + + // XXX this is a hack: any "file:" URI is considered writable. All + // others are considered read-only. + if ((PL_strncmp(uri, kFileURIPrefix, sizeof(kFileURIPrefix) - 1) != 0) && + (PL_strncmp(uri, kResourceURIPrefix, sizeof(kResourceURIPrefix) - 1) != 0)) { + mIsWritable = false; + } + + rv = gRDFService->RegisterDataSource(this, false); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetURI(char* *aURI) +{ + *aURI = nullptr; + if (!mURL) { + return NS_OK; + } + + nsAutoCString spec; + nsresult rv = mURL->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + *aURI = ToNewCString(spec); + if (!*aURI) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) +{ + // We don't accept assertions unless we're writable (except in the + // case that we're actually _reading_ the datasource in). + nsresult rv; + + if (IsLoading()) { + bool hasAssertion = false; + + nsCOMPtr<nsIRDFPurgeableDataSource> gcable = do_QueryInterface(mInner); + if (gcable) { + rv = gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &hasAssertion); + if (NS_FAILED(rv)) return rv; + } + + rv = NS_RDF_ASSERTION_ACCEPTED; + + if (! hasAssertion) { + rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue); + + if (NS_SUCCEEDED(rv) && gcable) { + // Now mark the new assertion, so it doesn't get + // removed when we sweep. Ignore rv, because we want + // to return what mInner->Assert() gave us. + bool didMark; + (void) gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &didMark); + } + + if (NS_FAILED(rv)) return rv; + } + + return rv; + } + else if (mIsWritable) { + rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue); + + if (rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + + return rv; + } + else { + return NS_RDF_ASSERTION_REJECTED; + } +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Unassert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target) +{ + // We don't accept assertions unless we're writable (except in the + // case that we're actually _reading_ the datasource in). + nsresult rv; + + if (IsLoading() || mIsWritable) { + rv = mInner->Unassert(source, property, target); + if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + } + else { + rv = NS_RDF_ASSERTION_REJECTED; + } + + return rv; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + nsresult rv; + + if (IsLoading() || mIsWritable) { + rv = mInner->Change(aSource, aProperty, aOldTarget, aNewTarget); + + if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + } + else { + rv = NS_RDF_ASSERTION_REJECTED; + } + + return rv; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + nsresult rv; + + if (IsLoading() || mIsWritable) { + rv = mInner->Move(aOldSource, aNewSource, aProperty, aTarget); + if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + } + else { + rv = NS_RDF_ASSERTION_REJECTED; + } + + return rv; +} + + +nsresult +RDFXMLDataSourceImpl::rdfXMLFlush(nsIURI *aURI) +{ + + nsresult rv; + + { + // Quick and dirty check to see if we're in XPCOM shutdown. If + // we are, we're screwed: it's too late to serialize because + // many of the services that we'll need to acquire to properly + // write the file will be unaquirable. + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + nsCOMPtr<nsIRDFService> dummy = do_GetService(kRDFServiceCID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("unable to Flush() dirty datasource during XPCOM shutdown"); + return rv; + } + } + + // Is it a file? If so, we can write to it. Some day, it'd be nice + // if we didn't care what kind of stream this was... + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI); + + if (fileURL) { + nsCOMPtr<nsIFile> file; + fileURL->GetFile(getter_AddRefs(file)); + if (file) { + // get a safe output stream, so we don't clobber the datasource file unless + // all the writes succeeded. + nsCOMPtr<nsIOutputStream> out; + rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out), + file, + PR_WRONLY | PR_CREATE_FILE, + /*octal*/ 0666, + 0); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIOutputStream> bufferedOut; + rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 4096); + if (NS_FAILED(rv)) return rv; + + rv = Serialize(bufferedOut); + if (NS_FAILED(rv)) return rv; + + // All went ok. Maybe except for problems in Write(), but the stream detects + // that for us + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(bufferedOut, &rv); + if (NS_FAILED(rv)) return rv; + + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save datasource file! possible dataloss"); + return rv; + } + } + } + + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::FlushTo(const char *aURI) +{ + NS_PRECONDITION(aURI != nullptr, "not initialized"); + if (!aURI) + return NS_ERROR_NULL_POINTER; + + // XXX this is a hack: any "file:" URI is considered writable. All + // others are considered read-only. + if ((PL_strncmp(aURI, kFileURIPrefix, sizeof(kFileURIPrefix) - 1) != 0) && + (PL_strncmp(aURI, kResourceURIPrefix, sizeof(kResourceURIPrefix) - 1) != 0)) + { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsCOMPtr<nsIURI> url; + nsresult rv = NS_NewURI(getter_AddRefs(url), aURI); + if (NS_FAILED(rv)) + return rv; + rv = rdfXMLFlush(url); + return rv; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Flush(void) +{ + if (!mIsWritable || !mIsDirty) + return NS_OK; + + // while it is not fatal if mURL is not set, + // indicate failure since we can't flush back to an unknown origin + if (! mURL) + return NS_ERROR_NOT_INITIALIZED; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] flush(%s)", this, mURL->GetSpecOrDefault().get())); + } + + nsresult rv; + if (NS_SUCCEEDED(rv = rdfXMLFlush(mURL))) + { + mIsDirty = false; + } + return rv; +} + + +//---------------------------------------------------------------------- +// +// nsIRDFXMLDataSource methods +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetReadOnly(bool* aIsReadOnly) +{ + *aIsReadOnly = !mIsWritable; + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::SetReadOnly(bool aIsReadOnly) +{ + if (mIsWritable && aIsReadOnly) + mIsWritable = false; + + return NS_OK; +} + +// nsIChannelEventSink + +// This code is copied from nsSameOriginChecker::OnChannelRedirect. See +// bug 475940 on providing this code in a shared location. +NS_IMETHODIMP +RDFXMLDataSourceImpl::AsyncOnChannelRedirect(nsIChannel *aOldChannel, + nsIChannel *aNewChannel, + uint32_t aFlags, + nsIAsyncVerifyRedirectCallback *cb) +{ + NS_PRECONDITION(aNewChannel, "Redirecting to null channel?"); + + nsresult rv; + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> oldPrincipal; + secMan->GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal)); + + nsCOMPtr<nsIURI> newURI; + aNewChannel->GetURI(getter_AddRefs(newURI)); + nsCOMPtr<nsIURI> newOriginalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI)); + + NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI); + + rv = oldPrincipal->CheckMayLoad(newURI, false, false); + if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) { + rv = oldPrincipal->CheckMayLoad(newOriginalURI, false, false); + } + + if (NS_FAILED(rv)) + return rv; + + cb->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Refresh(bool aBlocking) +{ + nsAutoCString spec; + if (mURL) { + spec = mURL->GetSpecOrDefault(); + } + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] refresh(%s) %sblocking", this, spec.get(), (aBlocking ? "" : "non"))); + + // If an asynchronous load is already pending, then just let it do + // the honors. + if (IsLoading()) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] refresh(%s) a load was pending", this, spec.get())); + + if (aBlocking) { + NS_WARNING("blocking load requested when async load pending"); + return NS_ERROR_FAILURE; + } + else { + return NS_OK; + } + } + + if (! mURL) + return NS_ERROR_FAILURE; + nsCOMPtr<nsIRDFXMLParser> parser = do_CreateInstance("@mozilla.org/rdf/xml-parser;1"); + if (! parser) + return NS_ERROR_FAILURE; + + nsresult rv = parser->ParseAsync(this, mURL, getter_AddRefs(mListener)); + if (NS_FAILED(rv)) return rv; + + if (aBlocking) { + rv = BlockingParse(mURL, this); + + mListener = nullptr; // release the parser + + if (NS_FAILED(rv)) return rv; + } + else { + // Null LoadGroup ? + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), + mURL, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + this); // aCallbacks + NS_ENSURE_SUCCESS(rv, rv); + rv = channel->AsyncOpen2(this); + NS_ENSURE_SUCCESS(rv, rv); + + // So we don't try to issue two asynchronous loads at once. + mLoadState = eLoadState_Pending; + } + + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::BeginLoad(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] begin-load(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + mLoadState = eLoadState_Loading; + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i]; + + if (obs) { + obs->OnBeginLoad(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Interrupt(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] interrupt(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i]; + + if (obs) { + obs->OnInterrupt(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Resume(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] resume(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i]; + + if (obs) { + obs->OnResume(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::EndLoad(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] end-load(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + mLoadState = eLoadState_Loaded; + + // Clear out any unmarked assertions from the datasource. + nsCOMPtr<nsIRDFPurgeableDataSource> gcable = do_QueryInterface(mInner); + if (gcable) { + gcable->Sweep(); + } + + // Notify load observers + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i]; + + if (obs) { + obs->OnEndLoad(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::AddNameSpace(nsIAtom* aPrefix, const nsString& aURI) +{ + mNameSpaces.Put(aURI, aPrefix); + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::AddXMLSinkObserver(nsIRDFXMLSinkObserver* aObserver) +{ + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.AppendObject(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::RemoveXMLSinkObserver(nsIRDFXMLSinkObserver* aObserver) +{ + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.RemoveObject(aObserver); + + return NS_OK; +} + + +//---------------------------------------------------------------------- +// +// nsIRequestObserver +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + return mListener->OnStartRequest(request, ctxt); +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::OnStopRequest(nsIRequest *request, + nsISupports *ctxt, + nsresult status) +{ + if (NS_FAILED(status)) { + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes + // itself as an observer + nsCOMPtr<nsIRDFXMLSinkObserver> obs = mObservers[i]; + + if (obs) { + obs->OnError(this, status, nullptr); + } + } + } + + nsresult rv; + rv = mListener->OnStopRequest(request, ctxt, status); + + mListener = nullptr; // release the parser + + return rv; +} + +//---------------------------------------------------------------------- +// +// nsIStreamListener +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::OnDataAvailable(nsIRequest *request, + nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, + uint32_t count) +{ + return mListener->OnDataAvailable(request, ctxt, inStr, sourceOffset, count); +} + +//---------------------------------------------------------------------- +// +// nsIRDFXMLSource +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Serialize(nsIOutputStream* aStream) +{ + nsresult rv; + nsCOMPtr<nsIRDFXMLSerializer> serializer + = do_CreateInstance("@mozilla.org/rdf/xml-serializer;1", &rv); + + if (! serializer) + return rv; + + rv = serializer->Init(this); + if (NS_FAILED(rv)) return rv; + + // Add any namespace information that we picked up when reading + // the RDF/XML + nsNameSpaceMap::const_iterator last = mNameSpaces.last(); + for (nsNameSpaceMap::const_iterator iter = mNameSpaces.first(); + iter != last; ++iter) { + // We might wanna change nsIRDFXMLSerializer to nsACString and + // use a heap allocated buffer here in the future. + NS_ConvertUTF8toUTF16 uri(iter->mURI); + serializer->AddNameSpace(iter->mPrefix, uri); + } + + // Serialize! + nsCOMPtr<nsIRDFXMLSource> source = do_QueryInterface(serializer); + if (! source) + return NS_ERROR_FAILURE; + + return source->Serialize(aStream); +} |