Skip to content

Commit

Permalink
Implements input validation for the AplicationId
Browse files Browse the repository at this point in the history
- Implements validation and tests
- Improves some error messages on the CLI
  • Loading branch information
andreban committed Apr 14, 2020
1 parent 61dcf3d commit a40acc2
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 8 deletions.
52 changes: 44 additions & 8 deletions packages/cli/src/lib/cmds/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as fs from 'fs';
import Color = require('color');
import * as inquirer from 'inquirer';
import {Config, JdkHelper, KeyTool, Log, TwaGenerator, TwaManifest} from '@bubblewrap/core';
import {Config, JdkHelper, KeyTool, Log, TwaGenerator, TwaManifest, util} from '@bubblewrap/core';
import {validateColor, validatePassword, validateUrl, notEmpty} from '../inputHelpers';
import {ParsedArgs} from 'minimist';
import {APP_NAME} from '../constants';
Expand All @@ -36,19 +36,34 @@ async function confirmTwaConfig(twaManifest: TwaManifest): Promise<TwaManifest>
type: 'input',
message: 'Domain being opened in the TWA:',
default: twaManifest.host,
validate: notEmpty,
validate: async (input): Promise<boolean> => {
if (notEmpty(input)) {
return true;
}
throw new Error('host cannot be empty');
},
}, {
name: 'name',
type: 'input',
message: 'Name of the application:',
default: twaManifest.name,
validate: notEmpty,
validate: async (input): Promise<boolean> => {
if (notEmpty(input)) {
return true;
}
throw new Error('name cannot be empty');
},
}, {
name: 'launcherName',
type: 'input',
message: 'Name to be shown on the Android Launcher:',
default: twaManifest.launcherName,
validate: notEmpty,
validate: async (input): Promise<boolean> => {
if (notEmpty(input)) {
return true;
}
throw new Error('Launcher name cannot be empty');
},
}, {
name: 'themeColor',
type: 'input',
Expand All @@ -66,7 +81,12 @@ async function confirmTwaConfig(twaManifest: TwaManifest): Promise<TwaManifest>
type: 'input',
message: 'Relative path to open the TWA:',
default: twaManifest.startUrl,
validate: notEmpty,
validate: async (input): Promise<boolean> => {
if (notEmpty(input)) {
return true;
}
throw new Error('URL cannot be empty');
},
}, {
name: 'iconUrl',
type: 'input',
Expand All @@ -91,19 +111,35 @@ async function confirmTwaConfig(twaManifest: TwaManifest): Promise<TwaManifest>
type: 'input',
message: 'Android Package Name (or Application ID):',
default: twaManifest.packageId,
validate: notEmpty,
validate: async (input): Promise<boolean> => {
if (!util.validatePackageId(input)) {
throw new Error('Invalid Application Id. Check requiements at ' +
'https://developer.android.com/studio/build/application-id');
}
return true;
},
}, {
name: 'keyPath',
type: 'input',
message: 'Location of the Signing Key:',
default: twaManifest.signingKey.path,
validate: notEmpty,
validate: async (input): Promise<boolean> => {
if (notEmpty(input)) {
return true;
}
throw new Error('KeyStore location cannot be empty');
},
}, {
name: 'keyAlias',
type: 'input',
message: 'Key name:',
default: twaManifest.signingKey.alias,
validate: notEmpty,
validate: async (input): Promise<boolean> => {
if (notEmpty(input)) {
return true;
}
throw new Error('Key alias cannot be empty');
},
},
]);

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {JdkHelper} from './lib/jdk/JdkHelper';
import {KeyTool} from './lib/jdk/KeyTool';
import {TwaManifest} from './lib/TwaManifest';
import {TwaGenerator} from './lib/TwaGenerator';
import * as util from './lib/util';

export {AndroidSdkTools,
Config,
Expand All @@ -31,4 +32,5 @@ export {AndroidSdkTools,
Log,
TwaGenerator,
TwaManifest,
util,
};
31 changes: 31 additions & 0 deletions packages/core/src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const extractZipPromise = promisify(extractZip);
// Regex for disallowed characters on Android Packages, as per
// https://developer.android.com/guide/topics/manifest/manifest-element.html#package
const DISALLOWED_ANDROID_PACKAGE_CHARS_REGEX = /[^a-zA-Z0-9_\.]/g;
const VALID_PACKAGE_ID_SEGMENT_REGEX = /^[a-zA-Z][A-Za-z0-9_]*$/;

export async function execute(cmd: string[], env: NodeJS.ProcessEnv): Promise<void> {
await execPromise(cmd.join(' '), {env: env});
Expand Down Expand Up @@ -134,3 +135,33 @@ export function generatePackageId(host: string): string {
parts.push('twa');
return parts.join('.').replace(DISALLOWED_ANDROID_PACKAGE_CHARS_REGEX, '_');
}

/**
* Validates a Package Id, according to the documentation at:
* https://developer.android.com/studio/build/application-id
*
* Rules summary for the Package Id:
* - It must have at least two segments (one or more dots).
* - Each segment must start with a leter.
* - All characters must be alphanumeric or an underscore [a-zA-Z0-9_].
*
* @param {string} input the package name to be validated
*/
export function validatePackageId(input: string): boolean {
if (input.length <= 0) {
return false;
}

const parts = input.split('.');
if (parts.length < 2) {
return false;
}

for (const part of parts) {
if (part.match(VALID_PACKAGE_ID_SEGMENT_REGEX) === null) {
return false;
}
}

return true;
}
30 changes: 30 additions & 0 deletions packages/core/src/spec/lib/utilSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,34 @@ describe('util', () => {
expect(result).toBe('com.appspot.pwa_directory_test.twa');
});
});

describe('#validatePackageId', () => {
it('returns true for valid packages', () => {
expect(util.validatePackageId('com.pwa_directory.appspot.com')).toBeTrue();
expect(util.validatePackageId('com.pwa1directory.appspot.com')).toBeTrue();
});

it('returns false for packages with invalid characters', () => {
expect(util.validatePackageId('com.pwa-directory.appspot.com')).toBeFalse();
expect(util.validatePackageId('[email protected]')).toBeFalse();
expect(util.validatePackageId('com.pwa*directory.appspot.com')).toBeFalse();
});

it('returns false for packages empty sections', () => {
expect(util.validatePackageId('com.example.')).toBeFalse();
expect(util.validatePackageId('.com.example')).toBeFalse();
expect(util.validatePackageId('com..example')).toBeFalse();
});

it('packages with less than 2 sections return false', () => {
expect(util.validatePackageId('com')).toBeFalse();
expect(util.validatePackageId('')).toBeFalse();
});

it('packages starting with non-letters return false', () => {
expect(util.validatePackageId('com.1char.twa')).toBeFalse();
expect(util.validatePackageId('1com.char.twa')).toBeFalse();
expect(util.validatePackageId('com.char.1twa')).toBeFalse();
});
});
});

0 comments on commit a40acc2

Please sign in to comment.