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
24 changes: 24 additions & 0 deletions spec/AudienceRouter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@ describe('AudiencesRouter', () => {
});
});

it('uses find condition from a where string in request.body', async () => {
const config = Config.get('test');
await rest.create(config, auth.master(config), '_Audience', {
name: 'Android Users',
query: '{ "test": "android" }',
});
await rest.create(config, auth.master(config), '_Audience', {
name: 'Iphone Users',
query: '{ "test": "ios" }',
});

const router = new AudiencesRouter();
const res = await router.handleFind({
config: config,
auth: auth.master(config),
body: { where: JSON.stringify({ query: '{ "test": "android" }' }) },
query: {},
info: {},
});

expect(res.response.results.length).toEqual(1);
expect(res.response.results[0].name).toEqual('Android Users');
});

it('query installations with limit = 0', done => {
const config = Config.get('test');
const androidAudienceRequest = {
Expand Down
76 changes: 76 additions & 0 deletions spec/InstallationsRouter.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const auth = require('../lib/Auth');
const Config = require('../lib/Config');
const rest = require('../lib/rest');
const httpRequest = require('../lib/request');
const InstallationsRouter = require('../lib/Routers/InstallationsRouter').InstallationsRouter;

describe('InstallationsRouter', () => {
Expand Down Expand Up @@ -244,4 +245,79 @@ describe('InstallationsRouter', () => {
done();
});
});

it('uses find condition from a where string in request.body', async () => {
const config = Config.get('test');
await rest.create(config, auth.nobody(config), '_Installation', {
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
});
await rest.create(config, auth.nobody(config), '_Installation', {
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
});

const router = new InstallationsRouter();
const res = await router.handleFind({
config: config,
auth: auth.master(config),
body: { where: JSON.stringify({ deviceType: 'android' }) },
query: {},
info: {},
});

expect(res.response.results.length).toEqual(1);
expect(res.response.results[0].deviceType).toEqual('android');
});

it('rejects an invalid where string in request.body', async () => {
const config = Config.get('test');
const router = new InstallationsRouter();
let error;
try {
await router.handleFind({
config: config,
auth: auth.master(config),
body: { where: 'not json' },
query: {},
info: {},
});
fail('find should have been rejected');
return;
} catch (e) {
error = e;
}
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
expect(error.message).toEqual('where parameter is not valid JSON');
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it('finds installations when the client sends the find as POST with _method=GET', async () => {
const config = Config.get('test');
await rest.create(config, auth.nobody(config), '_Installation', {
installationId: '12345678-abcd-abcd-abcd-123456789abc',
deviceType: 'android',
});
await rest.create(config, auth.nobody(config), '_Installation', {
installationId: '12345678-abcd-abcd-abcd-123456789abd',
deviceType: 'ios',
});

// A client that exceeds the maximum URL length sends the find as a POST
// with a urlencoded body, so `where` arrives as a string.
const response = await httpRequest({
method: 'POST',
url: 'http://localhost:8378/1/installations',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `_method=GET&where=${encodeURIComponent(
JSON.stringify({ installationId: { $in: ['12345678-abcd-abcd-abcd-123456789abc'] } })
)}`,
});

expect(response.data.results.length).toEqual(1);
expect(response.data.results[0].deviceType).toEqual('android');
});
});
1 change: 1 addition & 0 deletions src/Routers/AudiencesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class AudiencesRouter extends ClassesRouter {
handleFind(req) {
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
ClassesRouter.decodeWhere(body);

return rest
.find(
Expand Down
30 changes: 23 additions & 7 deletions src/Routers/ClassesRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,7 @@ export class ClassesRouter extends PromiseRouter {
if (body.redirectClassNameForKey) {
options.redirectClassNameForKey = String(body.redirectClassNameForKey);
}
if (typeof body.where === 'string') {
try {
body.where = JSON.parse(body.where);
} catch {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'where parameter is not valid JSON');
}
}
ClassesRouter.decodeWhere(body);
return rest
.find(
req.config,
Expand Down Expand Up @@ -157,6 +151,28 @@ export class ClassesRouter extends PromiseRouter {
return json;
}

/**
* Decodes a `where` that arrives as a JSON string instead of an object.
*
* A client that exceeds the maximum URL length sends a find as
* `POST` + `_method=GET` with a urlencoded body, so `where` reaches the
* router as a string rather than being decoded by `JSONFromQuery`. Every
* router that serves a find has to decode it, otherwise the string is passed
* to the query layer and iterated character by character.
*
* Mutates `body` in place and returns the decoded `where`.
*/
static decodeWhere(body) {
if (typeof body.where === 'string') {
try {
body.where = JSON.parse(body.where);
} catch {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'where parameter is not valid JSON');
}
}
return body.where;
}

static optionsFromBody(body, defaultLimit) {
const allowConstraints = [
'skip',
Expand Down
1 change: 1 addition & 0 deletions src/Routers/InstallationsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class InstallationsRouter extends ClassesRouter {
handleFind(req) {
const body = Object.assign(req.body || {}, ClassesRouter.JSONFromQuery(req.query));
const options = ClassesRouter.optionsFromBody(body, req.config.defaultLimit);
ClassesRouter.decodeWhere(body);
return rest
.find(
req.config,
Expand Down