Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/models/configurationSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface IRestClientSettings {
readonly enableSendRequestCodeLens: boolean;
readonly enableCustomVariableReferencesCodeLens: boolean;
readonly useContentDispositionFilename: boolean;
readonly jsonToForm?: boolean;
}

export class SystemSettings implements IRestClientSettings {
Expand Down Expand Up @@ -319,6 +320,8 @@ export class RequestSettings implements Partial<IRestClientSettings> {
private _followRedirect?: boolean = undefined;

private _rememberCookiesForSubsequentRequests?: boolean = undefined;

private _jsonToForm?: boolean = undefined;

public get followRedirect() {
return this._followRedirect;
Expand All @@ -328,12 +331,20 @@ export class RequestSettings implements Partial<IRestClientSettings> {
return this._rememberCookiesForSubsequentRequests;
}

public get jsonToForm() {
return this._jsonToForm;
}

public constructor(metadatas: Map<RequestMetadata, string | undefined>) {
if (metadatas.has(RequestMetadata.NoRedirect)) {
this._followRedirect = false;
} else if (metadatas.has(RequestMetadata.NoCookieJar)) {
this._rememberCookiesForSubsequentRequests = false;
}

if (metadatas.has(RequestMetadata.JsonToForm)) {
this._jsonToForm = true;
}
}
}

Expand Down Expand Up @@ -471,6 +482,10 @@ export class RestClientSettings implements IRestClientSettings {
return this.systemSettings.useContentDispositionFilename;
}

public get jsonToForm() {
return this.requestSettings.jsonToForm;
}

private readonly systemSettings = SystemSettings.Instance;

public constructor(private readonly requestSettings: RequestSettings) {
Expand Down
5 changes: 5 additions & 0 deletions src/models/requestMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export enum RequestMetadata {
* Used to allow user to interactively input variables for this request
*/
Prompt = 'prompt',

/**
* Represents a directive to automatically URL encode a JSON request body
*/
JsonToForm = 'jsontoform',
}

export function fromString(value: string): RequestMetadata | undefined {
Expand Down
32 changes: 32 additions & 0 deletions src/utils/httpRequestParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,38 @@ export class HttpRequestParser implements RequestParser {
let body = await this.parseBody(bodyLines, contentTypeHeader);
if (isGraphQlRequest) {
body = await this.createGraphQlBody(variableLines, contentTypeHeader, body);
} else if (this.settings.jsonToForm && typeof body === 'string') {
try {
// Strip redundant quotes around JSON objects/arrays to fix unescaped quote errors
const fixedBody = body.replace(/"\s*(\{[\s\S]*?\})\s*"/g, "$1").replace(/"\s*(\[[\s\S]*?\])\s*"/g, "$1");
const { parse } = require('jsonc-parser');
const errors: any[] = [];
const parsedJson = parse(fixedBody, errors, { allowTrailingComma: true });
if (errors.length > 0) {
throw new Error(`JSON parsing failed at offset ${errors[0].offset} with length ${errors[0].length}.`);
}
const encodedStringPairs: string[] = [];
for (const key in parsedJson) {
if (parsedJson.hasOwnProperty(key)) {
const value = parsedJson[key];
// If value is object/array, stringify it first
const stringValue = typeof value === 'object' ? JSON.stringify(value) : String(value);
encodedStringPairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(stringValue)}`);
}
}
body = encodedStringPairs.join('&');

// Force the content-type to be application/x-www-form-urlencoded if it was converted
const contentTypeKey = Object.keys(headers).find(k => k.toLowerCase() === 'content-type');
if (contentTypeKey) {
headers[contentTypeKey] = 'application/x-www-form-urlencoded';
} else {
headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
} catch (err) {
// Return descriptive error from JSON parse failure so the request errors out usefully
throw new Error(`Invalid JSON format for @jsonToForm directive: ${err.message}`);
}
} else if (this.settings.formParamEncodingStrategy !== FormParamEncodingStrategy.Never && typeof body === 'string' && MimeUtility.isFormUrlEncoded(contentTypeHeader)) {
if (this.settings.formParamEncodingStrategy === FormParamEncodingStrategy.Always) {
const stringPairs = body.split('&');
Expand Down