Harden auth/vault path sanitization and clean WeKnora docs
This commit is contained in:
@@ -11,20 +11,65 @@ function uploadedAssetTitle(asset) {
|
||||
return String(asset && (asset.file_name || asset.title || asset.name) || '未命名附件').trim() || '未命名附件';
|
||||
}
|
||||
|
||||
/** Reject path escape segments and null bytes after light decode. */
|
||||
function hasPathEscape(value) {
|
||||
var s = String(value || '').replace(/\\/g, '/');
|
||||
try {
|
||||
s = decodeURIComponent(s);
|
||||
} catch (_) {
|
||||
// keep raw
|
||||
}
|
||||
s = s.replace(/\\/g, '/');
|
||||
if (s.indexOf('\0') >= 0) return true;
|
||||
var parts = s.split('/');
|
||||
for (var i = 0; i < parts.length; i += 1) {
|
||||
if (parts[i] === '..') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Allow only safe URL schemes for editor img/src or link href. */
|
||||
function isSafeAssetUrl(url) {
|
||||
var value = String(url || '').trim();
|
||||
if (!value) return false;
|
||||
if (value.charAt(0) === '#' || value.charAt(0) === '/' || value.indexOf('./') === 0) {
|
||||
return !hasPathEscape(value);
|
||||
}
|
||||
// scheme-relative
|
||||
if (value.indexOf('//') === 0) return false;
|
||||
var colon = value.indexOf(':');
|
||||
if (colon < 0) {
|
||||
// relative path without scheme
|
||||
return !hasPathEscape(value);
|
||||
}
|
||||
var scheme = value.slice(0, colon).toLowerCase();
|
||||
if (scheme === 'http' || scheme === 'https' || scheme === 'blob' || scheme === 'data') {
|
||||
// data: raster images only. Reject svg+xml (can embed script even when base64 hides "script").
|
||||
if (scheme === 'data') {
|
||||
if (!/^data:image\//i.test(value)) return false;
|
||||
if (/^data:image\/svg\+xml/i.test(value)) return false;
|
||||
// deny explicit script markers in non-svg image payloads
|
||||
if (value.toLowerCase().indexOf('script') >= 0) return false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function uploadedAssetUrl(asset) {
|
||||
return String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
|
||||
var raw = String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
|
||||
return isSafeAssetUrl(raw) ? raw : '';
|
||||
}
|
||||
|
||||
function uploadedAssetMarkdownHref(asset) {
|
||||
var href = String(asset && (asset.markdownHref || asset.markdown_href) || '').trim();
|
||||
if (href) {
|
||||
var normalizedHref = href.replace(/\\/g, '/');
|
||||
if (normalizedHref.indexOf('../') === 0 || normalizedHref.indexOf('/../') >= 0) return '';
|
||||
if (hasPathEscape(href) || !isSafeAssetUrl(href)) return '';
|
||||
return href;
|
||||
}
|
||||
var relativePath = String(asset && (asset.markdownRelativePath || asset.markdown_relative_path) || '').trim().replace(/\\/g, '/');
|
||||
if (!relativePath) return '';
|
||||
if (relativePath.indexOf('../') === 0 || relativePath.indexOf('/../') >= 0) return '';
|
||||
if (!relativePath || hasPathEscape(relativePath)) return '';
|
||||
if (relativePath.indexOf('./') === 0) return relativePath;
|
||||
return './' + relativePath;
|
||||
}
|
||||
@@ -621,9 +666,9 @@ async function uploadFilesWithResolvedTarget(files, detail, options, deps) {
|
||||
if (errors.length) {
|
||||
var message = '部分文件上传失败:\n' + errors.slice(0, 6).join('\n') + (errors.length > 6 ? '\n...' : '');
|
||||
if (typeof deps.alert === 'function') {
|
||||
deps.alert(message);
|
||||
await deps.alert(message);
|
||||
} else {
|
||||
window.alert(message);
|
||||
await window.mnote.alert(message);
|
||||
}
|
||||
}
|
||||
return uploaded;
|
||||
@@ -633,7 +678,7 @@ function localAssetOpenUrl(asset, download, context) {
|
||||
if (!isLocalUploadedAsset(asset)) return '';
|
||||
var rootUri = String(context && context.rootUri || asset && (asset.rootUri || asset.root_uri) || '').trim() || currentRootUri();
|
||||
var rootRelativePath = String(asset && (asset.rootRelativePath || asset.root_relative_path) || '').trim();
|
||||
if (!rootUri || !rootRelativePath) return '';
|
||||
if (!rootUri || !rootRelativePath || hasPathEscape(rootRelativePath)) return '';
|
||||
var url = new URL('/api/local-folder/files/open', window.location.origin);
|
||||
url.searchParams.set('rootUri', rootUri);
|
||||
url.searchParams.set('path', rootRelativePath);
|
||||
|
||||
Reference in New Issue
Block a user