사용자가 낸 영상 링크를 임베드할 때는 받은 URL을 그대로 src에 넣지 않는다
유튜브면 영상 id만 뽑아 youtube-nocookie 주소를 새로 만들고, 그 밖의 주소는 iframe 대신 새 창 링크로 엽니다. 저장할 때 scheme·호스트·사용자 정보부터 거릅니다.
제출물에 시연 영상 링크를 받아 심사 화면에 바로 재생되게 하려고 합니다. 가장 쉬운 구현은 받은 URL을 <iframe src>에 넣는 것인데, 그러면 사용자가 정한 아무 페이지나 우리 화면 안에 띄우게 됩니다. javascript:·data: 주소, youtube.com.evil.example처럼 이름만 흉내 낸 호스트, https://id:pw@host 꼴의 사용자 정보가 든 주소도 그대로 들어옵니다.
원인은 「사용자 입력을 주소로 쓰는 것」과 「알려진 서비스의 영상을 보여 주는 것」을 같은 일로 다룬 데 있습니다. 화면에 필요한 것은 URL 전체가 아니라 영상 id 하나입니다.
해법은 두 단계입니다. 저장할 때는 http(s)이고 호스트가 있으며 사용자 정보가 없는 주소만 받고, 나머지는 422로 거절합니다. 보여 줄 때는 호스트를 정확히 비교해 유튜브면 watch?v=·youtu.be/·shorts/·m. 모양에서 11자 id만 뽑아 임베드 주소를 새로 만듭니다. 쿠키를 덜 남기는 youtube-nocookie.com을 씁니다. 그 밖의 주소는 iframe에 넣지 않고 target="_blank" rel="noopener noreferrer" 링크로만 엽니다.
const ID = /^[A-Za-z0-9_-]{11}$/;
const YT_HOSTS = new Set(["youtube.com", "www.youtube.com", "m.youtube.com", "youtu.be"]);
export function youtubeEmbed(raw) {
let u;
try { u = new URL(raw); } catch { return null; }
if (!["https:", "http:"].includes(u.protocol) || u.username || u.password) return null;
if (!YT_HOSTS.has(u.hostname)) return null; // 흉내 호스트는 여기서 걸림
let id = null;
if (u.hostname === "youtu.be") id = u.pathname.slice(1);
else if (u.pathname === "/watch") id = u.searchParams.get("v");
else if (u.pathname.startsWith("/shorts/")) id = u.pathname.split("/")[2];
return id && ID.test(id) ? `https://www.youtube-nocookie.com/embed/${id}` : null;
}
특정 시각부터 재생하고 싶으면 원래 URL의 t=를 그대로 옮기지 말고, 초로 해석한 정수를 새 주소의 start= 매개변수로 붙입니다. 시험에는 정상 모양 넷과 함께 흉내 호스트·javascript:·사용자 정보·이상한 id를 넣어 모두 null이 나오는지 확인합니다.