Skip to content

Commit 4a9fd34

Browse files
authored
chore: restrict writing to certain object keys (#176)
- Block 'prototype', '__proto__'& 'constructor' - Updated 'addBuildPhase' to ensure buildPhaseType does not accept restricted key. - Updated 'addTargetAttribute' to ensure target.uuid does not accept restricted key. - Updated 'pbxCreateGroupWithType' to ensure groupType does not accept restricted key. - Updated tests for 'addBuildPhase' & 'addTargetAttribute' - Added test case for 'pbxCreateGroupWithType' - Small test update to correct spelling of build phase type 'PBXFrameworksBuildPhase'
1 parent 780467c commit 4a9fd34

9 files changed

Lines changed: 117 additions & 5 deletions

lib/pbxProject.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ const COMMENT_KEY = /_comment$/;
8080

8181
// MARK: End of Typings
8282

83+
// MARK: Start of Internals
84+
85+
/**
86+
* A list of restricted object keys.
87+
*/
88+
const RESTRICTED_OBJECT_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
89+
90+
// MARK: End of Internals
91+
8392
function PBXProject (filename) {
8493
if (!(this instanceof PBXProject)) { return new PBXProject(filename); }
8594

@@ -921,6 +930,13 @@ PBXProject.prototype.addTargetDependency = function (target, dependencyTargets)
921930
* @returns {AddBuildPhaseResults} object containing the build phase & uuid
922931
*/
923932
PBXProject.prototype.addBuildPhase = function (filePathsArray, buildPhaseType, comment, target, optionsOrFolderType, subfolderPath) {
933+
// Xcode may introduce new build phase types so the buildPhaseType
934+
// param will not be validated against a fixed set of values.
935+
// Instead, it will reject object keys that shouldn't be writable.
936+
if (RESTRICTED_OBJECT_KEYS.has(buildPhaseType)) {
937+
throw new Error(`"${buildPhaseType}" is restricted and cannot be used as a build phase type.`);
938+
}
939+
924940
const fileReferenceSection = this.pbxFileReferenceSection();
925941
const buildFileSection = this.pbxBuildFileSection();
926942
const buildPhaseUuid = this.generateUuid();
@@ -1918,6 +1934,11 @@ PBXProject.prototype.pbxCreateGroupWithType = function (name, pathName, groupTyp
19181934
// Create comment
19191935
const commendId = key + '_comment';
19201936

1937+
// Reject object keys that shouldn't be writable.
1938+
if (RESTRICTED_OBJECT_KEYS.has(groupType)) {
1939+
throw new Error(`"${groupType}" is restricted and cannot be used as a group type.`);
1940+
}
1941+
19211942
// add obj and commentObj to groups;
19221943
let groups = this.hash.project.objects[groupType];
19231944
if (!groups) {
@@ -2212,6 +2233,12 @@ PBXProject.prototype.addTargetAttribute = function (prop, value, target) {
22122233
attributes.TargetAttributes = {};
22132234
}
22142235
target = target || this.getFirstTarget();
2236+
2237+
// Reject object keys that shouldn't be writable.
2238+
if (RESTRICTED_OBJECT_KEYS.has(target.uuid)) {
2239+
throw new Error(`"${target.uuid}" is restricted and cannot be used as a uuid.`);
2240+
}
2241+
22152242
if (attributes.TargetAttributes[target.uuid] === undefined) {
22162243
attributes.TargetAttributes[target.uuid] = {};
22172244
}

test/addBuildPhase.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,22 @@ describe('addBuildPhase', () => {
219219
assert.equal(buildPhase.shellScript, '"test"');
220220
assert.strictEqual(buildPhase.alwaysOutOfDate, undefined);
221221
});
222+
223+
it('should not throw error when a non-restricted value is used for build phase type.', () => {
224+
assert.doesNotThrow(
225+
() => {
226+
proj.addBuildPhase(['MyAssets.xcassets'], 'FooBar', 'Resources');
227+
},
228+
/Error: "FooBar" is restricted and cannot be used as a build phase type\./
229+
);
230+
});
231+
232+
it('should throw error when a restricted value was passed in for a build phase type.', () => {
233+
assert.throws(
234+
() => {
235+
proj.addBuildPhase(['MyAssets.xcassets'], 'prototype', 'Resources');
236+
},
237+
/Error: "prototype" is restricted and cannot be used as a build phase type\./
238+
);
239+
});
222240
});

test/addTarget.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ describe('addTarget', () => {
169169
const resourceFile = proj.addResourceFile('assets.bundle', options);
170170
const resourcePhase = proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
171171
const frameworkFile = proj.addFramework('libsqlite3.dylib', options);
172-
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworkBuildPhase', 'Frameworks', target.uuid);
172+
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
173173
const headerFile = proj.addHeaderFile('file.h', options);
174174

175175
assert.ok(sourcePhase);

test/addWatch2App.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('addWatchApp', () => {
8484
const resourceFile = proj.addResourceFile('assets.bundle', options);
8585
const resourcePhase = proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
8686
const frameworkFile = proj.addFramework('libsqlite3.dylib', options);
87-
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworkBuildPhase', 'Frameworks', target.uuid);
87+
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
8888
const headerFile = proj.addHeaderFile('file.h', options);
8989

9090
assert.ok(sourcePhase);

test/addWatch2Extension.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ describe('addWatchExtension', () => {
6464
const resourceFile = proj.addResourceFile('assets.bundle', options);
6565
const resourcePhase = proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
6666
const frameworkFile = proj.addFramework('libsqlite3.dylib', options);
67-
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworkBuildPhase', 'Frameworks', target.uuid);
67+
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
6868
const headerFile = proj.addHeaderFile('file.h', options);
6969

7070
assert.ok(sourcePhase);

test/addWatchApp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('addWatchApp', () => {
8484
const resourceFile = proj.addResourceFile('assets.bundle', options);
8585
const resourcePhase = proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
8686
const frameworkFile = proj.addFramework('libsqlite3.dylib', options);
87-
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworkBuildPhase', 'Frameworks', target.uuid);
87+
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
8888
const headerFile = proj.addHeaderFile('file.h', options);
8989

9090
assert.ok(sourcePhase);

test/addWatchExtension.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('addWatchExtension', () => {
6363
const resourceFile = proj.addResourceFile('assets.bundle', options);
6464
const resourcePhase = proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
6565
const frameworkFile = proj.addFramework('libsqlite3.dylib', options);
66-
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworkBuildPhase', 'Frameworks', target.uuid);
66+
const frameworkPhase = proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
6767
const headerFile = proj.addHeaderFile('file.h', options);
6868

6969
assert.ok(sourcePhase);

test/group.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,5 +434,23 @@ describe('group', () => {
434434
output = project.writeSync();
435435
assert.equal(output.match(/ProvisioningStyle\s*=\s*Manual/g), null);
436436
});
437+
438+
it('should not throw error when a non-restricted value is used for a target.uuid.', () => {
439+
assert.doesNotThrow(
440+
() => {
441+
project.addTargetAttribute('ProvisioningStyle', 'Manual', { uuid: 'FooBar' });
442+
},
443+
/Error: "FooBar" is restricted and cannot be used as a uuid\./
444+
);
445+
});
446+
447+
it('should throw error when a restricted value was passed in for a target.uuid.', () => {
448+
assert.throws(
449+
() => {
450+
project.addTargetAttribute('ProvisioningStyle', 'Manual', { uuid: 'prototype' });
451+
},
452+
/Error: "prototype" is restricted and cannot be used as a uuid\./
453+
);
454+
});
437455
});
438456
});

test/pbxCreateGroupWithType.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
*/
19+
20+
const { describe, it, beforeEach } = require('node:test');
21+
const assert = require('node:assert');
22+
23+
const PBXProject = require('../lib/pbxProject');
24+
let project;
25+
26+
describe('pbxCreateGroupWithType', () => {
27+
beforeEach(() => {
28+
project = new PBXProject('test/parser/projects/group.pbxproj');
29+
project.parseSync();
30+
});
31+
32+
it('should not throw error when a non-restricted value is used as a group type.', () => {
33+
assert.doesNotThrow(
34+
() => {
35+
project.pbxCreateGroupWithType('name', '.', 'FooBar');
36+
},
37+
/Error: "FooBar" is restricted and cannot be used as a group type\./
38+
);
39+
});
40+
41+
it('should throw error when a restricted value was passed in as a group type.', () => {
42+
assert.throws(
43+
() => {
44+
project.pbxCreateGroupWithType('name', '.', 'prototype');
45+
},
46+
/Error: "prototype" is restricted and cannot be used as a group type\./
47+
);
48+
});
49+
});

0 commit comments

Comments
 (0)