From 45e645b7701178ad3c711c6804df273e2907d9d3 Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Fri, 3 Aug 2018 17:58:21 +0100 Subject: [PATCH] mgr/dashboard: Add decorator to skip parameter encoding By enconding all parameters of api services we were also encoding parameters that were being sent in the body of the request. Those parameters don't need to be enconded and the server never decodes them. With this new decorator you can specify if you don't want a parameter to be enconded. This is a regression introduced in f21d0da5a3e19e3f9bbde084e71b4a09f8dcb0a1. Fixes: http://tracker.ceph.com/issues/26856 Signed-off-by: Tiago Melo --- .../src/app/shared/api/rbd.service.spec.ts | 8 -- .../src/app/shared/api/rbd.service.ts | 8 +- .../app/shared/decorators/cd-encode.spec.ts | 41 ++++++++++ .../src/app/shared/decorators/cd-encode.ts | 79 +++++++++++-------- 4 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.spec.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.spec.ts index 46127509eda..dcf2787685f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.spec.ts @@ -126,12 +126,4 @@ describe('RbdService', () => { const req = httpTesting.expectOne('api/block/image/poolName/rbdName/snap/snapshotName'); expect(req.request.method).toBe('DELETE'); }); - - describe('Encode decorator', () => { - it('should encode the imageName', () => { - service.get('poolName', 'rbd/name').subscribe(); - const req = httpTesting.expectOne('api/block/image/poolName/rbd%2Fname'); - expect(req.request.method).toBe('GET'); - }); - }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts index 9a466adf916..93decddec2a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { cdEncode } from '../decorators/cd-encode'; +import { cdEncode, cdEncodeNot } from '../decorators/cd-encode'; import { ApiModule } from './api.module'; @cdEncode @@ -47,7 +47,7 @@ export class RbdService { return this.http.get('api/block/image/default_features'); } - createSnapshot(poolName, rbdName, snapshotName) { + createSnapshot(poolName, rbdName, @cdEncodeNot snapshotName) { const request = { snapshot_name: snapshotName }; @@ -56,7 +56,7 @@ export class RbdService { }); } - renameSnapshot(poolName, rbdName, snapshotName, newSnapshotName) { + renameSnapshot(poolName, rbdName, snapshotName, @cdEncodeNot newSnapshotName) { const request = { new_snap_name: newSnapshotName }; @@ -65,7 +65,7 @@ export class RbdService { }); } - protectSnapshot(poolName, rbdName, snapshotName, isProtected) { + protectSnapshot(poolName, rbdName, snapshotName, @cdEncodeNot isProtected) { const request = { is_protected: isProtected }; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.spec.ts new file mode 100644 index 00000000000..49b504fd6a9 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.spec.ts @@ -0,0 +1,41 @@ +import { cdEncode, cdEncodeNot } from './cd-encode'; + +describe('cdEncode', () => { + @cdEncode + class ClassA { + x2: string; + y2: string; + + methodA(x1: string, @cdEncodeNot y1: string) { + this.x2 = x1; + this.y2 = y1; + } + } + + class ClassB { + x2: string; + y2: string; + + @cdEncode + methodB(x1: string, @cdEncodeNot y1: string) { + this.x2 = x1; + this.y2 = y1; + } + } + + const word = 'a+b/c-d'; + + it('should encode all params of ClassA, with exception of y1', () => { + const a = new ClassA(); + a.methodA(word, word); + expect(a.x2).toBe('a%2Bb%2Fc-d'); + expect(a.y2).toBe(word); + }); + + it('should encode all params of methodB, with exception of y1', () => { + const b = new ClassB(); + b.methodB(word, word); + expect(b.x2).toBe('a%2Bb%2Fc-d'); + expect(b.y2).toBe(word); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.ts index c2e4f9f1c28..c77be120f95 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/decorators/cd-encode.ts @@ -9,58 +9,71 @@ import * as _ from 'lodash'; * @param {Function} [target=null] * @returns {*} */ -export function cdEncode(target: Function = null): any { - if (target) { - encodeClass(target); +export function cdEncode(...args: any[]): any { + switch (args.length) { + case 1: + return encodeClass.apply(this, args); + case 3: + return encodeMethod.apply(this, args); + default: + throw new Error(); + } +} + +/** + * This decorator can be used in parameters only. + * It will exclude the parameter from being encode. + * This should be used in parameters that are going + * to be sent in the request's body. + * + * @export + * @param {Object} target + * @param {string} propertyKey + * @param {number} index + */ +export function cdEncodeNot(target: Object, propertyKey: string, index: number) { + const metadataKey = `__ignore_${propertyKey}`; + if (Array.isArray(target[metadataKey])) { + target[metadataKey].push(index); } else { - return encodeMethod(); + target[metadataKey] = [index]; } } function encodeClass(target: Function) { for (const propertyName of Object.keys(target.prototype)) { const descriptor = Object.getOwnPropertyDescriptor(target.prototype, propertyName); + const isMethod = descriptor.value instanceof Function; if (!isMethod) { continue; } - const originalMethod = descriptor.value; - descriptor.value = function(...args: any[]) { - args.forEach((arg, i, argsArray) => { - if (_.isString(arg)) { - argsArray[i] = encodeURIComponent(arg); - } - }); - - const result = originalMethod.apply(this, args); - return result; - }; - + encodeMethod(target.prototype, propertyName, descriptor); Object.defineProperty(target.prototype, propertyName, descriptor); } } -function encodeMethod() { - return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { - if (descriptor === undefined) { - descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); - } - const originalMethod = descriptor.value; +function encodeMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) { + if (descriptor === undefined) { + descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); + } + const originalMethod = descriptor.value; - descriptor.value = function() { - const args = []; + descriptor.value = function() { + const metadataKey = `__ignore_${propertyKey}`; + const indices: number[] = target[metadataKey] || []; + const args = []; - for (let i = 0; i < arguments.length; i++) { - if (_.isString(arguments[i])) { - args[i] = encodeURIComponent(arguments[i]); - } else { - args[i] = arguments[i]; - } + for (let i = 0; i < arguments.length; i++) { + if (_.isString(arguments[i]) && indices.indexOf(i) === -1) { + args[i] = encodeURIComponent(arguments[i]); + } else { + args[i] = arguments[i]; } + } - const result = originalMethod.apply(this, args); - return result; - }; + const result = originalMethod.apply(this, args); + return result; }; } -- 2.39.5