Skip to content

Commit 1b4d900

Browse files
authored
fix(har): ignore boundary when matching multipart/form-data body (microsoft#31672)
Fixes microsoft#31495
1 parent 459b762 commit 1b4d900

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,17 @@ class HarBackend {
348348
continue;
349349
if (method === 'POST' && postData && candidate.request.postData) {
350350
const buffer = await this._loadContent(candidate.request.postData);
351-
if (!buffer.equals(postData))
352-
continue;
351+
if (!buffer.equals(postData)) {
352+
const boundary = multipartBoundary(headers);
353+
if (!boundary)
354+
continue;
355+
const candidataBoundary = multipartBoundary(candidate.request.headers);
356+
if (!candidataBoundary)
357+
continue;
358+
// Try to match multipart/form-data ignroing boundary as it changes between requests.
359+
if (postData.toString().replaceAll(boundary, '') !== buffer.toString().replaceAll(candidataBoundary, ''))
360+
continue;
361+
}
353362
}
354363
entries.push(candidate);
355364
}
@@ -437,3 +446,13 @@ export async function urlToWSEndpoint(progress: Progress|undefined, endpointURL:
437446
wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
438447
return wsUrl.toString();
439448
}
449+
450+
function multipartBoundary(headers: HeadersArray) {
451+
const contentType = headers.find(h => h.name.toLowerCase() === 'content-type');
452+
if (!contentType?.value.includes('multipart/form-data'))
453+
return undefined;
454+
const boundary = contentType.value.match(/boundary=(\S+)/);
455+
if (boundary)
456+
return boundary[1];
457+
return undefined;
458+
}

tests/library/browsercontext-har.spec.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,46 @@ it('should update har.zip for context', async ({ contextFactory, server }, testI
412412
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
413413
});
414414

415+
it('should ignore boundary when matching multipart/form-data body', {
416+
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31495' }
417+
}, async ({ contextFactory, server }, testInfo) => {
418+
server.setRoute('/empty.html', (req, res) => {
419+
res.setHeader('Content-Type', 'text/html');
420+
res.end(`
421+
<form id="form" action="form.html" enctype="multipart/form-data" method="POST">
422+
<input id="file" type="file" multiple />
423+
<button type="submit">Upload</button>
424+
</form>`);
425+
});
426+
server.setRoute('/form.html', (req, res) => {
427+
res.setHeader('Content-Type', 'text/html');
428+
res.end('<div>done</div>');
429+
});
430+
431+
const harPath = testInfo.outputPath('har.zip');
432+
const context1 = await contextFactory();
433+
await context1.routeFromHAR(harPath, { update: true });
434+
const page1 = await context1.newPage();
435+
await page1.goto(server.PREFIX + '/empty.html');
436+
const reqPromise = server.waitForRequest('/form.html');
437+
await page1.locator('button').click();
438+
await expect(page1.locator('div')).toHaveText('done');
439+
const req = await reqPromise;
440+
expect((await req.postBody).toString()).toContain('---');
441+
await context1.close();
442+
443+
const context2 = await contextFactory();
444+
await context2.routeFromHAR(harPath, { notFound: 'abort' });
445+
const page2 = await context2.newPage();
446+
await page2.goto(server.PREFIX + '/empty.html');
447+
const requestPromise = page2.waitForRequest(/.*form.html/);
448+
await page2.locator('button').click();
449+
const request = await requestPromise;
450+
expect.soft(await request.response()).toBeTruthy();
451+
expect(request.failure()).toBe(null);
452+
await expect(page2.locator('div')).toHaveText('done');
453+
});
454+
415455
it('should update har.zip for page', async ({ contextFactory, server }, testInfo) => {
416456
const harPath = testInfo.outputPath('har.zip');
417457
const context1 = await contextFactory();
@@ -428,7 +468,6 @@ it('should update har.zip for page', async ({ contextFactory, server }, testInfo
428468
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
429469
});
430470

431-
432471
it('should update har.zip for page with different options', async ({ contextFactory, server }, testInfo) => {
433472
const harPath = testInfo.outputPath('har.zip');
434473
const context1 = await contextFactory();

0 commit comments

Comments
 (0)