diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c99e9b..99ccf3e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,9 +5,9 @@ name: Node.js CI on: push: - branches: [ main ] + branches: [ main, 'version/**' ] pull_request: - branches: [ main ] + branches: [ main, 'version/**' ] jobs: build: diff --git a/changelog.md b/changelog.md index 649eb20..9bf33ec 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,14 @@ ChangeLog * Support for a new 'Date' type, from draft [draft-ietf-httpbis-sfbis-02][7]. +1.0.1 (????-??-??) +------------------ + +* This library emitted `TypeError` or a plain `Error` in a few places in the + parser, where it should have been `ParseError` this is corrected everywhere + now. + + 1.0.0 (2023-06-13) ------------------ diff --git a/src/parser.ts b/src/parser.ts index 3ce6d40..9e454f5 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -164,6 +164,9 @@ export default class Parser { private parseBareItem(): BareItem { const char = this.lookChar(); + if (char === undefined) { + throw new ParseError(this.pos, 'Unexpected end of string'); + } if (char.match(/^[-0-9]/)) { return this.parseIntegerOrDecimal(); } @@ -286,7 +289,7 @@ export default class Parser { } else if (char === '"') { return outputString; } else if (!isAscii(char)) { - throw new Error('Strings must be in the ASCII range'); + throw new ParseError(this.pos, 'Strings must be in the ASCII range'); } else { outputString += char; } @@ -307,7 +310,7 @@ export default class Parser { while(!this.eof()) { const char = this.lookChar(); - if (!/^[:/!#$%&'*+\-.^_`|~A-Za-z0-9]$/.test(char)) { + if (char===undefined || !/^[:/!#$%&'*+\-.^_`|~A-Za-z0-9]$/.test(char)) { return new Token(outputString); } outputString += this.getChar(); @@ -382,7 +385,7 @@ export default class Parser { private parseKey(): string { - if (!this.lookChar().match(/^[a-z*]/)) { + if (!this.lookChar()?.match(/^[a-z*]/)) { throw new ParseError(this.pos, 'A key must begin with an asterisk or letter (a-z)'); } @@ -390,7 +393,7 @@ export default class Parser { while(!this.eof()) { const char = this.lookChar(); - if (!/^[a-z0-9_\-.*]$/.test(char)) { + if (char===undefined || !/^[a-z0-9_\-.*]$/.test(char)) { return outputString; } outputString += this.getChar(); @@ -402,8 +405,10 @@ export default class Parser { /** * Looks at the next character without advancing the cursor. + * + * Returns undefined if we were at the end of the string. */ - private lookChar():string { + private lookChar():string|undefined { return this.input[this.pos]; @@ -466,8 +471,9 @@ export default class Parser { } const isDigitRegex = /^[0-9]$/; -function isDigit(char: string): boolean { +function isDigit(char: string|undefined): boolean { + if (char===undefined) return false; return isDigitRegex.test(char); } diff --git a/test/httpwg-tests.js b/test/httpwg-tests.js index 498e61f..b925a23 100644 --- a/test/httpwg-tests.js +++ b/test/httpwg-tests.js @@ -1,6 +1,15 @@ const expect = require('chai').expect; -const parser = require('../dist/parser'); -const serializer = require('../dist/serializer'); +const { + parseItem, + parseList, + parseDictionary, + + serializeItem, + serializeList, + serializeDictionary, + + ParseError, +} = require('../dist'); const { Token, ByteSequence } = require('../dist'); const base32Encode = require('base32-encode'); const base32Decode = require('base32-decode'); @@ -101,13 +110,13 @@ function makeParseTest(test) { try { switch(test.header_type) { case 'item' : - result = parser.parseItem(input); + result = parseItem(input); break; case 'list' : - result = parser.parseList(input); + result = parseList(input); break; case 'dictionary' : - result = parser.parseDictionary(input); + result = parseDictionary(input); break; default: throw new Error('Unsupported header type: ' + test.header_type); @@ -119,13 +128,21 @@ function makeParseTest(test) { if (test.must_fail) { expect(hadError).to.equal(true, 'Parsing this should result in a failure'); + + if (!(caughtError instanceof ParseError)) { + console.error('Original error:'); + console.error(caughtError); + throw new Error( + `Errors during the parsing phase should be of type "ParseError" We got: "${caughtError.constructor.name}"`, + {cause: caughtError} + ); + } } else { if (hadError) { - // There was an error + if (test.can_fail) { - // Failure is OK - expect(hadError).to.equal(true); + expect(caughtError instanceof ParseError).to.equal(true); } else { // Failure is NOT OK throw new Error('We should not have failed but got an error: ' + caughtError.message); @@ -201,13 +218,13 @@ function makeSerializeTest(test) { try { switch(test.header_type) { case 'item' : - output = serializer.serializeItem(unpackTestValue(input)); + output = serializeItem(unpackTestValue(input)); break; case 'list' : - output = serializer.serializeList(unpackTestValue(input)); + output = serializeList(unpackTestValue(input)); break; case 'dictionary' : - output = serializer.serializeDictionary(unpackDictionary(input)); + output = serializeDictionary(unpackDictionary(input)); break; default: throw new Error('Unsupported header type: ' + test.header_type); @@ -224,6 +241,7 @@ function makeSerializeTest(test) { if (hadError) { // There was an error if (test.can_fail) { + // Failure is OK expect(hadError).to.equal(true); } else { @@ -235,6 +253,7 @@ function makeSerializeTest(test) { try { expect(output).to.deep.equal(expected); } catch (e) { + if (test.can_fail) { // Optional failure this.skip('can_fail was true');