-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix reconciler TXT record handling (#465)
* fix: Reconciler TXT record handling * fix: typo * feat: add tests, fix quotation mark handling * fix: backslash handling * fix * fix: comment
- Loading branch information
dadolhay
authored
Mar 28, 2023
1 parent
eeaed98
commit 3c16e38
Showing
4 changed files
with
198 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { DnsRecordType } from '@prisma/client'; | ||
|
||
/** | ||
* We need to use some special structures when sending / receiving recordSets | ||
* from Route53 | ||
* | ||
* https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/ResourceRecordTypes.html#TXTFormat | ||
* | ||
* `"String 1" "String 2" "String 3"` where the original value is cut to 255 char long strings | ||
* Note, TXT records should not include quotation marks | ||
*/ | ||
|
||
/** | ||
* in each segment, replace characters that are not between \o040 - \o176 | ||
* use a replace fn, to generate our 3 digit octal codes, i.e., '!' => '\041' | ||
* \o040 = \d33 = ascii `!` and \o176 = \d126 = '~' | ||
* `\` needs to become `\\` and `"` has to be `\"` | ||
*/ | ||
const escapeFn = (char: string): string => { | ||
if (char === '\\') { | ||
return '\\\\'; | ||
} | ||
if (char === '"') { | ||
return '\\"'; | ||
} | ||
|
||
return `\\${char.charCodeAt(0).toString(8).padStart(3, '0')}`; | ||
}; | ||
|
||
/** | ||
* convert back the escaped octal values i.e., '\041' => '!' | ||
* also unescape \ and " characters | ||
*/ | ||
const unescapeFn = (match: string, selection: string): string => { | ||
if (selection === '\\' || selection === '"') { | ||
return selection; | ||
} | ||
|
||
return String.fromCharCode(parseInt(selection, 8)); | ||
}; | ||
|
||
export const toRoute53RecordValue = (type: DnsRecordType, value: string): string => { | ||
if (type !== DnsRecordType.TXT) { | ||
return value; | ||
} | ||
|
||
// Create an uninitialized array with the length to hold our split up strings (max 255 chars) | ||
const segments = new Array(Math.ceil(value.length / 255)) | ||
// Initialize with undefined | ||
.fill(undefined) | ||
// Loop through, using the index split out the appropriate parts from the original string | ||
.map((segment, index) => value.substring(index * 255, (index + 1) * 255)) | ||
// escape | ||
.map((segment) => segment.replace(/([^!-~]|[\\"])/g, escapeFn)) | ||
// add quotation marks around the segments | ||
.map((segment) => `"${segment}"`); | ||
|
||
// Finally join the segments together with a white space | ||
return segments.join(' '); | ||
}; | ||
|
||
export const fromRoute53RecordValue = (type: DnsRecordType, value: string): string => { | ||
if (type !== DnsRecordType.TXT) { | ||
return value; | ||
} | ||
|
||
// Since space characters are octally escaped, I can use whitespace to split | ||
const segments = value | ||
.split(' ') | ||
// Strip out the leading and trailing parenthesis | ||
.map((segment) => segment.substring(1, segment.length - 1)) | ||
// convert back the escaped octal values i.e., '\041' => '!' | ||
// also unescape \ and " characters | ||
.map((segment) => segment.replace(/\\(\d{3}|\\|")/g, unescapeFn)); | ||
|
||
return segments.join(''); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { | ||
toRoute53RecordValue, | ||
fromRoute53RecordValue, | ||
} from '~/queues/reconciler/route53Utils.server'; | ||
import { DnsRecordType } from '@prisma/client'; | ||
|
||
describe('Route53 functionality test raw => Route53 format', () => { | ||
test('Ignores a non-TXT record', () => { | ||
const result = toRoute53RecordValue(DnsRecordType.A, '1.2.3.4'); | ||
|
||
expect(result).toEqual('1.2.3.4'); | ||
}); | ||
|
||
test('Handles input with no special chars, and < 255 length', () => { | ||
// Adding characters from the edge of range | ||
const result = toRoute53RecordValue(DnsRecordType.TXT, '!Hello~'); | ||
|
||
expect(result).toEqual('"!Hello~"'); | ||
}); | ||
|
||
test('Handles special characters correctly (space, quotation mark, backslash)', () => { | ||
const result = toRoute53RecordValue( | ||
DnsRecordType.TXT, | ||
// Using a sequence of chars, trying to mislead parsing | ||
[' ', '\\', '"', '\\', ' ', 'Hello', ' ', '"', 'World', '"', ' '].join('') | ||
); | ||
|
||
expect(result).toEqual( | ||
[ | ||
'"', | ||
'\\040', | ||
'\\\\', | ||
'\\"', | ||
'\\\\', | ||
'\\040', | ||
'Hello', | ||
'\\040', | ||
'\\"', | ||
'World', | ||
'\\"', | ||
'\\040', | ||
'"', | ||
].join('') | ||
); | ||
}); | ||
|
||
test('Handles long strings (> 255 char)', () => { | ||
const result = toRoute53RecordValue(DnsRecordType.TXT, 'a'.repeat(1000)); | ||
|
||
expect(result).toEqual( | ||
`"${'a'.repeat(255)}" "${'a'.repeat(255)}" "${'a'.repeat(255)}" "${'a'.repeat(235)}"` | ||
); | ||
}); | ||
}); | ||
|
||
describe('Route53 functionality test Route53 => raw format', () => { | ||
test('Ignores a non-TXT record', () => { | ||
const result = fromRoute53RecordValue(DnsRecordType.A, '1.2.3.4'); | ||
|
||
expect(result).toEqual('1.2.3.4'); | ||
}); | ||
|
||
test('Handles input with no special chars, and < 255 length', () => { | ||
// Adding characters from the edge of range | ||
const result = fromRoute53RecordValue(DnsRecordType.TXT, '"!Hello~"'); | ||
|
||
expect(result).toEqual('!Hello~'); | ||
}); | ||
|
||
test('Handles special characters correctly (space quotation mark and backslash)', () => { | ||
const result = fromRoute53RecordValue( | ||
DnsRecordType.TXT, | ||
// Using a sequence of chars, trying to mislead parsing (do not double unescape escape characters) | ||
[ | ||
'"', | ||
'\\040', | ||
'\\\\', | ||
'\\"', | ||
'\\\\', | ||
'\\040', | ||
'Hello', | ||
'\\040', | ||
'\\"', | ||
'World', | ||
'\\"', | ||
'\\040', | ||
'"', | ||
].join('') | ||
); | ||
|
||
expect(result).toEqual( | ||
[' ', '\\', '"', '\\', ' ', 'Hello', ' ', '"', 'World', '"', ' '].join('') | ||
); | ||
}); | ||
|
||
test('Handles long strings (> 255 char)', () => { | ||
const result = fromRoute53RecordValue( | ||
DnsRecordType.TXT, | ||
`"${'a'.repeat(255)}" "${'a'.repeat(255)}" "${'a'.repeat(255)}" "${'a'.repeat(235)}"` | ||
); | ||
|
||
expect(result).toEqual('a'.repeat(1000)); | ||
}); | ||
}); |