Skip to content

Commit dc57adb

Browse files
sdhullMarius Kleidl
andauthored
Error out if source provides less data than expected (#606)
* Adds end-to-end test that reproduces the issue * Error out if the source provides less data than specified * Remove impossible test * Clean up tests --------- Co-authored-by: Marius Kleidl <[email protected]>
1 parent 1a4b936 commit dc57adb

File tree

7 files changed

+347
-175
lines changed

7 files changed

+347
-175
lines changed

lib/browser/sources/FileSource.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export default class FileSource {
1717
}
1818

1919
const value = this._file.slice(start, end)
20-
return Promise.resolve({ value })
20+
const done = end >= this.size
21+
return Promise.resolve({ value, done })
2122
}
2223

2324
close() {

lib/node/sources/BufferSource.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ export default class BufferSource {
77
slice(start, end) {
88
const value = this._buffer.slice(start, end)
99
value.size = value.length
10-
return Promise.resolve({ value })
10+
const done = end >= this.size
11+
return Promise.resolve({ value, done })
1112
}
1213

1314
close() {}

lib/node/sources/FileSource.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ class FileSource {
4646
end: offset + end - 1,
4747
autoClose: true,
4848
})
49-
stream.size = end - start
50-
return Promise.resolve({ value: stream })
49+
stream.size = Math.min(end - start, this.size)
50+
const done = stream.size >= this.size
51+
return Promise.resolve({ value: stream, done })
5152
}
5253

5354
close() {

lib/upload.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,14 +782,30 @@ class BaseUpload {
782782
}
783783

784784
return this._source.slice(start, end).then(({ value, done }) => {
785+
const valueSize = value && value.size ? value.size : 0
786+
785787
// If the upload length is deferred, the upload size was not specified during
786788
// upload creation. So, if the file reader is done reading, we know the total
787789
// upload size and can tell the tus server.
788790
if (this.options.uploadLengthDeferred && done) {
789-
this._size = this._offset + (value && value.size ? value.size : 0)
791+
this._size = this._offset + valueSize
790792
req.setHeader('Upload-Length', this._size)
791793
}
792794

795+
// The specified uploadSize might not match the actual amount of data that a source
796+
// provides. In these cases, we cannot successfully complete the upload, so we
797+
// rather error out and let the user know. If not, tus-js-client will be stuck
798+
// in a loop of repeating empty PATCH requests.
799+
// See https://community.transloadit.com/t/how-to-abort-hanging-companion-uploads/16488/13
800+
const newSize = this._offset + valueSize
801+
if (!this.options.uploadLengthDeferred && done && newSize !== this._size) {
802+
return Promise.reject(
803+
new Error(
804+
`upload was configured with a size of ${this._size} bytes, but the source is done after ${newSize} bytes`
805+
)
806+
)
807+
}
808+
793809
if (value === null) {
794810
return this._sendRequest(req)
795811
}

test/spec/test-browser-specific.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,51 @@ describe('tus', () => {
658658

659659
await options.onSuccess.toBeCalled
660660
})
661+
662+
it('should throw an error if the source provides less data than uploadSize', async () => {
663+
const reader = makeReader('hello world')
664+
665+
const testStack = new TestHttpStack()
666+
const options = {
667+
httpStack: testStack,
668+
uploadSize: 100,
669+
chunkSize: 100,
670+
endpoint: 'http://tus.io/uploads',
671+
retryDelays: [],
672+
onError: waitableFunction('onError'),
673+
}
674+
675+
const upload = new tus.Upload(reader, options)
676+
upload.start()
677+
let req = await testStack.nextRequest()
678+
expect(req.url).toBe('http://tus.io/uploads')
679+
expect(req.method).toBe('POST')
680+
expect(req.requestHeaders['Tus-Resumable']).toBe('1.0.0')
681+
682+
req.respondWith({
683+
status: 204,
684+
responseHeaders: {
685+
Location: 'http://tus.io/uploads/foo',
686+
},
687+
})
688+
689+
req = await testStack.nextRequest()
690+
expect(req.url).toBe('http://tus.io/uploads/foo')
691+
expect(req.method).toBe('PATCH')
692+
693+
req.respondWith({
694+
status: 204,
695+
responseHeaders: {
696+
'Upload-Offset': 11,
697+
},
698+
})
699+
700+
const err = await options.onError.toBeCalled
701+
702+
expect(err.message).toBe(
703+
'tus: failed to upload chunk at offset 11, caused by Error: upload was configured with a size of 100 bytes, but the source is done after 11 bytes, originated from request (method: PATCH, url: http://tus.io/uploads/foo, response code: n/a, response text: n/a, request id: n/a)'
704+
)
705+
})
661706
})
662707

663708
describe('resolving of URIs', () => {

test/spec/test-common.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,38 @@ describe('tus', () => {
763763
)
764764
})
765765

766+
it('should throw an error if the source provides less data than uploadSize', async () => {
767+
const testStack = new TestHttpStack()
768+
const file = getBlob('hello world')
769+
const options = {
770+
httpStack: testStack,
771+
uploadSize: 100,
772+
endpoint: 'http://tus.io/uploads',
773+
retryDelays: [],
774+
onError: waitableFunction('onError'),
775+
}
776+
777+
const upload = new tus.Upload(file, options)
778+
upload.start()
779+
780+
const req = await testStack.nextRequest()
781+
expect(req.url).toBe('http://tus.io/uploads')
782+
expect(req.method).toBe('POST')
783+
expect(req.requestHeaders['Tus-Resumable']).toBe('1.0.0')
784+
785+
req.respondWith({
786+
status: 204,
787+
responseHeaders: {
788+
Location: 'http://tus.io/uploads/foo',
789+
},
790+
})
791+
792+
const err = await options.onError.toBeCalled
793+
expect(err.message).toBe(
794+
'tus: failed to upload chunk at offset 0, caused by Error: upload was configured with a size of 100 bytes, but the source is done after 11 bytes, originated from request (method: PATCH, url: http://tus.io/uploads/foo, response code: n/a, response text: n/a, request id: n/a)'
795+
)
796+
})
797+
766798
it('should throw if retryDelays is not an array', () => {
767799
const file = getBlob('hello world')
768800
const upload = new tus.Upload(file, {

0 commit comments

Comments
 (0)