gecko-dev/rdf/base/nsRDFXMLDataSource.cpp
2016-01-29 10:15:38 -08:00

1198 lines
34 KiB
C++

/* -*- 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
NSPR_LOG_MODULES=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 PRLogModuleInfo* 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(nsISupportsArray/*<nsIRDFResource>*/* aSources,
nsIRDFResource* aCommand,
nsISupportsArray/*<nsIRDFResource>*/* aArguments,
bool* aResult) override {
return mInner->IsCommandEnabled(aSources, aCommand, aArguments, aResult);
}
NS_IMETHOD DoCommand(nsISupportsArray/*<nsIRDFResource>*/* aSources,
nsIRDFResource* aCommand,
nsISupportsArray/*<nsIRDFResource>*/* aArguments) override {
// XXX Uh oh, this could cause problems wrt. the "dirty" flag
// if it changes the in-memory store's internal state.
return mInner->DoCommand(aSources, aCommand, aArguments);
}
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;
PRLogModuleInfo* RDFXMLDataSourceImpl::gLog;
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)
{
if (! gLog)
gLog = PR_NewLogModule("nsRDFXMLDataSource");
}
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;
nsCOMPtr<nsIRequest> request;
// 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;
mURL->GetSpec(spec);
*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)) {
nsAutoCString spec;
mURL->GetSpec(spec);
MOZ_LOG(gLog, LogLevel::Debug,
("rdfxml[%p] flush(%s)", this, spec.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) {
mURL->GetSpec(spec);
}
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)) {
nsAutoCString spec;
if (mURL) {
mURL->GetSpec(spec);
}
MOZ_LOG(gLog, LogLevel::Debug,
("rdfxml[%p] begin-load(%s)", this, spec.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)) {
nsAutoCString spec;
if (mURL) {
mURL->GetSpec(spec);
}
MOZ_LOG(gLog, LogLevel::Debug,
("rdfxml[%p] interrupt(%s)", this, spec.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)) {
nsAutoCString spec;
if (mURL) {
mURL->GetSpec(spec);
}
MOZ_LOG(gLog, LogLevel::Debug,
("rdfxml[%p] resume(%s)", this, spec.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)) {
nsAutoCString spec;
if (mURL) {
mURL->GetSpec(spec);
}
MOZ_LOG(gLog, LogLevel::Debug,
("rdfxml[%p] end-load(%s)", this, spec.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);
}