Skip to content

Commit

Permalink
fix(execute): refactor URL character encoding mechanism function (#3463)
Browse files Browse the repository at this point in the history
Refs #3462
  • Loading branch information
glowcloud committed Apr 8, 2024
1 parent f327d4b commit d2c1bba
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 34 deletions.
6 changes: 3 additions & 3 deletions src/execute/oas3/parameter-builders.js
@@ -1,4 +1,4 @@
import stylize, { encodeDisallowedCharacters } from './style-serializer.js';
import stylize, { encodeCharacters } from './style-serializer.js';
import serialize from './content-serializer.js';

export function path({ req, value, parameter }) {
Expand All @@ -11,14 +11,14 @@ export function path({ req, value, parameter }) {

req.url = req.url
.split(`{${name}}`)
.join(encodeDisallowedCharacters(serialize(value, effectiveMediaType), { escape: true }));
.join(encodeCharacters(serialize(value, effectiveMediaType)));
} else {
const styledValue = stylize({
key: parameter.name,
value,
style: style || 'simple',
explode: explode || false,
escape: true,
escape: 'reserved',
});

req.url = req.url.replace(new RegExp(`{${name}}`, 'g'), styledValue);
Expand Down
36 changes: 12 additions & 24 deletions src/execute/oas3/style-serializer.js
@@ -1,35 +1,19 @@
const isRfc3986Reserved = (char) => ":/?#[]@!$&'()*+,;=".indexOf(char) > -1;
const isRrc3986Unreserved = (char) => /^[a-z0-9\-._~]+$/i.test(char);
const isRfc3986Unreserved = (char) => /^[a-z0-9\-._~]+$/i.test(char);

// eslint-disable-next-line default-param-last
export function encodeDisallowedCharacters(str, { escape } = {}, parse) {
if (typeof str === 'number') {
str = str.toString();
}

if (typeof str !== 'string' || !str.length) {
return str;
}

if (!escape) {
return str;
}

if (parse) {
return JSON.parse(str);
}

export function encodeCharacters(str, characterSet = 'reserved') {
// In ES6 you can do this quite easily by using the new ... spread operator.
// This causes the string iterator (another new ES6 feature) to be used internally,
// and because that iterator is designed to deal with
// code points rather than UCS-2/UTF-16 code units.
return [...str]
.map((char) => {
if (isRrc3986Unreserved(char)) {
if (isRfc3986Unreserved(char)) {
return char;
}

if (isRfc3986Reserved(char) && escape === 'unsafe') {
if (isRfc3986Reserved(char) && characterSet === 'unsafe') {
return char;
}

Expand All @@ -56,13 +40,17 @@ export default function stylize(config) {
return encodePrimitive(config);
}

export function valueEncoder(value, escape) {
export function valueEncoder(value, escape = false) {
if (Array.isArray(value) || (value !== null && typeof value === 'object')) {
value = JSON.stringify(value);
} else if (typeof value === 'number' || typeof value === 'boolean') {
value = String(value);
}

if (escape && value.length > 0) {
return encodeCharacters(value, escape);
}
return encodeDisallowedCharacters(value, {
escape,
});
return value;
}

function encodeArray({ key, value, style, explode, escape }) {
Expand Down
153 changes: 146 additions & 7 deletions test/oas3/execute/style-serializer.js
@@ -1,9 +1,9 @@
import { encodeDisallowedCharacters } from '../../../src/execute/oas3/style-serializer.js';
import { encodeCharacters, valueEncoder } from '../../../src/execute/oas3/style-serializer.js';

describe('OAS3 style serializer', () => {
describe('encodeDisallowedCharacters', () => {
describe('encodeCharacters', () => {
test('should correctly encode ASCII characters', () => {
const tested = (str) => encodeDisallowedCharacters(str, { escape: true });
const tested = (str) => encodeCharacters(str);

expect(tested('!')).toEqual('%21');
expect(tested('#')).toEqual('%23');
Expand All @@ -28,7 +28,7 @@ describe('OAS3 style serializer', () => {
});

test('should correctly encode non-ASCII characters', () => {
const tested = (str) => encodeDisallowedCharacters(str, { escape: true });
const tested = (str) => encodeCharacters(str);
expect(tested('♥')).toEqual('%E2%99%A5');
expect(tested('テスト')).toEqual('%E3%83%86%E3%82%B9%E3%83%88');
expect(tested('𩸽')).toEqual('%F0%A9%B8%BD');
Expand All @@ -37,9 +37,109 @@ describe('OAS3 style serializer', () => {
'%F0%9F%91%A9%E2%80%8D%F0%9F%91%A9%E2%80%8D%F0%9F%91%A7%E2%80%8D%F0%9F%91%A7'
);
});
});

describe('valueEncoder', () => {
test('should correctly encode primitive values with `escape` set to `reserved`', () => {
const tested = (value) => valueEncoder(value, 'reserved');

expect(tested(123)).toEqual('123');
expect(tested('a#b$c%d[e]_~1')).toEqual('a%23b%24c%25d%5Be%5D_~1');
expect(tested(false)).toEqual('false');
});

test('should correctly encode primitive values with `escape` set to `unsafe`', () => {
const tested = (value) => valueEncoder(value, 'unsafe');

expect(tested(123)).toEqual('123');
expect(tested('a#b$c%d[e]_~1')).toEqual('a#b$c%25d[e]_~1');
expect(tested(false)).toEqual('false');
});

test('should skip encoding if `escape` is not set to true', () => {
const tested = (str) => encodeDisallowedCharacters(str);
test('should correctly encode objects with `escape` set to `reserved`', () => {
const tested = (value) => valueEncoder(value, 'reserved');

expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual(
'%7B%22a%22%3A%22a%23b%24c%25d%5Be%5D_~1%22%7D'
);
expect(tested({ a: { b: { c: 'd' } } })).toEqual(
'%7B%22a%22%3A%7B%22b%22%3A%7B%22c%22%3A%22d%22%7D%7D%7D'
);
expect(tested({ a: 123 })).toEqual('%7B%22a%22%3A123%7D');
expect(tested({ a: false })).toEqual('%7B%22a%22%3Afalse%7D');
});

test('should correctly encode objects with `escape` set to `unsafe`', () => {
const tested = (value) => valueEncoder(value, 'unsafe');

expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual('%7B%22a%22:%22a#b$c%25d[e]_~1%22%7D');
expect(tested({ a: { b: { c: 'd' } } })).toEqual(
'%7B%22a%22:%7B%22b%22:%7B%22c%22:%22d%22%7D%7D%7D'
);
expect(tested({ a: 123 })).toEqual('%7B%22a%22:123%7D');
expect(tested({ a: false })).toEqual('%7B%22a%22:false%7D');
});

test('should correctly encode arrays with `escape` set to `reserved`', () => {
const tested = (value) => valueEncoder(value, 'reserved');

expect(tested([1, 2, 3])).toEqual('%5B1%2C2%2C3%5D');
expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual(
'%5B%221%22%2C%222%22%2C%22a%23b%24c%25d%5Be%5D_~1%22%5D'
);
expect(
tested([
{
a: {
b: 'c',
},
},
])
).toEqual('%5B%7B%22a%22%3A%7B%22b%22%3A%22c%22%7D%7D%5D');
expect(
tested([
[
{
a: {
b: 'c',
},
},
],
])
).toEqual('%5B%5B%7B%22a%22%3A%7B%22b%22%3A%22c%22%7D%7D%5D%5D');
});

test('should correctly encode arrays with `escape` set to `unsafe`', () => {
const tested = (value) => valueEncoder(value, 'unsafe');

expect(tested([1, 2, 3])).toEqual('[1,2,3]');
expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual(
'[%221%22,%222%22,%22a#b$c%25d[e]_~1%22]'
);
expect(
tested([
{
a: {
b: 'c',
},
},
])
).toEqual('[%7B%22a%22:%7B%22b%22:%22c%22%7D%7D]');
expect(
tested([
[
{
a: {
b: 'c',
},
},
],
])
).toEqual('[[%7B%22a%22:%7B%22b%22:%22c%22%7D%7D]]');
});

test('should skip encoding if `escape` is not set', () => {
const tested = (value) => valueEncoder(value);

expect(tested('!')).toEqual('!');
expect(tested('#')).toEqual('#');
Expand All @@ -61,12 +161,51 @@ describe('OAS3 style serializer', () => {
expect(tested(']')).toEqual(']');
expect(tested('\n')).toEqual('\n');

// Non-ASCII too!
// Non-ASCII
expect(tested('♥')).toEqual('♥');
expect(tested('テスト')).toEqual('テスト');
expect(tested('𩸽')).toEqual('𩸽');
expect(tested('🍣')).toEqual('🍣');
expect(tested('👩‍👩‍👧‍👧')).toEqual('👩‍👩‍👧‍👧');

// Primitive
expect(tested(123)).toEqual('123');
expect(tested(false)).toEqual('false');

// Objects
expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual('{"a":"a#b$c%d[e]_~1"}');
expect(tested({ a: { b: { c: 'd' } } })).toEqual('{"a":{"b":{"c":"d"}}}');
expect(tested({ a: 123 })).toEqual('{"a":123}');
expect(tested({ a: false })).toEqual('{"a":false}');

// Arrays
expect(tested([1, 2, 3])).toEqual('[1,2,3]');
expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual('["1","2","a#b$c%d[e]_~1"]');
expect(
tested([
{
a: {
b: 'c',
},
},
{
d: {
e: 'f',
},
},
])
).toEqual('[{"a":{"b":"c"}},{"d":{"e":"f"}}]');
expect(
tested([
[
{
a: {
b: 'c',
},
},
],
])
).toEqual('[[{"a":{"b":"c"}}]]');
});
});
});

0 comments on commit d2c1bba

Please sign in to comment.