Merge pull request #11735 from keymanapp/fix/developer/11362-verify-email-addresses-in-package

fix(developer): verify email addresses in .kps and .keyboard_info
This commit is contained in:
Marc Durdin 2024-06-13 10:04:00 +10:00 committed by GitHub
commit b0ff102b45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 5920 additions and 9 deletions

View file

@ -2,4 +2,5 @@ export { validateMITLicense } from './src/validate-mit-license.js';
export { KeymanSentry, SentryNodeOptions } from './src/KeymanSentry.js';
export { getOption, loadOptions, clearOptions } from './src/options.js';
export { escapeMarkdownChar } from './src/markdown.js';
export { KeymanUrls } from './src/keyman-urls.js';
export { KeymanUrls } from './src/keyman-urls.js';
export { isValidEmail } from './src/is-valid-email.js';

View file

@ -0,0 +1,18 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Verify email address format, following WHATWG guidelines
*/
// There is no "good" definition of a valid email address. Email addresses are
// horrific. They can contain comments, whitespace, and all manner of ugly
// things. Because we use AJV to verify JSON files, we use their specification
// on what is a valid email address. Some useful references:
// * https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
// * http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363
// * https://github.com/ajv-validator/ajv-formats/blob/4ca86d21bd07571a30178cbb3714133db6eada9a/src/formats.ts#L122
// * https://github.com/ajv-validator/ajv-formats/blob/4ca86d21bd07571a30178cbb3714133db6eada9a/src/formats.ts#L65
export function isValidEmail(email: string) {
return /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(email);
}

View file

@ -0,0 +1,35 @@
import { assert } from 'chai';
import 'mocha';
import { isValidEmail } from '../src/is-valid-email.js';
describe('test-is-valid-email', function () {
it('should accept a valid email address', function() {
[
'email@example.com',
'email.example-true@example.com',
'email@example-example.com',
'email...@example.com',
'email_example1@1example.com',
'email+example@mail.example.com',
'Email@Example.Com'
].forEach(email => assert.isTrue(isValidEmail(email), `expected '${email}' to be valid`));
// This is accepted, but it's really a bit wonky. But that's an upstream
// issue with overly lax regex and not something we'll attempt to fix:
//
// assert.isTrue(isValidEmail('.@example.com'));
});
it('should reject invalid email addresses', function() {
[
'email@example.com, email2@example.com',
'<Mr Email> email@example.com',
'email@example_domain.com',
'email',
'email@.',
'email@example..com',
'email@',
'@example',
].forEach(email => assert.isFalse(isValidEmail(email), `expected '${email}' to be invalid`));
});
});

View file

@ -8,7 +8,7 @@ import { KeyboardInfoFile, KeyboardInfoFileIncludes, KeyboardInfoFileLanguageFon
import { KeymanFileTypes, CompilerCallbacks, KmpJsonFile, KmxFileReader, KMX, KeymanTargets, KeymanCompiler, CompilerOptions, KeymanCompilerResult, KeymanCompilerArtifacts, KeymanCompilerArtifact } from "@keymanapp/common-types";
import { KeyboardInfoCompilerMessages } from "./keyboard-info-compiler-messages.js";
import langtags from "./imports/langtags.js";
import { KeymanUrls, validateMITLicense } from "@keymanapp/developer-utils";
import { KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils";
import { KmpCompiler } from "@keymanapp/kmc-package";
import { SchemaValidators } from "@keymanapp/common-types";
@ -238,6 +238,11 @@ export class KeyboardInfoCompiler implements KeymanCompiler {
return null;
}
if(!isValidEmail(match[2])) {
this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_InvalidAuthorEmail({email:author.url}));
return null;
}
keyboard_info.authorEmail = match[2];
}
}

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2022 SIL International
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1 @@
khmer_angkor.js -text

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="utf-8"?>
<KeymanDeveloperProject>
<Options>
<BuildPath>$PROJECTPATH\build</BuildPath>
<CompilerWarningsAsErrors>True</CompilerWarningsAsErrors>
<WarnDeprecatedCode>True</WarnDeprecatedCode>
<CheckFilenameConventions>False</CheckFilenameConventions>
<ProjectType>keyboard</ProjectType>
</Options>
<Files>
<File>
<ID>id_f347675c33d2e6b1c705c787fad4941a</ID>
<Filename>khmer_angkor.kmn</Filename>
<Filepath>source\khmer_angkor.kmn</Filepath>
<FileVersion>1.3</FileVersion>
<FileType>.kmn</FileType>
<Details>
<Name>Khmer Angkor</Name>
<Copyright>© 2015-2022 SIL International</Copyright>
<Message>More than just a Khmer Unicode keyboard.</Message>
</Details>
</File>
<File>
<ID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ID>
<Filename>khmer_angkor.kps</Filename>
<Filepath>source\khmer_angkor.kps</Filepath>
<FileVersion></FileVersion>
<FileType>.kps</FileType>
<Details>
<Name>Khmer Angkor</Name>
<Copyright>© 2015-2022 SIL International</Copyright>
</Details>
</File>
<File>
<ID>id_8a1efc7c4ab7cfece8aedd847679ca27</ID>
<Filename>khmer_angkor.ico</Filename>
<Filepath>source\khmer_angkor.ico</Filepath>
<FileVersion></FileVersion>
<FileType>.ico</FileType>
<ParentFileID>id_f347675c33d2e6b1c705c787fad4941a</ParentFileID>
</File>
<File>
<ID>id_8dc195db32d1fd0514de0ad51fff5df0</ID>
<Filename>khmer_angkor.js</Filename>
<Filepath>source\..\build\khmer_angkor.js</Filepath>
<FileVersion></FileVersion>
<FileType>.js</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_10596632fcbf4138d24bcccf53e6ae01</ID>
<Filename>khmer_angkor.kvk</Filename>
<Filepath>source\..\build\khmer_angkor.kvk</Filepath>
<FileVersion></FileVersion>
<FileType>.kvk</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_0a851f95ce553ecd62cbee6c32ced68f</ID>
<Filename>khmer_angkor.kmx</Filename>
<Filepath>source\..\build\khmer_angkor.kmx</Filepath>
<FileVersion></FileVersion>
<FileType>.kmx</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_d8b6eb05f4b7e2945c10e04c1f49e4c8</ID>
<Filename>keyboard_layout.png</Filename>
<Filepath>source\welcome\keyboard_layout.png</Filepath>
<FileVersion></FileVersion>
<FileType>.png</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_724e5b4c63f10bc0abf7077f7c3172fc</ID>
<Filename>welcome.htm</Filename>
<Filepath>source\welcome\welcome.htm</Filepath>
<FileVersion></FileVersion>
<FileType>.htm</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_35857cb2b54f123612735ec948400082</ID>
<Filename>FONTLOG.txt</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\mondulkiri\FONTLOG.txt</Filepath>
<FileVersion></FileVersion>
<FileType>.txt</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_7e3afe5bb59b888b08b48cd5817d8de4</ID>
<Filename>Mondulkiri-B.ttf</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-B.ttf</Filepath>
<FileVersion></FileVersion>
<FileType>.ttf</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_b9734e80f86c69ea5ae4dfa9f0083d09</ID>
<Filename>Mondulkiri-BI.ttf</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-BI.ttf</Filepath>
<FileVersion></FileVersion>
<FileType>.ttf</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_25abe4d2b0abc03a5be5b666a8de776e</ID>
<Filename>Mondulkiri-I.ttf</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-I.ttf</Filepath>
<FileVersion></FileVersion>
<FileType>.ttf</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_b766568498108eee46ed1601ff69c47d</ID>
<Filename>Mondulkiri-R.ttf</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf</Filepath>
<FileVersion></FileVersion>
<FileType>.ttf</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_84544d04133cab3dbfc86b91ad1a4e17</ID>
<Filename>OFL.txt</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\mondulkiri\OFL.txt</Filepath>
<FileVersion></FileVersion>
<FileType>.txt</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_0c33fbefd1c20f487b1bea2343b3bb2c</ID>
<Filename>OFL-FAQ.txt</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\mondulkiri\OFL-FAQ.txt</Filepath>
<FileVersion></FileVersion>
<FileType>.txt</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_a59d89fca36a310147645fa2604e521b</ID>
<Filename>KAK_Documentation_EN.pdf</Filename>
<Filepath>source\welcome\KAK_Documentation_EN.pdf</Filepath>
<FileVersion></FileVersion>
<FileType>.pdf</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_5643c4cd3933b3ada0b4af6579305ec4</ID>
<Filename>KAK_Documentation_KH.pdf</Filename>
<Filepath>source\welcome\KAK_Documentation_KH.pdf</Filepath>
<FileVersion></FileVersion>
<FileType>.pdf</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_8da344c4cea6f467013357fe099006f5</ID>
<Filename>readme.htm</Filename>
<Filepath>source\readme.htm</Filepath>
<FileVersion></FileVersion>
<FileType>.htm</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_acb0dd94c60e345d999670e999cbd159</ID>
<Filename>image002.png</Filename>
<Filepath>source\welcome\image002.png</Filepath>
<FileVersion></FileVersion>
<FileType>.png</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_4edf70bc019f05b5ad39a2ea727ad547</ID>
<Filename>khmer_busra_kbd.ttf</Filename>
<Filepath>source\..\..\..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf</Filepath>
<FileVersion></FileVersion>
<FileType>.ttf</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
<File>
<ID>id_bc823844e4399751e1867016801f7327</ID>
<Filename>splash.gif</Filename>
<Filepath>source\splash.gif</Filepath>
<FileVersion></FileVersion>
<FileType>.gif</FileType>
<ParentFileID>id_8d4eb765f80c9f2b0f769cf4e4aaa456</ParentFileID>
</File>
</Files>
</KeymanDeveloperProject>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<Package>
<System>
<KeymanDeveloperVersion>15.0.266.0</KeymanDeveloperVersion>
<FileVersion>7.0</FileVersion>
</System>
<Options>
<LicenseFile>..\LICENSE.md</LicenseFile>
<FollowKeyboardVersion/>
</Options>
<Info>
<Name URL="">Khmer Angkor</Name>
<Copyright URL="">© 2015-2022 SIL International</Copyright>
<Author URL="mailto:makara_sok@sil.org, example@example.com">Multiple Authors</Author>
<Version URL=""></Version>
<WebSite URL="https://keyman.com/keyboards/khmer_angkor">https://keyman.com/keyboards/khmer_angkor</WebSite>
<Description URL="">Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors.</Description>
</Info>
<Files>
<File>
<Name>..\LICENSE.md</Name>
<Description>File LICENSE.md</Description>
<CopyLocation>0</CopyLocation>
<FileType>.md</FileType>
</File>
<File>
<Name>..\build\khmer_angkor.kmx</Name>
<Description>Keyboard Khmer Angkor</Description>
<CopyLocation>0</CopyLocation>
<FileType>.kmx</FileType>
</File>
<File>
<Name>..\build\khmer_angkor.js</Name>
<Description>Keyboard Khmer Angkor</Description>
<CopyLocation>0</CopyLocation>
<FileType>.js</FileType>
</File>
</Files>
<Keyboards>
<Keyboard>
<Name>Khmer Angkor</Name>
<ID>khmer_angkor</ID>
<Version>1.3</Version>
<Languages>
<Language ID="km">Central Khmer (Khmer, Cambodia)</Language>
</Languages>
</Keyboard>
</Keyboards>
</Package>

View file

@ -56,7 +56,7 @@ describe('KeyboardInfoCompilerMessages', function () {
assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_FileDoesNotExist).includes(KeymanFileTypes.Binary.WebKeyboard),
KeymanFileTypes.Binary.WebKeyboard+' not found in the message');
});
// ERROR_FileDoesNotExist (.kmp fileSize)
it('should generate ERROR_FileDoesNotExist error if .kmp file does not exist', async function() {
@ -141,7 +141,7 @@ describe('KeyboardInfoCompilerMessages', function () {
assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_LicenseFileIsMissing).includes(licenseFilename),
licenseFilename+' not found in the message');
});
// ERROR_LicenseFileIsDamaged (error on decode)
it('should generate ERROR_LicenseFileIsDamaged error if license file throws error on decode', async function() {
@ -197,7 +197,7 @@ describe('KeyboardInfoCompilerMessages', function () {
assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_LicenseIsNotValid).includes(licenseFilename),
licenseFilename+' not found in the message');
});
// ERROR_CannotBuildWithoutKmpFile
it('should generate ERROR_CannotBuildWithoutKmpFile error if .kmp file is not in sources', async function() {
@ -227,7 +227,7 @@ describe('KeyboardInfoCompilerMessages', function () {
assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_CannotBuildWithoutKmpFile),
`ERROR_CannotBuildWithoutKmpFile not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
});
// ERROR_NoLicenseFound
it('should generate ERROR_NoLicenseFound error if licence file is not in .kps options', async function() {
@ -257,7 +257,7 @@ describe('KeyboardInfoCompilerMessages', function () {
assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_NoLicenseFound),
`ERROR_NoLicenseFound not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
});
// ERROR_FontFileMetaDataIsInvalid
it('should generate ERROR_FontFileMetaDataIsInvalid error if font file meta data throws an error', async function() {
@ -285,7 +285,38 @@ describe('KeyboardInfoCompilerMessages', function () {
`ERROR_FontFileMetaDataIsInvalid not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_FontFileMetaDataIsInvalid).includes(kmpJsonData.files[0].name),
kmpJsonData.files[0].name+' not found in the message');
});
});
// ERROR_InvalidAuthorEmail
it('should generate ERROR_InvalidAuthorEmail error if multiple email addresses are listed in .kps', async function() {
const jsFilename = makePathToFixture('multiple-email-addresses', 'build', 'khmer_angkor.js');
const kpsFilename = makePathToFixture('multiple-email-addresses', 'source', 'khmer_angkor.kps');
const kmpFilename = makePathToFixture('multiple-email-addresses', 'build', 'khmer_angkor.kmp');
const sources = {
kmpFilename,
sourcePath: 'release/k/multiple-email-addresses',
kpsFilename,
jsFilename: jsFilename,
forPublishing: true,
};
const compiler = new KeyboardInfoCompiler();
assert.isTrue(await compiler.init(callbacks, {sources}));
let result: KeyboardInfoCompilerResult = null;
try {
result = await compiler.run(kmpFilename, null);
} catch(e) {
callbacks.printMessages();
throw e;
}
assert.isNull(result);
assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_InvalidAuthorEmail),
`ERROR_InvalidAuthorEmail not generated, instead got: `+JSON.stringify(callbacks.messages,null,2));
});
});
function nodeCompilerMessage(ncb: TestCompilerCallbacks, code: number): string {

View file

@ -7,7 +7,7 @@ import { minKeymanVersion } from "./min-keyman-version.js";
import { ModelInfoFile } from "./model-info-file.js";
import { CompilerCallbacks, CompilerOptions, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KmpJsonFile } from "@keymanapp/common-types";
import { ModelInfoCompilerMessages } from "./model-info-compiler-messages.js";
import { KeymanUrls, validateMITLicense } from "@keymanapp/developer-utils";
import { KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils";
/* c8 ignore start */
/**
@ -192,6 +192,11 @@ export class ModelInfoCompiler implements KeymanCompiler {
return null;
}
if(!isValidEmail(match[2])) {
this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_InvalidAuthorEmail({email:author.url}));
return null;
}
model_info.authorEmail = match[2];
}

View file

@ -134,5 +134,9 @@ export class CompilerMessages {
static ERROR_FileRecordIsMissingName = SevError | 0x001F;
static Error_FileRecordIsMissingName = (o:{description:string}) => m(this.ERROR_FileRecordIsMissingName,
`File record in the package with description '${o.description}' is missing a filename.`);
static ERROR_InvalidAuthorEmail = SevError | 0x0020;
static Error_InvalidAuthorEmail = (o:{email:string}) => m(this.ERROR_InvalidAuthorEmail,
`Invalid author email: ${def(o.email)}`);
}

View file

@ -1,6 +1,7 @@
import { KmpJsonFile, CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types';
import { CompilerMessages } from './package-compiler-messages.js';
import { keymanEngineForWindowsFiles, keymanForWindowsInstallerFiles, keymanForWindowsRedistFiles } from './redist-files.js';
import { isValidEmail } from '@keymanapp/developer-utils';
// The keyboard ID SHOULD adhere to this pattern:
const KEYBOARD_ID_PATTERN_PACKAGE = /^[a-z_][a-z0-9_]*\.(kps|kmp)$/;
@ -208,6 +209,21 @@ export class PackageValidation {
return false;
}
if(file.info?.author?.url) {
// we strip the mailto: from the .kps file for the .model_info
const match = file.info.author.url.match(/^(mailto\:)?(.+)$/);
/* c8 ignore next 3 */
if (match === null) {
this.callbacks.reportMessage(CompilerMessages.Error_InvalidAuthorEmail({email:file.info.author.url}));
return null;
}
if(!isValidEmail(match[2])) {
this.callbacks.reportMessage(CompilerMessages.Error_InvalidAuthorEmail({email:file.info.author.url}));
return null;
}
}
return true;
}
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Package>
<System>
<KeymanDeveloperVersion>15.0.266.0</KeymanDeveloperVersion>
<FileVersion>7.0</FileVersion>
</System>
<Info>
<!-- blank name -->
<Name URL="">Invalid Email Address</Name>
<Copyright URL="">© 2019 National Research Council Canada</Copyright>
<Author URL="&lt;Eddie Santos&gt; mailto:Eddie.Santos@nrc-cnrc.gc.ca">Eddie Antonio Santos</Author>
<Version>1.0</Version>
</Info>
<Files>
<File>
<Name>basic.kmx</Name>
<Description>Keyboard Basic</Description>
<CopyLocation>0</CopyLocation>
<FileType>.kmx</FileType>
</File>
</Files>
<Keyboards>
<Keyboard>
<Name>Basic</Name>
<ID>basic</ID>
<Version>1.0</Version>
<Languages>
<Language ID="km">Central Khmer (Khmer, Cambodia)</Language>
</Languages>
</Keyboard>
</Keyboards>
</Package>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Package>
<System>
<KeymanDeveloperVersion>15.0.266.0</KeymanDeveloperVersion>
<FileVersion>7.0</FileVersion>
</System>
<Info>
<!-- blank name -->
<Name URL="">Invalid Email Address</Name>
<Copyright URL="">© 2019 National Research Council Canada</Copyright>
<Author URL="mailto:Eddie.Santos@nrc-cnrc.gc.ca, example@example.com">Eddie Antonio Santos</Author>
<Version>1.0</Version>
</Info>
<Files>
<File>
<Name>basic.kmx</Name>
<Description>Keyboard Basic</Description>
<CopyLocation>0</CopyLocation>
<FileType>.kmx</FileType>
</File>
</Files>
<Keyboards>
<Keyboard>
<Name>Basic</Name>
<ID>basic</ID>
<Version>1.0</Version>
<Languages>
<Language ID="km">Central Khmer (Khmer, Cambodia)</Language>
</Languages>
</Keyboard>
</Keyboards>
</Package>

View file

@ -224,4 +224,16 @@ describe('CompilerMessages', function () {
CompilerMessages.ERROR_InvalidPackageFile);
});
// ERROR_InvalidAuthorEmail
it('should generate ERROR_InvalidAuthorEmail if author email address has multiple addresses', async function() {
await testForMessage(this, ['invalid', 'error_invalid_author_email_multiple.kps'],
CompilerMessages.ERROR_InvalidAuthorEmail);
});
it('should generate ERROR_InvalidAuthorEmail if author email address is formatted incorrectly', async function() {
await testForMessage(this, ['invalid', 'error_invalid_author_email.kps'],
CompilerMessages.ERROR_InvalidAuthorEmail);
});
});