Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions src/node_blob.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ using v8::Int32;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::SnapshotCreator;
Expand Down Expand Up @@ -125,7 +126,7 @@ void BlobFromFilePath(const FunctionCallbackInfo<Value>& args) {
Blob::Create(env, DataQueue::CreateIdempotent(std::move(entries)))) {
Local<Value> vals[2]{
blob->object(),
Uint32::NewFromUnsigned(env->isolate(), blob->length()),
Number::New(env->isolate(), static_cast<double>(blob->length())),
};
args.GetReturnValue().Set(
Array::New(env->isolate(), &vals[0], arraysize(vals)));
Expand Down Expand Up @@ -274,10 +275,10 @@ void Blob::ToSlice(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Blob* blob;
ASSIGN_OR_RETURN_UNWRAP(&blob, args.This());
CHECK(args[0]->IsUint32());
CHECK(args[1]->IsUint32());
size_t start = args[0].As<Uint32>()->Value();
size_t end = args[1].As<Uint32>()->Value();
CHECK(args[0]->IsNumber());
CHECK(args[1]->IsNumber());
uint64_t start = static_cast<uint64_t>(args[0].As<Number>()->Value());
uint64_t end = static_cast<uint64_t>(args[1].As<Number>()->Value());
BaseObjectPtr<Blob> slice = blob->Slice(env, start, end);
if (slice) args.GetReturnValue().Set(slice->object());
}
Expand All @@ -286,9 +287,10 @@ void Blob::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("data_queue_", data_queue_, "std::shared_ptr<DataQueue>");
}

BaseObjectPtr<Blob> Blob::Slice(Environment* env, size_t start, size_t end) {
return Create(env,
this->data_queue_->slice(start, static_cast<uint64_t>(end)));
BaseObjectPtr<Blob> Blob::Slice(Environment* env,
uint64_t start,
uint64_t end) {
return Create(env, this->data_queue_->slice(start, end));
}

Blob::Blob(Environment* env,
Expand Down Expand Up @@ -459,7 +461,7 @@ void Blob::StoreDataObject(const FunctionCallbackInfo<Value>& args) {

CHECK(args[0]->IsString()); // ID key
CHECK(Blob::HasInstance(realm->env(), args[1])); // Blob
CHECK(args[2]->IsUint32()); // Length
CHECK(args[2]->IsNumber()); // Length
CHECK(args[3]->IsString()); // Type

BlobBindingData* binding_data = realm->GetBindingData<BlobBindingData>();
Expand All @@ -469,7 +471,7 @@ void Blob::StoreDataObject(const FunctionCallbackInfo<Value>& args) {
Blob* blob;
ASSIGN_OR_RETURN_UNWRAP(&blob, args[1]);

size_t length = args[2].As<Uint32>()->Value();
uint64_t length = static_cast<uint64_t>(args[2].As<Number>()->Value());
Utf8Value type(isolate, args[3]);

binding_data->store_data_object(
Expand Down Expand Up @@ -526,9 +528,10 @@ void Blob::GetDataObject(const FunctionCallbackInfo<Value>& args) {
return;
}

Local<Value> values[] = {stored.blob->object(),
Uint32::NewFromUnsigned(isolate, stored.length),
type};
Local<Value> values[] = {
stored.blob->object(),
Number::New(isolate, static_cast<double>(stored.length)),
type};

args.GetReturnValue().Set(Array::New(isolate, values, arraysize(values)));
}
Expand All @@ -540,7 +543,9 @@ void BlobBindingData::StoredDataObject::MemoryInfo(
}

BlobBindingData::StoredDataObject::StoredDataObject(
const BaseObjectPtr<Blob>& blob_, size_t length_, const std::string& type_)
const BaseObjectPtr<Blob>& blob_,
uint64_t length_,
const std::string& type_)
: blob(blob_), length(length_), type(type_) {}

BlobBindingData::BlobBindingData(Realm* realm, Local<Object> wrap)
Expand Down
8 changes: 4 additions & 4 deletions src/node_blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ class Blob : public BaseObject {
SET_MEMORY_INFO_NAME(Blob)
SET_SELF_SIZE(Blob)

BaseObjectPtr<Blob> Slice(Environment* env, size_t start, size_t end);
BaseObjectPtr<Blob> Slice(Environment* env, uint64_t start, uint64_t end);

inline size_t length() const { return this->data_queue_->size().value(); }
inline uint64_t length() const { return this->data_queue_->size().value(); }

class BlobTransferData : public worker::TransferData {
public:
Expand Down Expand Up @@ -129,13 +129,13 @@ class BlobBindingData : public SnapshotableObject {

struct StoredDataObject : public MemoryRetainer {
BaseObjectPtr<Blob> blob;
size_t length;
uint64_t length;
std::string type;

StoredDataObject() = default;

StoredDataObject(const BaseObjectPtr<Blob>& blob_,
size_t length_,
uint64_t length_,
const std::string& type_);

void MemoryInfo(MemoryTracker* tracker) const override;
Expand Down
49 changes: 49 additions & 0 deletions test/pummel/test-blob-large-file-backed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

// This tests that a file-backed Blob whose size exceeds the range of a 32-bit
// unsigned integer reports its real size, can be sliced past that boundary,
// and survives a round trip through URL.createObjectURL().

const common = require('../common');

common.skipIf32Bits();

const assert = require('assert');
const fs = require('fs');
const { resolveObjectURL } = require('buffer');

const tmpdir = require('../common/tmpdir');
tmpdir.refresh();

const filepath = tmpdir.resolve('large-blob.bin');
const size = 5 * 1024 * 1024 * 1024; // 5GB, sparse
const message = 'Large File';

try {
const fd = fs.openSync(filepath, 'w+');
fs.ftruncateSync(fd, size);
const buf = Buffer.from(message);
fs.writeSync(fd, buf, 0, buf.length, size - buf.length);
fs.closeSync(fd);
} catch (e) {
if (e.code !== 'ENOSPC') {
throw e;
}
common.skip('insufficient disk space');
}

(async () => {
const blob = await fs.openAsBlob(filepath);
assert.strictEqual(blob.size, size);

const tail = blob.slice(size - message.length, size);
assert.strictEqual(tail.size, message.length);
assert.strictEqual(await tail.text(), message);

const url = URL.createObjectURL(blob);
try {
assert.strictEqual(resolveObjectURL(url).size, size);
} finally {
URL.revokeObjectURL(url);
}
})().then(common.mustCall());
Loading