diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 0b920ba8e7a8f05af1d3ae0863c7b1bec1d409f3..cc2cf787a8f9e8b84dda7b1e058ba0d4c73bcd49 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -16,6 +16,50 @@ function decodeUrlParameter(val) { return decodeURIComponent(val.replace(/\+/g, '%20')); } +/** + * Safely encodes a string to be used as a path + * + * Note: This function DOES encode typical URL parts like ?, =, &, #, and + + * If you need to use search parameters or URL fragments, they should be + * added AFTER calling this function, not before. + * + * Usecase: An image filename is stored verbatim, and you need to load it in + * the browser. + * + * Example: /some_path/file #1.jpg ==> /some_path/file%20%231.jpg + * Example: /some-path/file! Final!.jpg ==> /some-path/file%21%20Final%21.jpg + * + * Essentially, if a character *could* present a problem in a URL, it's escaped + * to the hexadecimal representation instead. This means it's a bit more + * aggressive than encodeURIComponent: that built-in function doesn't + * encode some characters that *could* be problematic, so this function + * adds them (#, !, ~, *, ', (, and )). + * Additionally, encodeURIComponent *does* encode `/`, but we want safer + * URLs, not non-functional URLs, so this function DEcodes slashes ('%2F'). + * + * @param {String} potentiallyUnsafePath + * @returns {String} + */ +export function encodeSaferUrl(potentiallyUnsafePath) { + const unencode = ['%2F']; + const encode = ['#', '!', '~', '\\*', "'", '\\(', '\\)']; + let saferPath = encodeURIComponent(potentiallyUnsafePath); + + unencode.forEach((code) => { + saferPath = saferPath.replace(new RegExp(code, 'g'), decodeURIComponent(code)); + }); + encode.forEach((code) => { + const encodedValue = code + .codePointAt(code.length - 1) + .toString(16) + .toUpperCase(); + + saferPath = saferPath.replace(new RegExp(code, 'g'), `%${encodedValue}`); + }); + + return saferPath; +} + export function cleanLeadingSeparator(path) { return path.replace(PATH_SEPARATOR_LEADING_REGEX, ''); } diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue index 9ece6a52805df7fb9726d0ecde7dc2cf27c7b5e7..a49eb7fd6118effcada2b2bed0191f2472784762 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue @@ -1,6 +1,7 @@