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