Skip to content

Commit aba8d9c

Browse files
committed
ui: update pdf link processing
1 parent 28286d6 commit aba8d9c

1 file changed

Lines changed: 23 additions & 10 deletions

File tree

packages/ui-default/backendlib/markdown-it-media.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
/* eslint-disable regexp/no-useless-non-capturing-group */
44
/* eslint-disable regexp/optimal-quantifier-concatenation */
55

6-
import _ from 'lodash';
76
import type MarkdownIt from 'markdown-it';
87
import { v4 as uuid } from 'uuid';
98

@@ -77,13 +76,22 @@ function resourceUrl(service: string, src: string, url: string) {
7776
if (service === 'vimeo') return `https://player.vimeo.com/video/${src}`;
7877
if (service === 'vine') return `https://vine.co/v/${src}/embed/simple`;
7978
if (service === 'prezi') {
80-
return `https://prezi.com/embed/${src}/?bgcolor=ffffff&lock_to_path=0&autoplay=0&autohide_ctrls=0&`
81-
+ 'landing_data=bHVZZmNaNDBIWnNjdEVENDRhZDFNZGNIUE43MHdLNWpsdFJLb2ZHanI5N1lQVHkxSHFxazZ0UUNCRHloSXZROHh3PT0&'
79+
return `https://prezi.com/embed/${src}/?bgcolor=ffffff&lock_to_path=0&autoplay=0&autohide_ctrls=0&`
80+
+ 'landing_data=bHVZZmNaNDBIWnNjdEVENDRhZDFNZGNIUE43MHdLNWpsdFJLb2ZHanI5N1lQVHkxSHFxazZ0UUNCRHloSXZROHh3PT0&'
8281
+ 'landing_sign=1kD6c0N6aYpMUS0wxnQjxzSqZlEB8qNFdxtdjYhwSuI';
8382
}
8483
return src;
8584
}
8685

86+
function pdfUrlKind(src: string) {
87+
const normalizedSrc = src.replace(/\s/g, '').replace(/\\/g, '/');
88+
if (/^data:application\/pdf(?:;[^,]*)?,/i.test(normalizedSrc)) return 'data';
89+
if (/^file:\/\//i.test(normalizedSrc)) return 'local';
90+
if (/^https?:\/\//i.test(normalizedSrc)) return 'external';
91+
if (normalizedSrc.startsWith('//') || /^[a-z][a-z\d+.-]*:/i.test(normalizedSrc)) return 'invalid';
92+
return 'local';
93+
}
94+
8795
declare module 'hydrooj' {
8896
interface ModuleInterfaces {
8997
richmedia: {
@@ -95,37 +103,42 @@ declare module 'hydrooj' {
95103
export function Media(md: MarkdownIt, { pdfToolbar = false }: { pdfToolbar?: boolean } = {}) {
96104
const supported = ['youtube', 'vimeo', 'vine', 'prezi', 'bilibili', 'youku', 'msoffice'];
97105
md.renderer.rules.video = function tokenizeReturn(tokens, idx) {
98-
let src = md.utils.escapeHtml(tokens[idx].attrGet('src'));
106+
const src = tokens[idx].attrGet('src');
99107
const service = tokens[idx].attrGet('service').replace(/[^A-Z0-9]/gi, '').toLowerCase();
100108
if (Hydro?.module?.richmedia?.[service]) {
101109
const result = Hydro?.module?.richmedia[service].get(service, src, md);
102110
if (result) return result;
103111
}
104112
if (service === 'pdf') {
105-
if (['file://', './', '../'].some((i) => src.startsWith(i)) || (src[1] === '/' && src[2] !== '/')) src += src.includes('?') ? '&noDisposition=1' : '?noDisposition=1';
113+
const kind = pdfUrlKind(src);
106114
// A response with has content-disposition header causes the browser to download the file automatically.
107115
// As we cannot control response header from external sites, we block embedding external PDFs.
108-
else return `<p>Embedding an external PDF is no longer supported.</p> <a href="${_.escape(src)}">Download</a>`;
116+
if (kind === 'external') return `<p>Embedding an external PDF is no longer supported.</p> <a href="${md.utils.escapeHtml(src)}">Download</a>`;
117+
if (kind === 'invalid') return '<p>Embedding this PDF URL is not supported.</p>';
118+
const fragmentPos = src.indexOf('#');
119+
const pdfUrl = fragmentPos === -1 ? src : src.slice(0, fragmentPos);
120+
const fragment = fragmentPos === -1 ? '' : src.slice(fragmentPos);
121+
const pdfSrc = kind === 'data' ? src : `${pdfUrl}${pdfUrl.includes('?') ? '&' : '?'}noDisposition=1${fragment}`;
109122
return `\
110123
<object classid="clsid:${uuid().toUpperCase()}">
111-
<param name="SRC" value="${src}">
112-
<embed type="application/pdf" width="100%" style="min-height:100vh;border:none;" fullscreen="yes" src="${src}#toolbar=${pdfToolbar ? '0' : '1'}&navpanes=0&view=FitH">
124+
<param name="SRC" value="${md.utils.escapeHtml(pdfSrc)}">
125+
<embed type="application/pdf" width="100%" style="min-height:100vh;border:none;" fullscreen="yes" src="${md.utils.escapeHtml(`${pdfSrc}#toolbar=${pdfToolbar ? '0' : '1'}&navpanes=0&view=FitH`)}">
113126
<noembed></noembed>
114127
</embed>
115128
</object>`;
116129
}
117130
if (['url', 'video'].includes(service)) {
118131
return `\
119132
<video width="100%" controls>
120-
<source src="${src}" type="${src.endsWith('ogg') ? 'video/ogg' : 'video/mp4'}">
133+
<source src="${md.utils.escapeHtml(src)}" type="${src.endsWith('ogg') ? 'video/ogg' : 'video/mp4'}">
121134
Your browser doesn't support video tag.
122135
</video>`;
123136
}
124137
if (supported.includes(service)) {
125138
return `\
126139
<iframe class="embed-responsive-item ${service}-player" type="text/html" \
127140
width="100%" style="min-height: 500px" ${allowFullScreen} \
128-
src="${resourceUrl(service, src, tokens[idx].attrGet('url'))}"
141+
src="${md.utils.escapeHtml(resourceUrl(service, src, tokens[idx].attrGet('url')))}"
129142
scrolling="no" border="0" frameborder="no" framespacing="0"></iframe>`;
130143
}
131144
return `<div data-${service}>${md.utils.escapeHtml(src)}</div>`;

0 commit comments

Comments
 (0)