Skip to content

Commit f04fff4

Browse files
authored
Harden YouTube URL handling in embed feature (#599)
### Description This pull request refactors the `parseYoutubeLink` function in `ClientPreview.tsx` to use the standard `URL` API for parsing YouTube URLs. This change improves the reliability and maintainability of the code by handling edge cases and reducing manual string manipulation. #### Improvements to YouTube URL parsing - Refactored `parseYoutubeLink` to use the `URL` API for robust parsing of YouTube URLs, improving error handling and supporting more YouTube link formats. - Enhanced extraction of video ID, playlist, and timestamp by using `URL` search parameters instead of manual string splitting, making the code more readable and less error-prone. Fixes https://github.com/SableClient/Sable/security/code-scanning/9 #### Type of change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ### Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings ### AI disclosure: - [ ] Partially AI assisted (clarify which code was AI assisted and briefly explain what it does). - [ ] Fully AI generated (explain what all the generated code does in moderate detail). <!-- Write any explanation required here, but do not generate the explanation using AI!! You must prove you understand what the code in this PR does. --> No AI involved
2 parents 82f9c8d + 4048d4d commit f04fff4

1 file changed

Lines changed: 35 additions & 18 deletions

File tree

src/app/components/url-preview/ClientPreview.tsx

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -156,31 +156,48 @@ type YoutubeLink = {
156156
isMusic: boolean;
157157
};
158158

159-
function parseYoutubeLink(url: string): YoutubeLink | null {
160-
const urlsplit = url.split('/');
161-
const path = urlsplit[urlsplit.length - 1];
159+
function parseYoutubeLink(url: Readonly<string>): YoutubeLink | null {
160+
/**
161+
* the parsed version of `url`
162+
*/
163+
let parsedURL: URL;
164+
try {
165+
parsedURL = new URL(url);
166+
} catch {
167+
// new URL can throw
168+
return null;
169+
}
170+
const urlHost = parsedURL.host;
171+
const urlSearchParams = parsedURL.searchParams;
162172

173+
/**
174+
* The id of the youtube video, for example `MTn_bhTVr2U`
175+
*/
163176
let videoId: string | undefined;
164-
let params: string[];
165-
166-
if (url.includes('youtu.be')) {
167-
const split = path.split('?');
168-
[videoId] = split;
169-
params = split[1]?.split('&');
170-
} else if (url.includes('/shorts/')) {
171-
const split = path.split('?');
172-
[videoId] = split;
173-
params = split[1]?.split('&') ?? [];
174-
} else if (url.includes('youtube.com')) {
175-
params = path.split('?')[1]?.split('&') ?? [];
176-
videoId = params.find((s) => s.startsWith('v='))?.split('v=')[1];
177+
178+
if (urlHost === 'youtu.be' || urlHost.endsWith('.youtu.be')) {
179+
// example https://youtu.be/MTn_bhTVr2U?si=xxxx
180+
// pathname includes the leading `/` so we have to split that
181+
videoId = parsedURL.pathname.slice(1);
182+
} else if (parsedURL.pathname.startsWith('/shorts/')) {
183+
// example https://youtube.com/shorts/R0KZIPOqITw?si=xxxx
184+
videoId = parsedURL.pathname.split('/').findLast(Boolean);
185+
} else if (
186+
(urlHost === 'youtube.com' || urlHost.endsWith('.youtube.com')) &&
187+
parsedURL.pathname === '/watch'
188+
) {
189+
// example: https://www.youtube.com/watch?v=MTn_bhTVr2U&list=RDjcB4zu4KX10&index=3
190+
// get returns null if `v` is not in the url
191+
videoId = urlSearchParams.get('v') ?? undefined;
177192
} else return null;
178193

179194
if (!videoId) return null;
180195

181196
// playlist is not used for the embed, it can be appended as is
182-
const playlist = params ? params.find((s) => s.startsWith('list=')) : undefined;
183-
const timestamp = params ? params.find((s) => s.startsWith('t='))?.split('t=')[1] : undefined;
197+
// returns null if `list` doesn't exist
198+
const playlist = urlSearchParams.get('list') ?? undefined;
199+
// returns null if `t` doesn't exist
200+
const timestamp = urlSearchParams.get('t') ?? urlSearchParams.get('start') ?? undefined;
184201

185202
return {
186203
videoId,

0 commit comments

Comments
 (0)