blob: 4dbec1282744403b351c0f025a61513196e89a3f [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Blob.h"
#include "BlobBuilder.h"
#include "BlobLoader.h"
#include "BlobPart.h"
#include "BlobURL.h"
#include "File.h"
#include "JSDOMPromiseDeferred.h"
#include "PolicyContainer.h"
#include "ReadableStream.h"
#include "ReadableStreamSource.h"
#include "ScriptExecutionContext.h"
#include "SharedBuffer.h"
#include "ThreadableBlobRegistry.h"
#include <wtf/IsoMallocInlines.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/ThreadSafeRefCounted.h>
#include <wtf/text/CString.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(Blob);
class BlobURLRegistry final : public URLRegistry {
public:
void registerURL(ScriptExecutionContext&, const URL&, URLRegistrable&) final;
void unregisterURL(const URL&) final;
static URLRegistry& registry();
};
void BlobURLRegistry::registerURL(ScriptExecutionContext& context, const URL& publicURL, URLRegistrable& blob)
{
ASSERT(&blob.registry() == this);
ThreadableBlobRegistry::registerBlobURL(context.securityOrigin(), context.policyContainer(), publicURL, static_cast<Blob&>(blob).url());
}
void BlobURLRegistry::unregisterURL(const URL& url)
{
ThreadableBlobRegistry::unregisterBlobURL(url);
}
URLRegistry& BlobURLRegistry::registry()
{
static NeverDestroyed<BlobURLRegistry> instance;
return instance;
}
Blob::Blob(UninitializedContructor, ScriptExecutionContext* context, URL&& url, String&& type)
: ActiveDOMObject(context)
, m_type(WTFMove(type))
, m_internalURL(WTFMove(url))
{
}
Blob::Blob(ScriptExecutionContext* context)
: ActiveDOMObject(context)
, m_size(0)
, m_internalURL(BlobURL::createInternalURL())
{
ThreadableBlobRegistry::registerBlobURL(m_internalURL, { }, { });
}
static Vector<BlobPart> buildBlobData(Vector<BlobPartVariant>&& blobPartVariants, const BlobPropertyBag& propertyBag)
{
BlobBuilder builder(propertyBag.endings);
for (auto& blobPartVariant : blobPartVariants) {
WTF::switchOn(blobPartVariant,
[&] (auto& part) {
builder.append(WTFMove(part));
}
);
}
return builder.finalize();
}
Blob::Blob(ScriptExecutionContext& context, Vector<BlobPartVariant>&& blobPartVariants, const BlobPropertyBag& propertyBag)
: ActiveDOMObject(&context)
, m_type(normalizedContentType(propertyBag.type))
, m_internalURL(BlobURL::createInternalURL())
{
ThreadableBlobRegistry::registerBlobURL(m_internalURL, buildBlobData(WTFMove(blobPartVariants), propertyBag), m_type);
}
Blob::Blob(ScriptExecutionContext* context, Vector<uint8_t>&& data, const String& contentType)
: ActiveDOMObject(context)
, m_type(contentType)
, m_size(data.size())
, m_internalURL(BlobURL::createInternalURL())
{
ThreadableBlobRegistry::registerBlobURL(m_internalURL, { BlobPart(WTFMove(data)) }, contentType);
}
Blob::Blob(ReferencingExistingBlobConstructor, ScriptExecutionContext* context, const Blob& blob)
: ActiveDOMObject(context)
, m_type(blob.type())
, m_size(blob.size())
, m_internalURL(BlobURL::createInternalURL())
{
ThreadableBlobRegistry::registerBlobURL(m_internalURL, { BlobPart(blob.url()) } , m_type);
}
Blob::Blob(DeserializationContructor, ScriptExecutionContext* context, const URL& srcURL, const String& type, std::optional<unsigned long long> size, const String& fileBackedPath)
: ActiveDOMObject(context)
, m_type(normalizedContentType(type))
, m_size(size)
, m_internalURL(BlobURL::createInternalURL())
{
if (fileBackedPath.isEmpty())
ThreadableBlobRegistry::registerBlobURL(nullptr, { }, m_internalURL, srcURL);
else
ThreadableBlobRegistry::registerBlobURLOptionallyFileBacked(m_internalURL, srcURL, fileBackedPath, m_type);
}
Blob::Blob(ScriptExecutionContext* context, const URL& srcURL, long long start, long long end, const String& type)
: ActiveDOMObject(context)
, m_type(normalizedContentType(type))
, m_internalURL(BlobURL::createInternalURL())
// m_size is not necessarily equal to end - start so we do not initialize it here.
{
ThreadableBlobRegistry::registerBlobURLForSlice(m_internalURL, srcURL, start, end, m_type);
}
Blob::~Blob()
{
ThreadableBlobRegistry::unregisterBlobURL(m_internalURL);
while (!m_blobLoaders.isEmpty())
(*m_blobLoaders.begin())->cancel();
}
Ref<Blob> Blob::slice(ScriptExecutionContext& context, long long start, long long end, const String& contentType) const
{
auto blob = adoptRef(*new Blob(&context, m_internalURL, start, end, contentType));
blob->suspendIfNeeded();
return blob;
}
unsigned long long Blob::size() const
{
if (!m_size) {
// FIXME: JavaScript cannot represent sizes as large as unsigned long long, we need to
// come up with an exception to throw if file size is not representable.
unsigned long long actualSize = ThreadableBlobRegistry::blobSize(m_internalURL);
m_size = isInBounds<long long>(actualSize) ? actualSize : 0;
}
return *m_size;
}
bool Blob::isValidContentType(const String& contentType)
{
// FIXME: Do we really want to treat the empty string and null string as valid content types?
unsigned length = contentType.length();
for (unsigned i = 0; i < length; ++i) {
if (contentType[i] < 0x20 || contentType[i] > 0x7e)
return false;
}
return true;
}
String Blob::normalizedContentType(const String& contentType)
{
if (!isValidContentType(contentType))
return emptyString();
return contentType.convertToASCIILowercase();
}
void Blob::loadBlob(ScriptExecutionContext& context, FileReaderLoader::ReadType readType, CompletionHandler<void(BlobLoader&)>&& completionHandler)
{
auto blobLoader = makeUnique<BlobLoader>([this, pendingActivity = makePendingActivity(*this), completionHandler = WTFMove(completionHandler)](BlobLoader& blobLoader) mutable {
completionHandler(blobLoader);
m_blobLoaders.take(&blobLoader);
});
blobLoader->start(*this, &context, readType);
if (blobLoader->isLoading())
m_blobLoaders.add(WTFMove(blobLoader));
}
void Blob::text(ScriptExecutionContext& context, Ref<DeferredPromise>&& promise)
{
loadBlob(context, FileReaderLoader::ReadAsText, [promise = WTFMove(promise)](BlobLoader& blobLoader) mutable {
if (auto optionalErrorCode = blobLoader.errorCode()) {
promise->reject(Exception { *optionalErrorCode });
return;
}
promise->resolve<IDLDOMString>(blobLoader.stringResult());
});
}
void Blob::arrayBuffer(ScriptExecutionContext& context, Ref<DeferredPromise>&& promise)
{
loadBlob(context, FileReaderLoader::ReadAsArrayBuffer, [promise = WTFMove(promise)](BlobLoader& blobLoader) mutable {
if (auto optionalErrorCode = blobLoader.errorCode()) {
promise->reject(Exception { *optionalErrorCode });
return;
}
auto arrayBuffer = blobLoader.arrayBufferResult();
if (!arrayBuffer) {
promise->reject(Exception { InvalidStateError });
return;
}
promise->resolve<IDLArrayBuffer>(*arrayBuffer);
});
}
ExceptionOr<Ref<ReadableStream>> Blob::stream(ScriptExecutionContext& scriptExecutionContext)
{
class BlobStreamSource : public FileReaderLoaderClient, public ReadableStreamSource {
public:
BlobStreamSource(ScriptExecutionContext& scriptExecutionContext, Blob& blob)
: m_loader(makeUniqueRef<FileReaderLoader>(FileReaderLoader::ReadType::ReadAsArrayBuffer, this))
{
m_loader->start(&scriptExecutionContext, blob);
}
private:
// ReadableStreamSource
void setActive() final { }
void setInactive() final { }
void doStart() final
{
m_isStarted = true;
if (m_exception)
controller().error(*m_exception);
}
void doPull() final { }
void doCancel() final
{
m_loader->cancel();
}
// FileReaderLoaderClient
void didStartLoading() final { }
void didReceiveData() final
{
auto result = m_loader->arrayBufferResult();
if (!result)
return;
if (m_loader->isCompleted() && !m_bytesRead)
controller().enqueue(WTFMove(result));
else {
auto bytesLoaded = m_loader->bytesLoaded();
controller().enqueue(result->slice(m_bytesRead, bytesLoaded));
m_bytesRead = bytesLoaded;
}
}
void didFinishLoading() final
{
controller().close();
}
void didFail(ExceptionCode code) final
{
Exception exception { code };
if (!m_isStarted) {
m_exception = WTFMove(exception);
return;
}
controller().error(exception);
}
UniqueRef<FileReaderLoader> m_loader;
size_t m_bytesRead { 0 };
bool m_isStarted { false };
std::optional<Exception> m_exception;
};
auto* globalObject = scriptExecutionContext.globalObject();
if (!globalObject)
return Exception { InvalidStateError };
return ReadableStream::create(*globalObject, adoptRef(*new BlobStreamSource(scriptExecutionContext, *this)));
}
#if ASSERT_ENABLED
bool Blob::isNormalizedContentType(const String& contentType)
{
// FIXME: Do we really want to treat the empty string and null string as valid content types?
unsigned length = contentType.length();
for (size_t i = 0; i < length; ++i) {
if (contentType[i] < 0x20 || contentType[i] > 0x7e)
return false;
if (isASCIIUpper(contentType[i]))
return false;
}
return true;
}
bool Blob::isNormalizedContentType(const CString& contentType)
{
// FIXME: Do we really want to treat the empty string and null string as valid content types?
size_t length = contentType.length();
const char* characters = contentType.data();
for (size_t i = 0; i < length; ++i) {
if (characters[i] < 0x20 || characters[i] > 0x7e)
return false;
if (isASCIIUpper(characters[i]))
return false;
}
return true;
}
#endif // ASSERT_ENABLED
URLRegistry& Blob::registry() const
{
return BlobURLRegistry::registry();
}
const char* Blob::activeDOMObjectName() const
{
return "Blob";
}
BlobURLHandle Blob::handle() const
{
return BlobURLHandle { m_internalURL };
}
} // namespace WebCore