diff --git a/packages/insomnia-app/app/common/__tests__/misc.test.ts b/packages/insomnia-app/app/common/__tests__/misc.test.ts
index 4f9739d8d3..a65defae10 100644
--- a/packages/insomnia-app/app/common/__tests__/misc.test.ts
+++ b/packages/insomnia-app/app/common/__tests__/misc.test.ts
@@ -1,18 +1,26 @@
import { globalBeforeEach } from '../../__jest__/before-each';
-import * as misc from '../misc';
import {
+ chunkArray,
convertEpochToMilliseconds,
+ debounce,
diffPatchObj,
+ filterHeaders,
+ fuzzyMatch,
+ fuzzyMatchAll,
+ generateId,
+ hasAuthHeader,
isNotNullOrUndefined,
+ keyedDebounce,
pluralize,
snapNumberToLimits,
+ xmlDecode,
} from '../misc';
describe('hasAuthHeader()', () => {
beforeEach(globalBeforeEach);
it('finds valid header', () => {
- const yes = misc.hasAuthHeader([
+ const yes = hasAuthHeader([
{
name: 'foo',
value: 'bar',
@@ -26,7 +34,7 @@ describe('hasAuthHeader()', () => {
});
it('finds valid header case insensitive', () => {
- const yes = misc.hasAuthHeader([
+ const yes = hasAuthHeader([
{
name: 'foo',
value: 'bar',
@@ -44,12 +52,12 @@ describe('generateId()', () => {
beforeEach(globalBeforeEach);
it('generates a valid ID', () => {
- const id = misc.generateId('foo');
+ const id = generateId('foo');
expect(id).toMatch(/^foo_[a-z0-9]{32}$/);
});
it('generates without prefix', () => {
- const id = misc.generateId();
+ const id = generateId();
expect(id).toMatch(/^[a-z0-9]{32}$/);
});
});
@@ -58,13 +66,13 @@ describe('filterHeaders()', () => {
beforeEach(globalBeforeEach);
it('handles bad headers', () => {
- expect(misc.filterHeaders(null, null)).toEqual([]);
- expect(misc.filterHeaders([], null)).toEqual([]);
- expect(misc.filterHeaders(['bad'], null)).toEqual([]);
- expect(misc.filterHeaders(['bad'], 'good')).toEqual([]);
- expect(misc.filterHeaders(null, 'good')).toEqual([]);
+ expect(filterHeaders(null, null)).toEqual([]);
+ expect(filterHeaders([], null)).toEqual([]);
+ expect(filterHeaders(['bad'], null)).toEqual([]);
+ expect(filterHeaders(['bad'], 'good')).toEqual([]);
+ expect(filterHeaders(null, 'good')).toEqual([]);
expect(
- misc.filterHeaders(
+ filterHeaders(
[
{
name: '',
@@ -75,7 +83,7 @@ describe('filterHeaders()', () => {
),
).toEqual([]);
expect(
- misc.filterHeaders(
+ filterHeaders(
[
{
name: 123,
@@ -86,7 +94,7 @@ describe('filterHeaders()', () => {
),
).toEqual([]);
expect(
- misc.filterHeaders(
+ filterHeaders(
[
{
name: 'good',
@@ -97,7 +105,7 @@ describe('filterHeaders()', () => {
),
).toEqual([]);
expect(
- misc.filterHeaders(
+ filterHeaders(
[
{
name: 'good',
@@ -108,7 +116,7 @@ describe('filterHeaders()', () => {
),
).toEqual([]);
expect(
- misc.filterHeaders(
+ filterHeaders(
[
{
name: 'good',
@@ -134,7 +142,7 @@ describe('keyedDebounce()', () => {
it('debounces correctly', () => {
const resultsList = [];
- const fn = misc.keyedDebounce(results => {
+ const fn = keyedDebounce(results => {
resultsList.push(results);
}, 100);
fn('foo', 'bar');
@@ -163,7 +171,7 @@ describe('debounce()', () => {
it('debounces correctly', () => {
const resultList = [];
- const fn = misc.debounce((...args) => {
+ const fn = debounce((...args) => {
resultList.push(args);
}, 100);
fn('foo');
@@ -182,12 +190,12 @@ describe('fuzzyMatch()', () => {
beforeEach(globalBeforeEach);
it('can get a positive fuzzy match on a single field', () => {
- expect(misc.fuzzyMatch('test', 'testing')).toEqual({
+ expect(fuzzyMatch('test', 'testing')).toEqual({
score: -3,
indexes: [0, 1, 2, 3],
target: 'testing',
});
- expect(misc.fuzzyMatch('tst', 'testing')).toEqual({
+ expect(fuzzyMatch('tst', 'testing')).toEqual({
score: -2004,
indexes: [0, 2, 3],
target: 'testing',
@@ -195,8 +203,8 @@ describe('fuzzyMatch()', () => {
});
it('can get a negative fuzzy match on a single field', () => {
- expect(misc.fuzzyMatch('foo', undefined)).toBeNull();
- expect(misc.fuzzyMatch('foo', 'bar')).toBeNull();
+ expect(fuzzyMatch('foo', undefined)).toBeNull();
+ expect(fuzzyMatch('foo', 'bar')).toBeNull();
});
});
@@ -204,16 +212,16 @@ describe('fuzzyMatchAll()', () => {
beforeEach(globalBeforeEach);
it('can get a positive fuzzy match on multiple fields', () => {
- expect(misc.fuzzyMatchAll('', [undefined])).toEqual(null);
- expect(misc.fuzzyMatchAll('', ['testing'])).toEqual(null);
- expect(misc.fuzzyMatchAll(' ', ['testing'])).toEqual(null);
- expect(misc.fuzzyMatchAll('test', ['testing', 'foo'])).toEqual({
+ expect(fuzzyMatchAll('', [undefined])).toEqual(null);
+ expect(fuzzyMatchAll('', ['testing'])).toEqual(null);
+ expect(fuzzyMatchAll(' ', ['testing'])).toEqual(null);
+ expect(fuzzyMatchAll('test', ['testing', 'foo'])).toEqual({
score: -3,
indexes: [0, 1, 2, 3],
target: 'testing foo',
});
expect(
- misc.fuzzyMatchAll('test foo', ['testing', 'foo'], {
+ fuzzyMatchAll('test foo', ['testing', 'foo'], {
splitSpace: true,
}),
).toEqual({
@@ -221,13 +229,13 @@ describe('fuzzyMatchAll()', () => {
indexes: [0, 1, 2, 3, 0, 1, 2],
target: 'testing foo',
});
- expect(misc.fuzzyMatchAll('tst', ['testing'])).toEqual({
+ expect(fuzzyMatchAll('tst', ['testing'])).toEqual({
score: -2004,
indexes: [0, 2, 3],
target: 'testing',
});
expect(
- misc.fuzzyMatch('tst this ou', 'testing this out', {
+ fuzzyMatch('tst this ou', 'testing this out', {
splitSpace: true,
loose: true,
}),
@@ -239,15 +247,15 @@ describe('fuzzyMatchAll()', () => {
});
it('can get a negative fuzzy match on multiple fields', () => {
- expect(misc.fuzzyMatchAll('foo', [undefined])).toEqual(null);
- expect(misc.fuzzyMatchAll('foo', ['bar'])).toEqual(null);
- expect(misc.fuzzyMatchAll('wrong this ou', ['testing', 'this', 'out'])).toEqual(null);
+ expect(fuzzyMatchAll('foo', [undefined])).toEqual(null);
+ expect(fuzzyMatchAll('foo', ['bar'])).toEqual(null);
+ expect(fuzzyMatchAll('wrong this ou', ['testing', 'this', 'out'])).toEqual(null);
});
});
describe('chunkArray()', () => {
it('works with exact divisor', () => {
- const chunks = misc.chunkArray([1, 2, 3, 4, 5, 6], 3);
+ const chunks = chunkArray([1, 2, 3, 4, 5, 6], 3);
expect(chunks).toEqual([
[1, 2, 3],
[4, 5, 6],
@@ -255,7 +263,7 @@ describe('chunkArray()', () => {
});
it('works with weird divisor', () => {
- const chunks = misc.chunkArray([1, 2, 3, 4, 5, 6], 4);
+ const chunks = chunkArray([1, 2, 3, 4, 5, 6], 4);
expect(chunks).toEqual([
[1, 2, 3, 4],
[5, 6],
@@ -263,12 +271,12 @@ describe('chunkArray()', () => {
});
it('works with empty', () => {
- const chunks = misc.chunkArray([], 4);
+ const chunks = chunkArray([], 4);
expect(chunks).toEqual([]);
});
it('works with less than one chunk', () => {
- const chunks = misc.chunkArray([1, 2], 4);
+ const chunks = chunkArray([1, 2], 4);
expect(chunks).toEqual([[1, 2]]);
});
});
@@ -397,3 +405,11 @@ describe('isNotNullOrUndefined', () => {
expect(isNotNullOrUndefined(undefined)).toBe(false);
});
});
+
+describe('xmlDecode()', () => {
+ it('unescape characters', () => {
+ const input = '<a href="http://example.com?query1=value1&query2=value2">a link</a>';
+ const output = 'a link';
+ expect(xmlDecode(input)).toEqual(output);
+ });
+});
diff --git a/packages/insomnia-app/app/common/misc.ts b/packages/insomnia-app/app/common/misc.ts
index 3bc584fe1b..46f8157ec1 100644
--- a/packages/insomnia-app/app/common/misc.ts
+++ b/packages/insomnia-app/app/common/misc.ts
@@ -200,6 +200,19 @@ export function preventDefault(e: Event) {
e.preventDefault();
}
+export function xmlDecode(input: string) {
+ const ESCAPED_CHARACTERS_MAP = {
+ '&': '&',
+ '"': '"',
+ '<': '<',
+ '>': '>',
+ };
+
+ return input.replace(/("|<|>|&)/g, (_: string, item: keyof typeof ESCAPED_CHARACTERS_MAP) => (
+ ESCAPED_CHARACTERS_MAP[item])
+ );
+}
+
export function fnOrString(v: string | ((...args: any[]) => any), ...args: any[]) {
if (typeof v === 'string') {
return v;
diff --git a/packages/insomnia-app/app/ui/components/key-value-editor/row.tsx b/packages/insomnia-app/app/ui/components/key-value-editor/row.tsx
index aba94c99d1..7ba317e9a1 100644
--- a/packages/insomnia-app/app/ui/components/key-value-editor/row.tsx
+++ b/packages/insomnia-app/app/ui/components/key-value-editor/row.tsx
@@ -169,6 +169,7 @@ class KeyValueEditorRow extends PureComponent {
_handleTypeChange(def) {
// Remove newlines if converting to text
+ // WARNING: props should never be overwritten!
let value = this.props.pair.value || '';
if (def.type === 'text' && !def.multiline && value.includes('\n')) {
diff --git a/packages/insomnia-app/app/ui/components/viewers/response-viewer.tsx b/packages/insomnia-app/app/ui/components/viewers/response-viewer.tsx
index 724b9fc73d..e2406ca6b9 100644
--- a/packages/insomnia-app/app/ui/components/viewers/response-viewer.tsx
+++ b/packages/insomnia-app/app/ui/components/viewers/response-viewer.tsx
@@ -12,6 +12,7 @@ import {
import { clickLink } from '../../../common/electron-helpers';
import { hotKeyRefs } from '../../../common/hotkeys';
import { executeHotKey } from '../../../common/hotkeys-listener';
+import { xmlDecode } from '../../../common/misc';
import CodeEditor from '../codemirror/code-editor';
import KeydownBinder from '../keydown-binder';
import CSVViewer from './response-csv-viewer';
@@ -71,15 +72,6 @@ class ResponseViewer extends Component {
}
}
- _decodeIconv(bodyBuffer: Buffer, charset: string) {
- try {
- return iconv.decode(bodyBuffer, charset);
- } catch (err) {
- console.warn('[response] Failed to decode body', err);
- return bodyBuffer.toString();
- }
- }
-
_handleDismissBlocker() {
this.setState({
blockingBecauseTooLarge: false,
@@ -205,6 +197,49 @@ class ResponseViewer extends Component {
});
}
+ _getBody() {
+ const { bodyBuffer } = this.state;
+ const { contentType } = this.props;
+
+ if (!bodyBuffer) {
+ return '';
+ }
+
+ // Show everything else as "source"
+ const match = contentType.match(/charset=([\w-]+)/);
+ const charset = match && match.length >= 2 ? match[1] : 'utf-8';
+
+ try {
+ return iconv.decode(bodyBuffer, charset);
+ } catch (err) {
+ console.warn('[response] Failed to decode body', err);
+ // Sometimes iconv conversion fails so fallback to regular buffer
+ return bodyBuffer.toString();
+ }
+ }
+
+ /** Try to detect content-types if there isn't one */
+ _getMode() {
+ const { contentType } = this.props;
+ const body = this._getBody();
+ if (body?.match(/^\s*<\?xml [^?]*\?>/)) {
+ return 'application/xml';
+ } else {
+ return contentType;
+ }
+ }
+
+ _handleClickLink(url: string) {
+ const mode = this._getMode();
+
+ if (mode === 'application/xml') {
+ clickLink(xmlDecode(url));
+ return;
+ }
+
+ clickLink(url);
+ }
+
_renderView() {
const {
disableHtmlPreviewJs,
@@ -222,7 +257,8 @@ class ResponseViewer extends Component {
updateFilter,
url,
} = this.props;
- let contentType = this.props.contentType;
+ // WARNING: props should never be overwritten!
+ let { contentType } = this.props;
const { bodyBuffer, error: parseError } = this.state;
const error = responseError || parseError;
@@ -331,31 +367,37 @@ class ResponseViewer extends Component {
);
- } else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.includes('html')) {
- const match = contentType.match(/charset=([\w-]+)/);
- const charset = match && match.length >= 2 ? match[1] : 'utf-8';
+ }
+
+ if (previewMode === PREVIEW_MODE_FRIENDLY && ct.includes('html')) {
return (
);
- } else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('application/pdf') === 0) {
+ }
+
+ if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('application/pdf') === 0) {
return (
);
- } else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('text/csv') === 0) {
+ }
+
+ if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('text/csv') === 0) {
return (
);
- } else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('multipart/') === 0) {
+ }
+
+ if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('multipart/') === 0) {
return (
{
url={url}
/>
);
- } else if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('audio/') === 0) {
+ }
+
+ if (previewMode === PREVIEW_MODE_FRIENDLY && ct.indexOf('audio/') === 0) {
const justContentType = contentType.split(';')[0];
const base64Body = bodyBuffer.toString('base64');
return (
@@ -384,57 +428,42 @@ class ResponseViewer extends Component {
);
- } else if (previewMode === PREVIEW_MODE_RAW) {
- const match = contentType.match(/charset=([\w-]+)/);
- const charset = match && match.length >= 2 ? match[1] : 'utf-8';
+ }
+
+ if (previewMode === PREVIEW_MODE_RAW) {
return (
);
- } else {
- // Show everything else as "source"
- const match = contentType.match(/charset=([\w-]+)/);
- const charset = match && match.length >= 2 ? match[1] : 'utf-8';
-
- // Sometimes iconv conversion fails so fallback to regular buffer
- const body = this._decodeIconv(bodyBuffer, charset);
-
- // Try to detect content-types if there isn't one
- let mode;
-
- if (!mode && body.match(/^\s*<\?xml [^?]*\?>/)) {
- mode = 'application/xml';
- } else {
- mode = contentType;
- }
-
- return (
-
- );
}
+
+ // Show everything else as "source"
+ return (
+
+ );
}
render() {