fix(har): ignore boundary when matching multipart/form-data body (#31672)

Fixes https://github.com/microsoft/playwright/issues/31495
This commit is contained in:
Yury Semikhatsky 2024-07-12 16:59:48 -07:00 committed by GitHub
parent 459b762565
commit 1b4d9003c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 3 deletions

View File

@ -348,8 +348,17 @@ class HarBackend {
continue;
if (method === 'POST' && postData && candidate.request.postData) {
const buffer = await this._loadContent(candidate.request.postData);
if (!buffer.equals(postData))
if (!buffer.equals(postData)) {
const boundary = multipartBoundary(headers);
if (!boundary)
continue;
const candidataBoundary = multipartBoundary(candidate.request.headers);
if (!candidataBoundary)
continue;
// Try to match multipart/form-data ignroing boundary as it changes between requests.
if (postData.toString().replaceAll(boundary, '') !== buffer.toString().replaceAll(candidataBoundary, ''))
continue;
}
}
entries.push(candidate);
}
@ -437,3 +446,13 @@ export async function urlToWSEndpoint(progress: Progress|undefined, endpointURL:
wsUrl.protocol = wsUrl.protocol === 'https:' ? 'wss:' : 'ws:';
return wsUrl.toString();
}
function multipartBoundary(headers: HeadersArray) {
const contentType = headers.find(h => h.name.toLowerCase() === 'content-type');
if (!contentType?.value.includes('multipart/form-data'))
return undefined;
const boundary = contentType.value.match(/boundary=(\S+)/);
if (boundary)
return boundary[1];
return undefined;
}

View File

@ -412,6 +412,46 @@ it('should update har.zip for context', async ({ contextFactory, server }, testI
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
});
it('should ignore boundary when matching multipart/form-data body', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31495' }
}, async ({ contextFactory, server }, testInfo) => {
server.setRoute('/empty.html', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end(`
<form id="form" action="form.html" enctype="multipart/form-data" method="POST">
<input id="file" type="file" multiple />
<button type="submit">Upload</button>
</form>`);
});
server.setRoute('/form.html', (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end('<div>done</div>');
});
const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory();
await context1.routeFromHAR(harPath, { update: true });
const page1 = await context1.newPage();
await page1.goto(server.PREFIX + '/empty.html');
const reqPromise = server.waitForRequest('/form.html');
await page1.locator('button').click();
await expect(page1.locator('div')).toHaveText('done');
const req = await reqPromise;
expect((await req.postBody).toString()).toContain('---');
await context1.close();
const context2 = await contextFactory();
await context2.routeFromHAR(harPath, { notFound: 'abort' });
const page2 = await context2.newPage();
await page2.goto(server.PREFIX + '/empty.html');
const requestPromise = page2.waitForRequest(/.*form.html/);
await page2.locator('button').click();
const request = await requestPromise;
expect.soft(await request.response()).toBeTruthy();
expect(request.failure()).toBe(null);
await expect(page2.locator('div')).toHaveText('done');
});
it('should update har.zip for page', async ({ contextFactory, server }, testInfo) => {
const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory();
@ -428,7 +468,6 @@ it('should update har.zip for page', async ({ contextFactory, server }, testInfo
await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)');
});
it('should update har.zip for page with different options', async ({ contextFactory, server }, testInfo) => {
const harPath = testInfo.outputPath('har.zip');
const context1 = await contextFactory();