[core-rest-pipeline] handle x-www-form-urlencoded form data#18560
[core-rest-pipeline] handle x-www-form-urlencoded form data#18560jeremymeng merged 4 commits intoAzure:mainfrom
x-www-form-urlencoded form data#18560Conversation
Currently `formDataPolicy` doesn't support x-www-form-urlencoded form data. Sending request with such form data would cause `write EPIPE` error. This PR adds handling of `x-www-form-urlencoded` form data. With this the workaround in container registry library can be removed.
|
making it a draft PR for now. Will not merge before releasing ACR RC next week. |
|
This pull request is protected by Check Enforcer. What is Check Enforcer?Check Enforcer helps ensure all pull requests are covered by at least one check-run (typically an Azure Pipeline). When all check-runs associated with this pull request pass then Check Enforcer itself will pass. Why am I getting this message?You are getting this message because Check Enforcer did not detect any check-runs being associated with this pull request within five minutes. This may indicate that your pull request is not covered by any pipelines and so Check Enforcer is correctly blocking the pull request being merged. What should I do now?If the check-enforcer check-run is not passing and all other check-runs associated with this PR are passing (excluding license-cla) then you could try telling Check Enforcer to evaluate your pull request again. You can do this by adding a comment to this pull request as follows: What if I am onboarding a new service?Often, new services do not have validation pipelines associated with them, in order to bootstrap pipelines for a new service, you can issue the following command as a pull request comment: |
xirzec
left a comment
There was a problem hiding this comment.
Thanks for adding this support! It's strange that we never needed it in core-http, but I'm glad we're adding it now.
I think there is a simpler way than doing the urlencoding manually -- namely using URLSearchParams to do the heavy lifting. See the comment I left in the browser version of the policy. I think we can do the same thing in node.
| * @returns result of form data encoded for application/x-www-form-urlencoded content type. | ||
| * See https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4 for more details. | ||
| */ | ||
| export function urlEncode(formData: FormDataMap) { |
There was a problem hiding this comment.
nit: can we name this something that indicates it is only for form data? e.g.
| export function urlEncode(formData: FormDataMap) { | |
| export function urlEncodeFormData(formData: FormDataMap) { |
| const result = await policy.sendRequest(request, next); | ||
|
|
||
| assert.isUndefined(result.request.formData); | ||
| assert.strictEqual(result.request.body?.toString(), "[object FormData]"); |
There was a problem hiding this comment.
this doesn't seem like a very useful assertion - can we grab out the form data to make sure it's what we expect?
There was a problem hiding this comment.
form-data's FormData has these
declare class FormData extends stream.Readable {
constructor(options?: Options);
append(key: string, value: any, options?: FormData.AppendOptions | string): void;
getHeaders(userHeaders?: FormData.Headers): FormData.Headers;
submit(
params: string | FormData.SubmitOptions,
callback?: (error: Error | null, response: http.IncomingMessage) => void
): http.ClientRequest;
getBuffer(): Buffer;
setBoundary(boundary: string): void;
getBoundary(): string;
getLength(callback: (err: Error | null, length: number) => void): void;
getLengthSync(): number;
hasKnownLength(): boolean;
}which is different from browser's FormData interface
interface FormData {
append(name: string, value: string | Blob, fileName?: string): void;
delete(name: string): void;
get(name: string): FormDataEntryValue | null;
getAll(name: string): FormDataEntryValue[];
has(name: string): boolean;
set(name: string, value: string | Blob, fileName?: string): void;
forEach(callbackfn: (value: FormDataEntryValue, key: string, parent: FormData) => void, thisArg?: any): void;
}I guess I will have to do different tests for Node and browser.
There was a problem hiding this comment.
The node one supports 'toString', if that helps?
There was a problem hiding this comment.
not very helpful. it gave "[object FormData]" as in my first version of the test
| @@ -17,25 +18,30 @@ export function formDataPolicy(): PipelinePolicy { | |||
| name: formDataPolicyName, | |||
| async sendRequest(request: PipelineRequest, next: SendRequest): Promise<PipelineResponse> { | |||
There was a problem hiding this comment.
I think I found a much easier way to handle the encoding that avoids needing us to have a special helper:
async sendRequest(request: PipelineRequest, next: SendRequest): Promise<PipelineResponse> {
if (request.formData) {
const formData = request.formData;
const requestForm = new FormData();
for (const formKey of Object.keys(formData)) {
const formValue = formData[formKey];
if (Array.isArray(formValue)) {
for (const subValue of formValue) {
requestForm.append(formKey, subValue);
}
} else {
requestForm.append(formKey, formValue);
}
}
request.body = requestForm;
request.formData = undefined;
const contentType = request.headers.get("Content-Type");
if (contentType && contentType.indexOf("application/x-www-form-urlencoded") !== -1) {
request.body = new URLSearchParams(requestForm).toString();
} else if (contentType && contentType.indexOf("multipart/form-data") !== -1) {
// browser will automatically apply a suitable content-type header
request.headers.delete("Content-Type");
}
}
return next(request);
}There was a problem hiding this comment.
This looks great! I didn't know that we can construct URLSearchParams with FormData.
I just gave it a try. |
Yeah I think in the node version we'll have to fork the logic here a little, but it's still easy to take the original formData object and use that: new URLSearchParams(Object.entries(formData)).toString() |
I will probably need to loop and append to URLSearchParams to handle the Array case but we don't need to add a helper method any more |
- Split tests because multi-part verification is different between NodeJs and browser
xirzec
left a comment
There was a problem hiding this comment.
Very nice! Thanks for the update. 👍
|
/azp run js - containerregistry - tests |
|
Azure Pipelines successfully started running 1 pipeline(s). |

Currently
formDataPolicydoesn't support x-www-form-urlencoded form data.Sending request with such form data would cause
write EPIPEerror. This PRadds handling of
x-www-form-urlencodedform data. With this the workaround incontainer registry library can be removed.
Related: #14767