Skip to content

Commit 015fe85

Browse files
committed
file DELETE support
1 parent c66cc8d commit 015fe85

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed

spec/ParseFile.spec.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,86 @@ describe('Parse.File testing', () => {
3333
});
3434
});
3535

36+
it('supports REST end-to-end file create, read, delete, read', done => {
37+
var headers = {
38+
'Content-Type': 'image/jpeg',
39+
'X-Parse-Application-Id': 'test',
40+
'X-Parse-REST-API-Key': 'rest'
41+
};
42+
request.post({
43+
headers: headers,
44+
url: 'http://localhost:8378/1/files/testfile.txt',
45+
body: 'check one two',
46+
}, (error, response, body) => {
47+
expect(error).toBe(null);
48+
var b = JSON.parse(body);
49+
expect(b.name).toMatch(/_testfile.txt$/);
50+
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*testfile.txt$/);
51+
request.get(b.url, (error, response, body) => {
52+
expect(error).toBe(null);
53+
expect(body).toEqual('check one two');
54+
delete headers['Content-Type'];
55+
headers['X-Parse-Master-Key'] = 'test';
56+
request.del({
57+
headers: headers,
58+
url: 'http://localhost:8378/1/files/' + b.name
59+
}, (error, response, body) => {
60+
expect(error).toBe(null);
61+
expect(response.statusCode).toEqual(200);
62+
delete headers['X-Parse-Master-Key'];
63+
request.get({
64+
headers: headers,
65+
url: b.url
66+
}, (error, response, body) => {
67+
expect(error).toBe(null);
68+
expect(response.statusCode).toEqual(404);
69+
done();
70+
});
71+
});
72+
});
73+
});
74+
});
75+
76+
it('blocks file deletions with missing or incorrect master-key header', done => {
77+
var headers = {
78+
'Content-Type': 'image/jpeg',
79+
'X-Parse-Application-Id': 'test',
80+
'X-Parse-REST-API-Key': 'rest'
81+
};
82+
request.post({
83+
headers: headers,
84+
url: 'http://localhost:8378/1/files/thefile.jpg',
85+
body: 'the file body'
86+
}, (error, response, body) => {
87+
expect(error).toBe(null);
88+
var b = JSON.parse(body);
89+
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*thefile.jpg$/);
90+
delete headers['Content-Type'];
91+
// missing X-Parse-Master-Key header
92+
request.del({
93+
headers: headers,
94+
url: 'http://localhost:8378/1/files/' + b.name
95+
}, (error, response, body) => {
96+
expect(error).toBe(null);
97+
var del_b = JSON.parse(body);
98+
expect(response.statusCode).toEqual(400);
99+
expect(del_b.code).toEqual(119);
100+
// incorrect X-Parse-Master-Key header
101+
headers['X-Parse-Master-Key'] = 'tryagain';
102+
request.del({
103+
headers: headers,
104+
url: 'http://localhost:8378/1/files/' + b.name
105+
}, (error, response, body) => {
106+
expect(error).toBe(null);
107+
var del_b2 = JSON.parse(body);
108+
expect(response.statusCode).toEqual(400);
109+
expect(del_b2.code).toEqual(119);
110+
done();
111+
});
112+
});
113+
});
114+
});
115+
36116
it('handles other filetypes', done => {
37117
var headers = {
38118
'Content-Type': 'image/jpeg',

src/Adapters/Files/FilesAdapter.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
export class FilesAdapter {
1515
createFile(config, filename, data) { }
1616

17+
deleteFile(config, filename) { }
18+
1719
getFileData(config, filename) { }
1820

1921
getFileLocation(config, filename) { }

src/Adapters/Files/GridStoreAdapter.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ export class GridStoreAdapter extends FilesAdapter {
2020
});
2121
}
2222

23+
deleteFile(config, filename) {
24+
return config.database.connect().then(() => {
25+
let gridStore = new GridStore(config.database.db, filename, 'w');
26+
return gridStore.open();
27+
}).then((gridStore) => {
28+
return gridStore.unlink();
29+
}).then((gridStore) => {
30+
return gridStore.close();
31+
});
32+
}
33+
2334
getFileData(config, filename) {
2435
return config.database.connect().then(() => {
2536
return GridStore.exist(config.database.db, filename);

src/Adapters/Files/S3Adapter.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ export class S3Adapter extends FilesAdapter {
5656
});
5757
}
5858

59+
deleteFile(config, filename) {
60+
return new Promise((resolve, reject) => {
61+
let params = {
62+
Key: this._bucketPrefix + filename
63+
};
64+
this._s3Client.deleteObject(params, (err, data) =>{
65+
if(err !== null) {
66+
return reject(err);
67+
}
68+
resolve(data);
69+
});
70+
});
71+
}
72+
5973
// Search for and return a file if found by filename
6074
// Returns a promise that succeeds with the buffer result from S3
6175
getFileData(config, filename) {

src/Controllers/FilesController.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ export class FilesController {
7474
};
7575
}
7676

77+
deleteHandler() {
78+
return (req, res, next) => {
79+
// enforce use of master key for file deletions
80+
if(!req.auth.isMaster){
81+
next(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
82+
'Master key required for file deletion.'));
83+
return;
84+
}
85+
86+
this._filesAdapter.deleteFile(req.config, req.params.filename).then(() => {
87+
res.status(200);
88+
// TODO: return useful JSON here?
89+
res.end();
90+
}).catch((error) => {
91+
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR,
92+
'Could not delete file.'));
93+
});
94+
};
95+
}
96+
7797
/**
7898
* Find file references in REST-format object and adds the url key
7999
* with the current mount point and app id.
@@ -119,6 +139,12 @@ export class FilesController {
119139
this.createHandler()
120140
);
121141

142+
router.delete('/files/:filename',
143+
Middlewares.allowCrossDomain,
144+
Middlewares.handleParseHeaders,
145+
this.deleteHandler()
146+
);
147+
122148
return router;
123149
}
124150
}

0 commit comments

Comments
 (0)