diff --git a/guacamole/src/main/frontend/src/app/client/controllers/clientController.js b/guacamole/src/main/frontend/src/app/client/controllers/clientController.js index 2db800029..32aa2b1e5 100644 --- a/guacamole/src/main/frontend/src/app/client/controllers/clientController.js +++ b/guacamole/src/main/frontend/src/app/client/controllers/clientController.js @@ -239,7 +239,7 @@ angular.module('client').controller('clientController', ['$scope', '$routeParams ids.push(id); // Reconstruct path, updating attached clients via change in route - $location.path('/client/' + encodeURIComponent(ManagedClientGroup.getIdentifier(ids))); + $location.path('/client/' + ManagedClientGroup.getIdentifier(ids)); }; diff --git a/guacamole/src/main/frontend/src/app/navigation/types/ClientIdentifier.js b/guacamole/src/main/frontend/src/app/navigation/types/ClientIdentifier.js index bb16a823c..47af7adc5 100644 --- a/guacamole/src/main/frontend/src/app/navigation/types/ClientIdentifier.js +++ b/guacamole/src/main/frontend/src/app/navigation/types/ClientIdentifier.js @@ -100,6 +100,63 @@ angular.module('navigation').factory('ClientIdentifier', ['$injector', }; + /** + * Encodes the given value as base64url, a variant of base64 defined by + * RFC 4648: https://datatracker.ietf.org/doc/html/rfc4648#section-5. + * + * The "base64url" variant is identical to standard base64 except that it + * uses "-" instead of "+", "_" instead of "/", and padding with "=" is + * optional. + * + * @param {string} value + * The string value to encode. + * + * @returns {string} + * The provided string value encoded as unpadded base64url. + */ + var base64urlEncode = function base64urlEncode(value) { + + // Translate padded standard base64 to unpadded base64url + return $window.btoa(value).replace(/[+/=]/g, + (str) => ({ + '+' : '-', + '/' : '_', + '=' : '' + })[str] + ); + + }; + + /** + * Decodes the given base64url or base64 string. The input string may + * contain "=" padding characters, but this is not required. + * + * @param {string} value + * The base64url or base64 value to decode. + * + * @returns {string} + * The result of decoding the provided base64url or base64 string. + */ + var base64urlDecode = function base64urlDecode(value) { + + // Add any missing padding (standard base64 requires input strings to + // be multiples of 4 in length, padded using '=') + value += ([ + '', + '===', + '==', + '=' + ])[value.length % 4]; + + // Translate padded base64url to padded standard base64 + return $window.atob(value.replace(/[-_]/g, + (str) => ({ + '-' : '+', + '_' : '/' + })[str] + )); + }; + /** * Converts the given ClientIdentifier or ClientIdentifier-like object to * a String representation. Any object having the same properties as @@ -115,7 +172,7 @@ angular.module('navigation').factory('ClientIdentifier', ['$injector', * or ClientIdentifier-like object. */ ClientIdentifier.toString = function toString(id) { - return $window.btoa([ + return base64urlEncode([ id.id, id.type, id.dataSource @@ -137,7 +194,7 @@ angular.module('navigation').factory('ClientIdentifier', ['$injector', ClientIdentifier.fromString = function fromString(str) { try { - var values = $window.atob(str).split('\0'); + var values = base64urlDecode(str).split('\0'); return new ClientIdentifier({ id : values[0], type : values[1],