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 f21d0da5a3.

Fixes: http://tracker.ceph.com/issues/26856

Signed-off-by: Tiago Melo <tmelo@suse.com>
This commit is contained in:
Tiago Melo 2018-08-03 17:58:21 +01:00
parent 0feccf0ca9
commit 45e645b770
4 changed files with 91 additions and 45 deletions

View File

@ -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');
});
});
});

View File

@ -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
};

View File

@ -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);
});
});

View File

@ -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;
};
}