removed serialization, was a security risk
This commit is contained in:
@@ -2,5 +2,4 @@ export * from './src/Backend';
|
|||||||
export * from './src/Frontend';
|
export * from './src/Frontend';
|
||||||
export * from './src/Interfaces'
|
export * from './src/Interfaces'
|
||||||
export * from './src/Types';
|
export * from './src/Types';
|
||||||
export * from './src/Utils'
|
export * from './src/Utils'
|
||||||
export * from './src/Decorator'
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { CLASSNAME_ATTRIBUTE } from "./Strings"
|
|
||||||
|
|
||||||
export abstract class DeserializerFactory {
|
|
||||||
static entityClasses = {}
|
|
||||||
|
|
||||||
static from(object: any) {
|
|
||||||
if(!object){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof object !== 'object'){ //definitely not a class object
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Array.isArray(object)){
|
|
||||||
return object.map(DeserializerFactory.from)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clazz = DeserializerFactory.entityClasses[object[CLASSNAME_ATTRIBUTE]]
|
|
||||||
delete object[CLASSNAME_ATTRIBUTE]
|
|
||||||
|
|
||||||
if(!clazz){ //anonymous object or class not registered as @Serializable
|
|
||||||
Object.keys(object).forEach(key => {
|
|
||||||
object[key] = DeserializerFactory.from(object[key])
|
|
||||||
})
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
|
|
||||||
const obj = new clazz()
|
|
||||||
Object.keys(object).forEach(key => {
|
|
||||||
obj[key] = DeserializerFactory.from(object[key])
|
|
||||||
})
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static makeDeserializable(object: any){
|
|
||||||
if(!object)
|
|
||||||
return
|
|
||||||
|
|
||||||
if(typeof object !== 'object')
|
|
||||||
return object
|
|
||||||
|
|
||||||
if(object.constructor.name === 'Object'){
|
|
||||||
Object.keys(object).forEach(key => {
|
|
||||||
object[key] = DeserializerFactory.makeDeserializable(object[key])
|
|
||||||
})
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
|
|
||||||
object[CLASSNAME_ATTRIBUTE] = object.constructor.name
|
|
||||||
Object.keys(object).forEach(key => {
|
|
||||||
object[key] = DeserializerFactory.makeDeserializable(object[key])
|
|
||||||
})
|
|
||||||
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Serializable(attr?: any) {
|
|
||||||
return function _Serializable<T extends { new(...args: any[]): {} }>(clazz: T) {
|
|
||||||
DeserializerFactory.entityClasses[clazz.name] = clazz
|
|
||||||
|
|
||||||
return clazz
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-7
@@ -5,8 +5,6 @@ import * as T from './Types';
|
|||||||
import * as I from './Interfaces';
|
import * as I from './Interfaces';
|
||||||
import { stripAfterEquals, appendComma } from './Utils';
|
import { stripAfterEquals, appendComma } from './Utils';
|
||||||
import { CALLBACK_NAME, DESTROY_PREFIX, SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings';
|
import { CALLBACK_NAME, DESTROY_PREFIX, SOCKET_NOT_CONNECTED, UNKNOWN_RPC_IDENTIFIER, USER_DEFINED_TIMEOUT } from './Strings';
|
||||||
import { DeserializerFactory } from './Decorator';
|
|
||||||
DeserializerFactory
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,10 +207,9 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
|||||||
const headerArgs = fnArgs.join(",")
|
const headerArgs = fnArgs.join(",")
|
||||||
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||||
sesame = appendComma(sesame)
|
sesame = appendComma(sesame)
|
||||||
const deserializer = DeserializerFactory
|
|
||||||
return eval(`async (${headerArgs}) => {
|
return eval(`async (${headerArgs}) => {
|
||||||
const returnvalue = await this.call("${fnName}", ${sesame} ${argParams})
|
const returnvalue = await this.call("${fnName}", ${sesame} ${argParams})
|
||||||
return deserializer.from(returnvalue)
|
return returnvalue
|
||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +227,6 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
|||||||
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
const argParams = fnArgs.map(stripAfterEquals).join(",")
|
||||||
sesame = appendComma(sesame, true)
|
sesame = appendComma(sesame, true)
|
||||||
headerArgs = fnArgs.length > 0 ? headerArgs + "," : headerArgs
|
headerArgs = fnArgs.length > 0 ? headerArgs + "," : headerArgs
|
||||||
const deserializer = DeserializerFactory
|
|
||||||
const destroy_prefix = DESTROY_PREFIX
|
const destroy_prefix = DESTROY_PREFIX
|
||||||
|
|
||||||
const frontendHookStr = `
|
const frontendHookStr = `
|
||||||
@@ -248,10 +244,10 @@ export class RPCSocket<Ifc extends T.RPCInterface = T.RPCInterface> implements I
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.socket.hook(r.uuid, (...args) => {
|
this.socket.hook(r.uuid, (...args) => {
|
||||||
${CALLBACK_NAME}.apply(${CALLBACK_NAME}, args.map(deserializer.from))
|
${CALLBACK_NAME}.apply(${CALLBACK_NAME}, args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return deserializer.from(r.return)
|
return r.return
|
||||||
}else{
|
}else{
|
||||||
throw new Error("Empty response")
|
throw new Error("Empty response")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -4,7 +4,6 @@ import * as T from "./Types";
|
|||||||
import * as I from "./Interfaces";
|
import * as I from "./Interfaces";
|
||||||
import { Socket } from "socket.io"
|
import { Socket } from "socket.io"
|
||||||
import { CALL_NOT_FOUND, DESTROY_PREFIX, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings";
|
import { CALL_NOT_FOUND, DESTROY_PREFIX, RPC_BAD_TYPE, RPC_NO_NAME } from "./Strings";
|
||||||
import { DeserializerFactory } from "./Decorator";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate an RPC to RPCInfo for serialization.
|
* Translate an RPC to RPCInfo for serialization.
|
||||||
@@ -80,12 +79,11 @@ const callGenerator = (rpcName: string, $__socket__$: I.Socket, rpcFunction: T.G
|
|||||||
const argsArr = extractArgs(rpcFunction)
|
const argsArr = extractArgs(rpcFunction)
|
||||||
const args = argsArr.join(',')
|
const args = argsArr.join(',')
|
||||||
const argsStr = argsArr.map(stripAfterEquals).join(',')
|
const argsStr = argsArr.map(stripAfterEquals).join(',')
|
||||||
const deserializer = DeserializerFactory
|
|
||||||
|
|
||||||
const callStr = `async (${args}) => {
|
const callStr = `async (${args}) => {
|
||||||
try{
|
try{
|
||||||
const res = await rpcFunction(${argsStr})
|
const res = await rpcFunction(${argsStr})
|
||||||
return deserializer.makeDeserializable(res)
|
return res
|
||||||
}catch(e){
|
}catch(e){
|
||||||
errorHandler($__socket__$, e, rpcName, [${args}])
|
errorHandler($__socket__$, e, rpcName, [${args}])
|
||||||
}
|
}
|
||||||
@@ -108,7 +106,6 @@ export function stripAfterEquals(str: string): string {
|
|||||||
* @returns A {@link HookFunction}
|
* @returns A {@link HookFunction}
|
||||||
*/
|
*/
|
||||||
const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction, injectSocket?: boolean): T.HookInfo['generator'] => {
|
const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, sesameFn?: T.SesameFunction, injectSocket?: boolean): T.HookInfo['generator'] => {
|
||||||
const deserializer = DeserializerFactory
|
|
||||||
let argsArr = extractArgs(rpc.hook)
|
let argsArr = extractArgs(rpc.hook)
|
||||||
argsArr.shift()//remove callback param
|
argsArr.shift()//remove callback param
|
||||||
|
|
||||||
@@ -125,7 +122,6 @@ const hookGenerator = (rpc: T.HookRPC<any, any>, errorHandler: T.ErrorHandler, s
|
|||||||
const uuid = uuidv4()
|
const uuid = uuidv4()
|
||||||
const res = await rpc.hook((...cbargs) => {
|
const res = await rpc.hook((...cbargs) => {
|
||||||
${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
|
${rpc.onCallback ? `rpc.onCallback.apply({}, cbargs)` : ``}
|
||||||
cbargs = cbargs.map(deserializer.makeDeserializable)
|
|
||||||
$__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
|
$__socket__$.call.apply($__socket__$, [uuid, ...cbargs])
|
||||||
},${callArgs})
|
},${callArgs})
|
||||||
${rpc.onDestroy ? `$__socket__$.bind(destroy_prefix+uuid, () => {
|
${rpc.onDestroy ? `$__socket__$.bind(destroy_prefix+uuid, () => {
|
||||||
|
|||||||
+2
-124
@@ -1,5 +1,4 @@
|
|||||||
import { describe, it } from "mocha";
|
import { describe, it } from "mocha";
|
||||||
import { RPCServer, RPCSocket, Serializable } from '../Index'
|
|
||||||
import { RPCExporter, Socket } from "../src/Interfaces";
|
import { RPCExporter, Socket } from "../src/Interfaces";
|
||||||
import { ConnectedSocket, Callback, GenericFunction } from "../src/Types";
|
import { ConnectedSocket, Callback, GenericFunction } from "../src/Types";
|
||||||
import * as log from 'why-is-node-running';
|
import * as log from 'why-is-node-running';
|
||||||
@@ -10,6 +9,7 @@ import { PromiseIO } from "../src/PromiseIO/Server";
|
|||||||
import { PromiseIOClient } from "../src/PromiseIO/Client";
|
import { PromiseIOClient } from "../src/PromiseIO/Client";
|
||||||
import { assert, expect } from 'chai';
|
import { assert, expect } from 'chai';
|
||||||
import { CLASSNAME_ATTRIBUTE, USER_DEFINED_TIMEOUT } from "../src/Strings";
|
import { CLASSNAME_ATTRIBUTE, USER_DEFINED_TIMEOUT } from "../src/Strings";
|
||||||
|
import { RPCServer, RPCSocket } from "../Index";
|
||||||
var should = require('chai').should();
|
var should = require('chai').should();
|
||||||
var chai = require("chai");
|
var chai = require("chai");
|
||||||
var chaiAsPromised = require("chai-as-promised");
|
var chaiAsPromised = require("chai-as-promised");
|
||||||
@@ -1010,126 +1010,4 @@ describe("attaching handlers before connecting", () => {
|
|||||||
done(e)
|
done(e)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("class (de-)serialization", () => {
|
|
||||||
|
|
||||||
@Serializable()
|
|
||||||
class SubClass {
|
|
||||||
fString = "F"
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable()
|
|
||||||
class TestClass {
|
|
||||||
aString = "A"
|
|
||||||
aNumber = 46
|
|
||||||
aObject = {
|
|
||||||
x: "x",
|
|
||||||
y: undefined,
|
|
||||||
sub: new SubClass()
|
|
||||||
}
|
|
||||||
aClassObject = new SubClass()
|
|
||||||
|
|
||||||
public returnOK() {
|
|
||||||
return "OK"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const verifyObject = (obj: any) => {
|
|
||||||
expect(obj).to.be.an.instanceOf(TestClass)
|
|
||||||
expect(obj.aString).to.be.a('string')
|
|
||||||
expect(obj.aNumber).to.be.a('number')
|
|
||||||
expect(obj.aObject).to.be.a('object')
|
|
||||||
expect(obj.aObject.x).to.be.a('string')
|
|
||||||
expect(obj.aObject.y).to.be.undefined
|
|
||||||
expect(obj.aObject.sub).to.be.an.instanceOf(SubClass)
|
|
||||||
expect(obj.aClassObject).to.be.an.instanceOf(SubClass)
|
|
||||||
expect(obj).to.not.have.key(CLASSNAME_ATTRIBUTE)
|
|
||||||
expect(obj.aObject.sub).to.not.have.key(CLASSNAME_ATTRIBUTE)
|
|
||||||
expect(obj.aClassObject).to.not.have.key(CLASSNAME_ATTRIBUTE)
|
|
||||||
expect(obj.returnOK()).to.be.equal('OK')
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Responses", () => {
|
|
||||||
type TestIfc = {
|
|
||||||
Test: {
|
|
||||||
returnClass: () => Promise<TestClass>
|
|
||||||
classCallback: (callback: Callback<[TestClass]>) => Promise<TestClass>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let myServer: RPCServer<TestIfc>;
|
|
||||||
let mySocket: ConnectedSocket<TestIfc>;
|
|
||||||
|
|
||||||
before(function (done) {
|
|
||||||
myServer = new RPCServer<TestIfc>([{
|
|
||||||
name: "Test",
|
|
||||||
RPCs: [
|
|
||||||
async function returnClass() {
|
|
||||||
return new TestClass()
|
|
||||||
}, {
|
|
||||||
name: "classCallback",
|
|
||||||
hook: async function (callback) {
|
|
||||||
setTimeout(_ => callback(new TestClass()), 250)
|
|
||||||
return new TestClass()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}])
|
|
||||||
myServer.listen(8084)
|
|
||||||
|
|
||||||
new RPCSocket<TestIfc>(8084, 'localhost').connect().then(connsock => {
|
|
||||||
mySocket = connsock
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
after(function (done) {
|
|
||||||
mySocket.close()
|
|
||||||
myServer.close()
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
it("receives class object in call response", async () => {
|
|
||||||
const obj: TestClass = await mySocket['Test'].returnClass()
|
|
||||||
verifyObject(obj)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("receives class object in hook response", async function () {
|
|
||||||
const obj: TestClass = await mySocket.Test.classCallback(function(x){
|
|
||||||
x
|
|
||||||
})
|
|
||||||
verifyObject(obj)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("receives class object in callback", function (done) {
|
|
||||||
mySocket.Test.classCallback(function (cbValue) {
|
|
||||||
verifyObject(cbValue)
|
|
||||||
done()
|
|
||||||
}).then(verifyObject)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe("Parameters", () => {
|
|
||||||
it("Class object in call", function(done){
|
|
||||||
const server = new RPCServer([
|
|
||||||
{
|
|
||||||
name: "Test",
|
|
||||||
RPCs: [
|
|
||||||
function callWithClass(testObj: TestClass){
|
|
||||||
verifyObject(testObj)
|
|
||||||
done()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]).listen(8086)
|
|
||||||
|
|
||||||
new RPCSocket(8086, 'localhost').connect().then(sock => {
|
|
||||||
sock['Test'].callWithClass(new TestClass()).then(_ => {
|
|
||||||
sock.close()
|
|
||||||
server.close()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user