Skip to content

Commit b9aad79

Browse files
committed
Fixing bug that requests blocks outside of file byte range
BlockedSource now honours the reported file size Total file size is parsed from Content-Range header in HTTP requests
1 parent 8ef472f commit b9aad79

File tree

2 files changed

+82
-6
lines changed

2 files changed

+82
-6
lines changed

src/source.js

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import http from 'http';
44
import https from 'https';
55
import urlMod from 'url';
66

7+
import { parseContentRange } from './utils';
8+
79

810
function readRangeFromBlocks(blocks, rangeOffset, rangeLength) {
911
const rangeTop = rangeOffset + rangeLength;
@@ -121,6 +123,9 @@ class BlockedSource {
121123

122124
// block ids waiting for a batched request. Either a Set or null
123125
this.blockIdsAwaitingRequest = null;
126+
127+
// file size to not read over. To be set when first response was set
128+
this.fileSize = null;
124129
}
125130

126131
/**
@@ -130,7 +135,10 @@ class BlockedSource {
130135
* @returns {ArrayBuffer} The subset of the file.
131136
*/
132137
async fetch(offset, length, immediate = false) {
133-
const top = offset + length;
138+
let top = offset + length;
139+
if (this.fileSize !== null) {
140+
top = Math.min(top, this.fileSize);
141+
}
134142

135143
// calculate what blocks intersect the specified range (offset + length)
136144
// determine what blocks are already stored or beeing requested
@@ -192,6 +200,9 @@ class BlockedSource {
192200
const o = i * this.blockSize;
193201
const t = Math.min(o + this.blockSize, response.data.byteLength);
194202
const data = response.data.slice(o, t);
203+
if (this.fileSize === null && response.fileSize) {
204+
this.fileSize = response.fileSize;
205+
}
195206
this.blockRequests.delete(id);
196207
this.blocks.set(id, {
197208
data,
@@ -257,10 +268,17 @@ export function makeFetchSource(url, { headers = {}, blockSize } = {}) {
257268
} else if (response.status === 206) {
258269
const data = response.arrayBuffer
259270
? await response.arrayBuffer() : (await response.buffer()).buffer;
271+
272+
const contentRange = parseContentRange(response.headers.get('Content-Range'));
273+
let fileSize;
274+
if (contentRange !== null) {
275+
fileSize = contentRange.length;
276+
}
260277
return {
261278
data,
262279
offset,
263280
length,
281+
fileSize,
264282
};
265283
} else {
266284
const data = response.arrayBuffer
@@ -269,6 +287,7 @@ export function makeFetchSource(url, { headers = {}, blockSize } = {}) {
269287
data,
270288
offset: 0,
271289
length: data.byteLength,
290+
fileSize: data.byteLength,
272291
};
273292
}
274293
}, { blockSize });
@@ -297,16 +316,23 @@ export function makeXHRSource(url, { headers = {}, blockSize } = {}) {
297316
request.onload = () => {
298317
const data = request.response;
299318
if (request.status === 206) {
319+
const contentRange = parseContentRange(request.getResponseHeader('Content-Range'));
320+
let fileSize;
321+
if (contentRange !== null) {
322+
fileSize = contentRange.length;
323+
}
300324
resolve({
301325
data,
302326
offset,
303327
length,
328+
fileSize,
304329
});
305330
} else {
306331
resolve({
307332
data,
308333
offset: 0,
309334
length: data.byteLength,
335+
fileSize: data.byteLength,
310336
});
311337
}
312338
};
@@ -328,10 +354,18 @@ export function makeHttpSource(url, { headers = {}, blockSize } = {}) {
328354
return new BlockedSource(async (offset, length) => new Promise((resolve, reject) => {
329355
const parsed = urlMod.parse(url);
330356
const request = (parsed.protocol === 'http:' ? http : https).get(
331-
{ ...parsed,
357+
{
358+
...parsed,
332359
headers: {
333360
...headers, Range: `bytes=${offset}-${offset + length - 1}`,
334-
} }, (result) => {
361+
},
362+
},
363+
(result) => {
364+
const contentRange = parseContentRange(result.headers['content-range']);
365+
let fileSize;
366+
if (contentRange !== null) {
367+
fileSize = contentRange.length;
368+
}
335369
const chunks = [];
336370
// collect chunks
337371
result.on('data', (chunk) => {
@@ -345,6 +379,7 @@ export function makeHttpSource(url, { headers = {}, blockSize } = {}) {
345379
data,
346380
offset,
347381
length: data.byteLength,
382+
fileSize,
348383
});
349384
});
350385
},
@@ -392,11 +427,11 @@ export function makeBufferSource(arrayBuffer) {
392427

393428
function closeAsync(fd) {
394429
return new Promise((resolve, reject) => {
395-
close(fd, err => {
430+
close(fd, (err) => {
396431
if (err) {
397-
reject(err)
432+
reject(err);
398433
} else {
399-
resolve()
434+
resolve();
400435
}
401436
});
402437
});

src/utils.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,44 @@ export function toArrayRecursively(input) {
7676
}
7777
return input;
7878
}
79+
80+
// copied from https://github.com/academia-de-codigo/parse-content-range-header/blob/master/index.js
81+
export function parseContentRange(headerValue) {
82+
if (!headerValue) {
83+
return null;
84+
}
85+
86+
if (typeof headerValue !== 'string') {
87+
throw new Error('invalid argument');
88+
}
89+
90+
const parseInt = (number) => Number.parseInt(number, 10);
91+
92+
// Check for presence of unit
93+
let matches = headerValue.match(/^(\w*) /);
94+
const unit = matches && matches[1];
95+
96+
// check for start-end/size header format
97+
matches = headerValue.match(/(\d+)-(\d+)\/(\d+|\*)/);
98+
if (matches) {
99+
return {
100+
unit,
101+
first: parseInt(matches[1]),
102+
last: parseInt(matches[2]),
103+
length: matches[3] === '*' ? null : parseInt(matches[3]),
104+
};
105+
}
106+
107+
// check for size header format
108+
matches = headerValue.match(/(\d+|\*)/);
109+
if (matches) {
110+
return {
111+
unit,
112+
first: null,
113+
last: null,
114+
length: matches[1] === '*' ? null : parseInt(matches[1]),
115+
};
116+
}
117+
118+
return null;
119+
}

0 commit comments

Comments
 (0)