diff --git a/index.js b/index.js index 8a8c723..7611ab6 100644 --- a/index.js +++ b/index.js @@ -63,16 +63,25 @@ const ENTERPRISE_USER = 'EnterpriseUserAccount'; const allowedChars = [ '0-9', // ASCII digits 'A-Za-z', // ASCII alphabets - '\\p{Script=Hiragana}', // Hiragana - '\\p{Script=Katakana}', // Katakana - '\\p{Script=Han}', // CJK Han (Kanji) - '\\p{Script=Hangul}', // Hangul - '._/@\\-+', // ASCII symbols (Common filename-safe symbols allowed by Git) - '\\u3005' // Ideographic iteration mark: 々 + '\\p{Script_Extensions=Hiragana}', // Hiragana + '\\p{Script_Extensions=Katakana}', // Katakana + '\\p{Script_Extensions=Han}', // CJK Han (Kanji) + '\\p{Script_Extensions=Hangul}', // Hangul + '\\p{Script_Extensions=Greek}', // Greek (e.g. α β γ) + ',._/#%@\\-+=()', // ASCII symbols (Common filename-safe symbols allowed by Git) + '\\u3005', // Ideographic iteration mark: 々 + '\\u3010', // Full-width opening black lenticular bracket: 【 + '\\u3011', // Full-width closing black lenticular bracket: 】 + '\\uff1a', // Full-width colon: : + '\\uff08', // Full-width opening parenthesis: ( + '\\uff09', // Full-width closing parenthesis: ) + '\\u3000', // Full-width space + '\\uff10-\\uff19', // Full-width digits: 0-9 + '\\uff21-\\uff3a', // Full-width uppercase letters: A-Z + '\\uff41-\\uff5a' // Full-width lowercase letters: a-z ]; const BRANCH_NAME_ALLOWED_CHAR_RE = new RegExp(`^[${allowedChars.join('')}]+$`, 'u'); -const BRANCH_NAME_DANGEROUS_CHAR_RE = /['"`;!#$&<>|]/u; -const BRANCH_NAME_FORBIDDEN_SEQUENCE_RE = /(\/\/|(^|\/)\.|\/$|\.\.|@\{|\.lock$|\.$)/; +const BRANCH_NAME_DANGEROUS_CHAR_RE = /['"`;!$&<>|]/u; /** * Trim shell command indents @@ -153,8 +162,7 @@ function isSafeBranchName(name) { return ( BRANCH_NAME_ALLOWED_CHAR_RE.test(name) && !hasControlCharacters(name) && - !BRANCH_NAME_DANGEROUS_CHAR_RE.test(name) && - !BRANCH_NAME_FORBIDDEN_SEQUENCE_RE.test(name) + !BRANCH_NAME_DANGEROUS_CHAR_RE.test(name) ); } diff --git a/test/index.test.js b/test/index.test.js index ca42e6a..96e8aa0 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -277,23 +277,24 @@ describe('index', function () { { category: 'ASCII digits', value: 'feature/20260615' }, { category: 'ASCII uppercase', value: 'feature/RELEASE' }, { category: 'ASCII lowercase', value: 'feature/release' }, - { category: 'hiragana', value: 'feature/ひらがなゔ' }, - { category: 'katakana', value: 'feature/カタカナヴ' }, + { category: 'hiragana', value: 'feature/ひらがなゔー' }, + { category: 'katakana', value: 'feature/カタカナヴー' }, { category: 'cjk', value: 'feature/漢字東京' }, - { category: 'hangul', value: 'feature/한글테스트' } + { category: 'hangul', value: 'feature/한글테스트' }, + { category: 'greek', value: 'feature/alpha-β' }, + { category: 'jp punctuation', value: 'feature/、。々' }, + { category: 'jp brackets', value: 'feature/「名」【称】' }, + { category: 'fullwidth symbols', value: 'feature/():' }, + { category: 'fullwidth digits', value: 'feature/987' }, + { category: 'fullwidth letters', value: 'feature/Mm' } ]; const rejectedBranchCategorySamples = [ { category: 'ASCII symbols', value: 'feature/a+b.c=d,e@_](-)%' }, { category: 'extended latin', value: 'feature/Angstrom-Ångström-Đ' }, - { category: 'greek', value: 'feature/alpha-β' }, { category: 'control-special', value: 'feature/zero\u200bwidth※↑→−' }, { category: 'circled numbers', value: 'feature/④⑥⑧' }, - { category: 'jp punctuation', value: 'feature/、。々' }, - { category: 'jp brackets', value: 'feature/「名」【称】' }, { category: 'variation selector', value: 'feature/テスト\ufe0f' }, - { category: 'fullwidth symbols', value: 'feature/&():>_' }, - { category: 'fullwidth digits', value: 'feature/987' }, - { category: 'fullwidth letters', value: 'feature/Mm' } + { category: 'fullwidth symbols', value: 'feature/#&>_' } ]; beforeEach(() => { @@ -325,7 +326,7 @@ describe('index', function () { ); }); - [`'`, '"', '`', ';', '!', '#', '&', '$', '<', '>', '|'].forEach(char => { + [`'`, '"', '`', ';', '!', '&', '$', '<', '>', '|'].forEach(char => { it(`rejects branch names containing shell metacharacter: ${char}`, () => { config.branch = `branch${char}name`; @@ -339,20 +340,6 @@ describe('index', function () { }); }); - ['branch..name', 'branch@{name', 'branch/.name', 'branch/', '.branch', 'branch.lock'].forEach(branchName => { - it(`rejects branch names with forbidden git ref sequence: ${branchName}`, () => { - config.branch = branchName; - - return scm.getCheckoutCommand(config).then( - () => assert.fail('expected getCheckoutCommand to reject'), - err => { - assert.match(err.message, /Invalid branch name/); - assert.equal(err.statusCode, 400); - } - ); - }); - }); - it('accepts branch names with conservatively-safe special characters', () => { config.branch = 'feature/some_module.v1+rc1-final@host'; @@ -468,7 +455,7 @@ describe('index', function () { ); }); - [`'`, '"', '`', ';', '!', '#', '&', '$', '<', '>', '|'].forEach(char => { + [`'`, '"', '`', ';', '!', '&', '$', '<', '>', '|'].forEach(char => { it(`rejects PR branch names containing shell metacharacter: ${char}`, () => { config.prRef = 'pull/3/merge'; config.prBranchName = `branch${char}name`;