mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
* fix: URL-encode pad names in admin 'Open' button and recent pads (#7865) - encodeURIComponent in admin PadPage 'Open' button href - decodeURIComponent when reading pad name from URL pathname in pad_userlist.ts and colibris/pad.js (recent pads storage) - encodeURIComponent in colibris/index.js recent pads href; display text uses stored name directly (no double-decode) - add recent_pads spec asserting encoded URLs - add share dialog spec asserting URL encoding of special chars * fix: address Qodo review on recent-pads encoding and admin Open button - Normalize legacy URL-encoded recentPads names before re-encoding the href in colibris/index.js, preventing double-encoding (%2F -> %252F) of entries stored by older versions. - Add noopener,noreferrer to the admin 'Open' window.open call to prevent reverse tabnabbing, matching the pattern used elsewhere. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3e6f9d7bb2
commit
189ea4a29b
6 changed files with 57 additions and 5 deletions
|
|
@ -418,7 +418,7 @@ export const PadPage = () => {
|
|||
</button>
|
||||
<button
|
||||
className="pm-btn pm-btn-primary pm-btn--sm"
|
||||
onClick={() => window.open(`../../p/${pad.padName}`, '_blank')}
|
||||
onClick={() => window.open(`../../p/${encodeURIComponent(pad.padName)}`, '_blank', 'noopener,noreferrer')}
|
||||
>
|
||||
<Eye size={13}/> <Trans i18nKey="admin_pads.open"/>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -557,7 +557,7 @@ const paduserlist = (() => {
|
|||
if (localStorage.getItem('recentPads') != null) {
|
||||
const recentPadsList = JSON.parse(localStorage.getItem('recentPads'));
|
||||
const pathSegments = window.location.pathname.split('/');
|
||||
const padName = pathSegments[pathSegments.length - 1];
|
||||
const padName = decodeURIComponent(pathSegments[pathSegments.length - 1]);
|
||||
const existingPad = recentPadsList.find((pad) => pad.name === padName);
|
||||
if (existingPad) {
|
||||
existingPad.members = online;
|
||||
|
|
|
|||
|
|
@ -70,12 +70,21 @@ window.customStart = () => {
|
|||
li.style.cursor = 'pointer';
|
||||
|
||||
li.className = 'recent-pad';
|
||||
const padPath = `${window.location.href}p/${pad.name}`;
|
||||
// Normalize the stored name to its decoded form. Entries saved by older
|
||||
// versions may already be URL-encoded; decoding first avoids
|
||||
// double-encoding (e.g. %2F -> %252F) when we build the href below.
|
||||
let padName = pad.name;
|
||||
try {
|
||||
padName = decodeURIComponent(pad.name);
|
||||
} catch {
|
||||
// pad.name is not valid percent-encoding; use it as-is.
|
||||
}
|
||||
const padPath = `${window.location.href}p/${encodeURIComponent(padName)}`;
|
||||
const link = document.createElement('a');
|
||||
link.style.textDecoration = 'none';
|
||||
|
||||
link.href = padPath;
|
||||
link.innerText = pad.name;
|
||||
link.innerText = padName;
|
||||
li.appendChild(link);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ window.customStart = () => {
|
|||
$('.buttonicon').on('mouseup', function () { $(this).parent().removeClass('pressed'); });
|
||||
|
||||
const pathSegments = window.location.pathname.split('/');
|
||||
const padName = pathSegments[pathSegments.length - 1];
|
||||
const padName = decodeURIComponent(pathSegments[pathSegments.length - 1]);
|
||||
const recentPads = localStorage.getItem('recentPads');
|
||||
if (recentPads == null) {
|
||||
localStorage.setItem('recentPads', JSON.stringify([]));
|
||||
|
|
|
|||
|
|
@ -133,4 +133,17 @@ test.describe('embed links', function () {
|
|||
await checkiFrameCode(embedCode, true, page);
|
||||
});
|
||||
})
|
||||
|
||||
test.describe('URL encoding of pad names', function () {
|
||||
test('share link encodes special characters in pad name', async function ({page}) {
|
||||
const padName = 'test%2Fencoding';
|
||||
await page.goto(`http://localhost:9001/p/${padName}`);
|
||||
|
||||
const shareButton = page.locator('.buttonicon-embed');
|
||||
await shareButton.click();
|
||||
|
||||
const shareLink = await page.locator('#linkinput').inputValue();
|
||||
expect(shareLink).toContain(padName);
|
||||
});
|
||||
})
|
||||
})
|
||||
|
|
|
|||
30
src/tests/frontend-new/specs/recent_pads.spec.ts
Normal file
30
src/tests/frontend-new/specs/recent_pads.spec.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Recent Pads', () => {
|
||||
test('should display correctly encoded URLs for recent pads', async ({ page }) => {
|
||||
const padName = 'test pad with spaces & / chars';
|
||||
const recentPads = [
|
||||
{
|
||||
name: padName,
|
||||
timestamp: new Date().toISOString(),
|
||||
members: 1,
|
||||
},
|
||||
];
|
||||
|
||||
await page.addInitScript((data) => {
|
||||
window.localStorage.setItem('recentPads', data);
|
||||
}, JSON.stringify(recentPads));
|
||||
|
||||
await page.goto('http://localhost:9001/');
|
||||
|
||||
const recentPad = page.locator('.recent-pad').first();
|
||||
await expect(recentPad).toBeVisible();
|
||||
|
||||
const link = recentPad.locator('a');
|
||||
await expect(link).toHaveText(padName);
|
||||
|
||||
const expectedEncodedName = encodeURIComponent(padName);
|
||||
const expectedHrefRegex = new RegExp(`p/${expectedEncodedName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
|
||||
await expect(link).toHaveAttribute('href', expectedHrefRegex);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue