Przeglądaj źródła

Use URI Encoding to encode Base 64 query string

In rare occasions, when using special characters in a string (ex.: ü),
the Base 64 value contains the '/' character, which is not URL query
string friendly and was previously not URI encoded.

This PR makes sure that every time a Base 64 value is used in a query
string, it's also URI encoded to make sure '/' character is not part of
the query string.

Please note that the server must decode (deqoute) the query string
before decoding its Base 64 value.
Sergiu Miclea 5 lat temu
rodzic
commit
3f679d5909

+ 2 - 1
package.json

@@ -60,6 +60,7 @@
     "file-saver": "^2.0.2",
     "fs": "^0.0.1-security",
     "html-webpack-plugin": "^3.2.0",
+    "js-base64": "^3.5.2",
     "js-cookie": "^2.2.1",
     "jszip": "^3.5.0",
     "lodash": "^4.17.19",
@@ -96,4 +97,4 @@
     "node-fetch": "^2.6.1",
     "yargs-parser": "^13.1.2"
   }
-}
+}

+ 3 - 2
src/components/pages/AssessmentDetailsPage/AssessmentDetailsPage.tsx

@@ -41,6 +41,7 @@ import assessmentStore from '../../../stores/AssessmentStore'
 import providerStore from '../../../stores/ProviderStore'
 
 import assessmentImage from './images/assessment.svg'
+import DomUtils from '../../../utils/DomUtils'
 
 const Wrapper = styled.div<any>``
 
@@ -71,7 +72,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
 
   UNSAFE_componentWillMount() {
     document.title = 'Assessment Details'
-    const urlData: LocalData = JSON.parse(atob(decodeURIComponent(this.props.match.params.info)))
+    const urlData: LocalData = DomUtils.decodeFromBase64Url(this.props.match.params.info)
     if (!azureStore.loadLocalData(urlData.assessmentName)) {
       azureStore.setLocalData(urlData)
     }
@@ -91,7 +92,7 @@ class AssessmentDetailsPage extends React.Component<Props, State> {
   }
 
   getUrlInfo() {
-    return JSON.parse(atob(decodeURIComponent(this.props.match.params.info)))
+    return DomUtils.decodeFromBase64Url(this.props.match.params.info)
   }
 
   getSourceEndpoints() {

+ 2 - 1
src/components/pages/AssessmentsPage/AssessmentsPage.tsx

@@ -32,6 +32,7 @@ import projectStore from '../../../stores/ProjectStore'
 import userStore from '../../../stores/UserStore'
 import configLoader from '../../../utils/Config'
 import DropdownLink from '../../molecules/DropdownLink/DropdownLink'
+import DomUtils from '../../../utils/DomUtils'
 
 const Wrapper = styled.div<any>``
 
@@ -142,7 +143,7 @@ class AssessmentsPage extends React.Component<Props, State> {
       endpoint, connectionInfo, resourceGroupName, projectName, groupName, assessmentName,
     }
 
-    this.props.history.push(`/assessment/${encodeURIComponent(btoa(JSON.stringify({ ...info })))}`)
+    this.props.history.push(`/assessment/${DomUtils.encodeToBase64Url({ ...info })}`)
   }
 
   handleProjectChange() {

+ 2 - 1
src/sources/AzureSource.ts

@@ -16,6 +16,7 @@ import moment from 'moment'
 
 import Api from '../utils/ApiCaller'
 import type { Assessment, VmItem, VmSize } from '../@types/Assessment'
+import DomUtils from '../utils/DomUtils'
 
 const azureUrl = 'https://management.azure.com/'
 const defaultApiVersion = '2019-10-01'
@@ -29,7 +30,7 @@ const assessedVmsUrl = ({ ...other }) => `${assessmentDetailsUrl({ ...other })}/
 
 class Util {
   static buildUrl(baseUrl: string, apiVersion?: string): string {
-    const url = `/proxy/${btoa(`${azureUrl + baseUrl}?api-version=${apiVersion || defaultApiVersion}`)}`
+    const url = `/proxy/${DomUtils.encodeToBase64Url(`${azureUrl + baseUrl}?api-version=${apiVersion || defaultApiVersion}`)}`
     return url
   }
 

+ 2 - 1
src/sources/EndpointSource.ts

@@ -21,6 +21,7 @@ import ObjectUtils from '../utils/ObjectUtils'
 import type { Endpoint, Validation, Storage } from '../@types/Endpoint'
 
 import configLoader from '../utils/Config'
+import DomUtils from '../utils/DomUtils'
 
 const getBarbicanPayload = (data: any) => ({
   payload: JSON.stringify(data),
@@ -273,7 +274,7 @@ class EndpointSource {
   }
 
   async loadStorage(endpointId: string, data: any): Promise<Storage> {
-    const env = btoa(JSON.stringify(data))
+    const env = DomUtils.encodeToBase64Url(data)
     const response = await Api.get(`${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/storage?env=${env}`)
     return response.data.storage
   }

+ 4 - 3
src/sources/InstanceSource.ts

@@ -19,6 +19,7 @@ import configLoader from '../utils/Config'
 
 import { InstanceInfoPlugin } from '../plugins/endpoint'
 import { ProviderTypes } from '../@types/Providers'
+import DomUtils from '../utils/DomUtils'
 
 class InstanceSource {
   async loadInstancesChunk(
@@ -56,7 +57,7 @@ class InstanceSource {
     if (env) {
       queryParams = {
         ...queryParams,
-        env: btoa(JSON.stringify(env)),
+        env: DomUtils.encodeToBase64Url(env),
       }
     }
 
@@ -87,9 +88,9 @@ class InstanceSource {
     const {
       endpointId, instanceName, targetProvider, reqId, quietError, env, cache, skipLog,
     } = opts
-    let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances/${btoa(instanceName)}`
+    let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances/${DomUtils.encodeToBase64Url(instanceName)}`
     if (env) {
-      url += `?env=${btoa(JSON.stringify(env))}`
+      url += `?env=${DomUtils.encodeToBase64Url(env)}`
     }
     const response = await Api.send({
       url,

+ 2 - 1
src/sources/NetworkSource.ts

@@ -16,6 +16,7 @@ import Api from '../utils/ApiCaller'
 import type { Network } from '../@types/Network'
 
 import configLoader from '../utils/Config'
+import DomUtils from '../utils/DomUtils'
 
 class NetworkSource {
   async loadNetworks(enpointId: string, environment: { [prop: string]: any } | null, options?: {
@@ -24,7 +25,7 @@ class NetworkSource {
   }): Promise<Network[]> {
     let url = `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/endpoints/${enpointId}/networks`
     if (environment) {
-      url = `${url}?env=${btoa(JSON.stringify(environment))}`
+      url = `${url}?env=${DomUtils.encodeToBase64Url(environment)}`
     }
     const response = await Api.send({
       url,

+ 2 - 1
src/sources/ProviderSource.ts

@@ -19,6 +19,7 @@ import { SchemaParser } from './Schemas'
 import type { Field } from '../@types/Field'
 import type { Providers, ProviderTypes } from '../@types/Providers'
 import type { OptionValues } from '../@types/Endpoint'
+import DomUtils from '../utils/DomUtils'
 
 class ProviderSource {
   async getConnectionInfoSchema(providerName: ProviderTypes): Promise<Field[]> {
@@ -63,7 +64,7 @@ class ProviderSource {
   ): Promise<OptionValues[]> {
     let envString = ''
     if (envData) {
-      envString = `?env=${btoa(JSON.stringify(envData))}`
+      envString = `?env=${DomUtils.encodeToBase64Url(envData)}`
     }
     const callName = optionsType === 'source' ? 'source-options' : 'destination-options'
     const fieldName = optionsType === 'source' ? 'source_options' : 'destination_options'

+ 2 - 2
src/sources/WizardSource.ts

@@ -139,14 +139,14 @@ class WizardSource {
       return
     }
     const location = locationExp[0].replace('?', '')
-    window.history.replaceState({}, '', `${location}?d=${btoa(JSON.stringify(data))}`)
+    window.history.replaceState({}, '', `${location}?d=${DomUtils.encodeToBase64Url(data)}`)
   }
 
   getUrlState() {
     const dataExpExec = /\?d=(.*)/.exec(window.location.href)
     let result = null
     try {
-      result = dataExpExec && JSON.parse(atob(dataExpExec[1]))
+      result = dataExpExec && DomUtils.decodeFromBase64Url(dataExpExec[1])
     } catch (err) {
       console.error(err)
     }

+ 17 - 0
src/utils/DomUtils.ts

@@ -12,6 +12,8 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { Base64 } from 'js-base64'
+
 class DomUtils {
   static getScrollableParent(
     element: HTMLElement, includeHidden?: boolean,
@@ -151,6 +153,21 @@ class DomUtils {
   static isSafari() {
     return navigator.userAgent.indexOf('Chrome') === -1 && navigator.userAgent.indexOf('Safari') > -1
   }
+
+  static encodeToBase64Url(data: any) {
+    let dataStr: string
+    if (typeof data === 'string') {
+      dataStr = data
+    } else {
+      dataStr = JSON.stringify(data)
+    }
+
+    return Base64.encode(dataStr).replace(/\+/g, '-').replace(/\//g, '_')
+  }
+
+  static decodeFromBase64Url(data: string) {
+    return JSON.parse(Base64.decode(data.replace(/-/g, '+').replace(/_/g, '/')))
+  }
 }
 
 export default DomUtils

+ 5 - 0
yarn.lock

@@ -7680,6 +7680,11 @@ js-base64@^2.1.9:
   version "2.3.2"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf"
 
+js-base64@^3.5.2:
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.5.2.tgz#3cc800e4f10812b55fb5ec53e7cabaef35dc6d3c"
+  integrity sha512-VG2qfvV5rEQIVxq9UmAVyWIaOdZGt9M16BLu8vFkyWyhv709Hyg4nKUb5T+Ru+HmAr9RHdF+kQDKAhbJlcdKeQ==
+
 js-cookie@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"