From 5f1653d19a86f6e9bbdce5795b651786355bd209 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 23 Sep 2022 21:00:08 +1000 Subject: [PATCH 1/3] fix beta --- spec/ParseFile.spec.js | 207 +++++++++++++++++++++- src/Adapters/Files/GridFSBucketAdapter.js | 33 ++-- src/Routers/FilesRouter.js | 7 +- 3 files changed, 227 insertions(+), 20 deletions(-) diff --git a/spec/ParseFile.spec.js b/spec/ParseFile.spec.js index 281ce45cc2..a72064f2a4 100644 --- a/spec/ParseFile.spec.js +++ b/spec/ParseFile.spec.js @@ -692,7 +692,198 @@ describe('Parse.File testing', () => { }); }); - xdescribe('Gridstore Range tests', () => { + describe('Gridstore Range tests', () => { + it('supports bytes range out of range', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: repeat('argle bargle', 100), + }); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + Range: 'bytes=15000-18000', + }, + }); + expect(file.headers['content-range']).toBe('bytes 1212-1212/1212'); + }); + + it('supports bytes range if end greater than start', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: repeat('argle bargle', 100), + }); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + Range: 'bytes=15000-100', + }, + }); + expect(file.headers['content-range']).toBe('bytes 100-1212/1212'); + }); + + it('supports bytes range if end is undefined', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: repeat('argle bargle', 100), + }); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + Range: 'bytes=100-', + }, + }); + expect(file.headers['content-range']).toBe('bytes 100-1212/1212'); + }); + + it('supports bytes range if start and end undefined', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: repeat('argle bargle', 100), + }); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + Range: 'bytes=abc-efs', + }, + }).catch(e => e); + expect(file.headers['content-range']).toBeUndefined(); + }); + + it('supports bytes range if start and end undefined', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: repeat('argle bargle', 100), + }); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + }, + }).catch(e => e); + expect(file.headers['content-range']).toBeUndefined(); + }); + + it('supports bytes range if end is greater than size', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: repeat('argle bargle', 100), + }); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + Range: 'bytes=0-2000', + }, + }).catch(e => e); + expect(file.headers['content-range']).toBe('bytes 0-1212/1212'); + }); + + it('supports bytes range if end is greater than size', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: repeat('argle bargle', 100), + }); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + Range: 'bytes=0-2000', + }, + }).catch(e => e); + expect(file.headers['content-range']).toBe('bytes 0-1212/1212'); + }); + + it('supports bytes range with 0 length', async () => { + const headers = { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }; + const response = await request({ + method: 'POST', + headers: headers, + url: 'http://localhost:8378/1//files/file.txt ', + body: 'a', + }).catch(e => e); + const b = response.data; + const file = await request({ + url: b.url, + headers: { + 'Content-Type': 'application/octet-stream', + 'X-Parse-Application-Id': 'test', + Range: 'bytes=-2000', + }, + }).catch(e => e); + expect(file.headers['content-range']).toBe('bytes 0-1/1'); + }); + it('supports range requests', done => { const headers = { 'Content-Type': 'application/octet-stream', @@ -781,7 +972,7 @@ describe('Parse.File testing', () => { }); }); - xit('supports getting last n bytes', done => { + it('supports getting last n bytes', done => { const headers = { 'Content-Type': 'application/octet-stream', 'X-Parse-Application-Id': 'test', @@ -879,8 +1070,8 @@ describe('Parse.File testing', () => { }); }); - it('fails to stream unknown file', done => { - request({ + it('fails to stream unknown file', async () => { + const response = await request({ url: 'http://localhost:8378/1/files/test/file.txt', headers: { 'Content-Type': 'application/octet-stream', @@ -888,12 +1079,10 @@ describe('Parse.File testing', () => { 'X-Parse-REST-API-Key': 'rest', Range: 'bytes=13-240', }, - }).then(response => { - expect(response.status).toBe(404); - const body = response.text; - expect(body).toEqual('File not found.'); - done(); }); + expect(response.status).toBe(404); + const body = response.text; + expect(body).toEqual('File not found.'); }); }); diff --git a/src/Adapters/Files/GridFSBucketAdapter.js b/src/Adapters/Files/GridFSBucketAdapter.js index 06896e73b5..46938be7cf 100644 --- a/src/Adapters/Files/GridFSBucketAdapter.js +++ b/src/Adapters/Files/GridFSBucketAdapter.js @@ -228,25 +228,38 @@ export class GridFSBucketAdapter extends FilesAdapter { const partialstart = parts[0]; const partialend = parts[1]; - const start = parseInt(partialstart, 10); - const end = partialend ? parseInt(partialend, 10) : files[0].length - 1; + const fileLength = files[0].length; + const fileStart = parseInt(partialstart, 10); + const fileEnd = partialend ? parseInt(partialend, 10) : fileLength; - res.writeHead(206, { - 'Accept-Ranges': 'bytes', - 'Content-Length': end - start + 1, - 'Content-Range': 'bytes ' + start + '-' + end + '/' + files[0].length, - 'Content-Type': contentType, - }); + let start = Math.min(fileStart || 0, fileEnd, fileLength); + let end = Math.max(fileStart || 0, fileEnd) + 1 || fileLength; + if (isNaN(fileStart)) { + start = fileLength - end + 1; + end = fileLength; + } + end = Math.min(end, fileLength); + start = Math.max(start, 0); + + res.status(206); + res.header('Accept-Ranges', 'bytes'); + res.header('Content-Length', end - start); + res.header('Content-Range', 'bytes ' + start + '-' + end + '/' + fileLength); + res.header('Content-Type', contentType); const stream = bucket.openDownloadStreamByName(filename); stream.start(start); + if (end) { + stream.end(end); + } stream.on('data', chunk => { res.write(chunk); }); stream.on('error', () => { res.sendStatus(404); }); - stream.on('end', () => { - res.end(); + stream.on('error', (e) => { + res.status(404); + res.send(e.message); }); } diff --git a/src/Routers/FilesRouter.js b/src/Routers/FilesRouter.js index 2ea33987ba..f8f25475a7 100644 --- a/src/Routers/FilesRouter.js +++ b/src/Routers/FilesRouter.js @@ -266,5 +266,10 @@ export class FilesRouter { } function isFileStreamable(req, filesController) { - return req.get('Range') && typeof filesController.adapter.handleFileStream === 'function'; + const range = (req.get('Range') || '/-/').split('-'); + const start = Number(range[0]); + const end = Number(range[1]); + return ( + (!isNaN(start) || !isNaN(end)) && typeof filesController.adapter.handleFileStream === 'function' + ); } From 9cb02233adf66c9bbd306e81e0542eaf57a4c026 Mon Sep 17 00:00:00 2001 From: dblythy Date: Fri, 23 Sep 2022 21:40:40 +1000 Subject: [PATCH 2/3] fix tests --- spec/ParseFile.spec.js | 6 +++--- src/Adapters/Files/GridFSBucketAdapter.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/ParseFile.spec.js b/spec/ParseFile.spec.js index a72064f2a4..7a118f2608 100644 --- a/spec/ParseFile.spec.js +++ b/spec/ParseFile.spec.js @@ -704,7 +704,7 @@ describe('Parse.File testing', () => { headers: headers, url: 'http://localhost:8378/1//files/file.txt ', body: repeat('argle bargle', 100), - }); + }) const b = response.data; const file = await request({ url: b.url, @@ -713,7 +713,7 @@ describe('Parse.File testing', () => { 'X-Parse-Application-Id': 'test', Range: 'bytes=15000-18000', }, - }); + }).catch (e => e); expect(file.headers['content-range']).toBe('bytes 1212-1212/1212'); }); @@ -1079,7 +1079,7 @@ describe('Parse.File testing', () => { 'X-Parse-REST-API-Key': 'rest', Range: 'bytes=13-240', }, - }); + }).catch(e => e); expect(response.status).toBe(404); const body = response.text; expect(body).toEqual('File not found.'); diff --git a/src/Adapters/Files/GridFSBucketAdapter.js b/src/Adapters/Files/GridFSBucketAdapter.js index 46938be7cf..8ff4d2b8eb 100644 --- a/src/Adapters/Files/GridFSBucketAdapter.js +++ b/src/Adapters/Files/GridFSBucketAdapter.js @@ -254,13 +254,13 @@ export class GridFSBucketAdapter extends FilesAdapter { stream.on('data', chunk => { res.write(chunk); }); - stream.on('error', () => { - res.sendStatus(404); - }); stream.on('error', (e) => { res.status(404); res.send(e.message); }); + stream.on('end', () => { + res.end(); + }); } handleShutdown() { From 0b96e9d0cdd2a9286d0adcec2441e501d2d9a910 Mon Sep 17 00:00:00 2001 From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:09:54 +0200 Subject: [PATCH 3/3] Update ParseFile.spec.js --- spec/ParseFile.spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/ParseFile.spec.js b/spec/ParseFile.spec.js index 7a118f2608..88d6953eed 100644 --- a/spec/ParseFile.spec.js +++ b/spec/ParseFile.spec.js @@ -692,7 +692,7 @@ describe('Parse.File testing', () => { }); }); - describe('Gridstore Range tests', () => { + describe_only_db('mongo')('Gridstore Range', () => { it('supports bytes range out of range', async () => { const headers = { 'Content-Type': 'application/octet-stream', @@ -704,7 +704,7 @@ describe('Parse.File testing', () => { headers: headers, url: 'http://localhost:8378/1//files/file.txt ', body: repeat('argle bargle', 100), - }) + }); const b = response.data; const file = await request({ url: b.url, @@ -713,7 +713,7 @@ describe('Parse.File testing', () => { 'X-Parse-Application-Id': 'test', Range: 'bytes=15000-18000', }, - }).catch (e => e); + }).catch(e => e); expect(file.headers['content-range']).toBe('bytes 1212-1212/1212'); });