From 00a126aa532d32ee73c699ed92c5f6bd42fdbc17 Mon Sep 17 00:00:00 2001 From: Thomas Randolph Date: Wed, 10 Feb 2021 23:19:40 -0700 Subject: [PATCH 1/4] Add URL Utility for making safe-ER URL paths --- .../javascripts/lib/utils/url_utility.js | 30 +++++++++++++++++++ spec/frontend/lib/utils/url_utility_spec.js | 23 ++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 0b920ba8e7a8f0..2c9847dec0364b 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -16,6 +16,36 @@ 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. + * + * @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/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index 61df3aa19c2352..7812b2b7400fbd 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -880,4 +880,27 @@ describe('URL utility', () => { expect(urlUtils.getURLOrigin(url)).toBe(expectation); }); }); + + describe('encodeSaferUrl', () => { + it.each` + character | input | output + ${' '} | ${'/url/hello 1.jpg'} | ${'/url/hello%201.jpg'} + ${'#'} | ${'/url/hello#1.jpg'} | ${'/url/hello%231.jpg'} + ${'!'} | ${'/url/hello!.jpg'} | ${'/url/hello%21.jpg'} + ${'~'} | ${'/url/hello~.jpg'} | ${'/url/hello%7E.jpg'} + ${'*'} | ${'/url/hello*.jpg'} | ${'/url/hello%2A.jpg'} + ${"'"} | ${"/url/hello'.jpg"} | ${'/url/hello%27.jpg'} + ${'('} | ${'/url/hello(.jpg'} | ${'/url/hello%28.jpg'} + ${')'} | ${'/url/hello).jpg'} | ${'/url/hello%29.jpg'} + ${'?'} | ${'/url/hello?.jpg'} | ${'/url/hello%3F.jpg'} + ${'='} | ${'/url/hello=.jpg'} | ${'/url/hello%3D.jpg'} + ${'+'} | ${'/url/hello+.jpg'} | ${'/url/hello%2B.jpg'} + ${'&'} | ${'/url/hello&.jpg'} | ${'/url/hello%26.jpg'} + `( + 'properly escapes `$character` characters while retaining the integrity of the URL', + ({ input, output }) => { + expect(urlUtils.encodeSaferUrl(input)).toBe(output); + }, + ); + }); }); -- GitLab From 2cac4e4e6392f4ca2020e731a2ee56597b3b09c4 Mon Sep 17 00:00:00 2001 From: Thomas Randolph Date: Wed, 10 Feb 2021 23:20:08 -0700 Subject: [PATCH 2/4] Use encodeSaferURL utility to render image diff --- .../components/content_viewer/viewers/image_viewer.vue | 8 ++++++-- .../content_viewer/viewers/image_viewer_spec.js | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) 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 9ece6a52805df7..a49eb7fd6118ef 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 @@