From 86a69f4f9547b48446fc314f91aff7d33f60cce4 Mon Sep 17 00:00:00 2001 From: Pierre Cauchois Date: Wed, 16 May 2018 12:51:32 +0200 Subject: [PATCH] fix(service): URI-encode device id for method invocation (fix #289) --- service/devdoc/device_method_requirements.md | 18 +++++++++------- service/src/device_method.ts | 3 ++- service/test/_device_method_test.js | 22 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/service/devdoc/device_method_requirements.md b/service/devdoc/device_method_requirements.md index dd510317c..5c8bffaa0 100644 --- a/service/devdoc/device_method_requirements.md +++ b/service/devdoc/device_method_requirements.md @@ -34,28 +34,28 @@ rebootMethod.invokeOn('deviceId', function(err, result, response) { ### DeviceMethod(params, restApiClient) [Constructor] The `DeviceMethod` constructor initializes a new instance of a `DeviceMethod` object. -**SRS_NODE_IOTHUB_DEVICE_METHOD_16_004: [** The `DeviceMethod` constructor shall throw a `ReferenceError` if `params.methodName` is `null`, `undefined` or an empty string. **]** +**SRS_NODE_IOTHUB_DEVICE_METHOD_16_004: [** The `DeviceMethod` constructor shall throw a `ReferenceError` if `params.methodName` is `null`, `undefined` or an empty string. **]** -**SRS_NODE_IOTHUB_DEVICE_METHOD_16_005: [** The `DeviceMethod` constructor shall throw a `TypeError` if `params.methodName` is not a `string`. **]** +**SRS_NODE_IOTHUB_DEVICE_METHOD_16_005: [** The `DeviceMethod` constructor shall throw a `TypeError` if `params.methodName` is not a `string`. **]** **SRS_NODE_IOTHUB_DEVICE_METHOD_16_013: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params` property to the `params` argument value. **]** -**SRS_NODE_IOTHUB_DEVICE_METHOD_16_006: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.responseTimeoutInSeconds` property value to the `params.responseTimeoutInSeconds` argument value or to the default (`30`) if the `responseTimeoutInSeconds` value is falsy. **]** +**SRS_NODE_IOTHUB_DEVICE_METHOD_16_006: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.responseTimeoutInSeconds` property value to the `params.responseTimeoutInSeconds` argument value or to the default (`30`) if the `responseTimeoutInSeconds` value is falsy. **]** **SRS_NODE_IOTHUB_DEVICE_METHOD_16_016: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.connectTimeoutInSeconds` property value to the `params.connectTimeoutInSeconds` argument value or to the default (`0`) if the `connectTimeoutInSeconds` value is falsy. **]** -**SRS_NODE_IOTHUB_DEVICE_METHOD_16_015: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.payload` property value to the `params.payload` argument value or to the default (`null`) if the `payload` argument is `null` or `undefined`. **]** +**SRS_NODE_IOTHUB_DEVICE_METHOD_16_015: [** The `DeviceMethod` constructor shall set the `DeviceMethod.params.payload` property value to the `params.payload` argument value or to the default (`null`) if the `payload` argument is `null` or `undefined`. **]** ### invokeOn(deviceId, done) The `invokeOn` uses the IoT Hub service to run a method on a device and returns the result. The payload will be sent to the device and act as method arguments. The `timeoutInSeconds` value is expressed in seconds and indicates how much time a method should wait for a result before timing out, once the connection between the service and the device has been established. -**SRS_NODE_IOTHUB_DEVICE_METHOD_16_008: [** The `invokeOn` method shall throw a `ReferenceError` if `deviceId` is `null`, `undefined` or an empty string. **]** +**SRS_NODE_IOTHUB_DEVICE_METHOD_16_008: [** The `invokeOn` method shall throw a `ReferenceError` if `deviceId` is `null`, `undefined` or an empty string. **]** **SRS_NODE_IOTHUB_DEVICE_METHOD_16_011: [** The `invokeOn` method shall construct an HTTP request using information supplied by the caller, as follows: ``` POST /twins//methods?api-version= HTTP/1.1 -Authorization: +Authorization: Content-Type: application/json; charset=utf-8 Request-Id: { @@ -65,8 +65,10 @@ Request-Id: "payload": } ``` -**]** +**]** -**SRS_NODE_IOTHUB_DEVICE_METHOD_16_009: [** The `invokeOn` method shall invoke the `done` callback with a standard javascript `Error` object if the method execution failed. **]** +**SRS_NODE_IOTHUB_DEVICE_METHOD_16_017: [** The `invokeOn` method shall uri-encode the device id. **]** + +**SRS_NODE_IOTHUB_DEVICE_METHOD_16_009: [** The `invokeOn` method shall invoke the `done` callback with a standard javascript `Error` object if the method execution failed. **]** **SRS_NODE_IOTHUB_DEVICE_METHOD_16_010: [** The `invokeOn` method shall invoke the `done` callback with a `null` first argument, a result second argument and a transport-specific response third argument if the method execution succeeded. **]** \ No newline at end of file diff --git a/service/src/device_method.ts b/service/src/device_method.ts index 4a8b68f42..b8fdb7b60 100644 --- a/service/src/device_method.ts +++ b/service/src/device_method.ts @@ -63,7 +63,8 @@ export class DeviceMethod { /*Codes_SRS_NODE_IOTHUB_DEVICE_METHOD_16_008: [The `invokeOn` method shall throw a `ReferenceError` if `deviceId` is `null`, `undefined` or an empty string.]*/ if (deviceId === null || deviceId === undefined || deviceId === '') throw new ReferenceError('deviceId cannot be \'' + deviceId + '\''); - const path = '/twins/' + deviceId + '/methods' + endpoint.versionQueryString(); + /*Codes_SRS_NODE_IOTHUB_DEVICE_METHOD_16_017: [The `invokeOn` method shall uri-encode the device id.]*/ + const path = '/twins/' + encodeURIComponent(deviceId) + '/methods' + endpoint.versionQueryString(); const headers = { 'Content-Type': 'application/json; charset=utf-8' }; diff --git a/service/test/_device_method_test.js b/service/test/_device_method_test.js index 8cd84b58a..c93c7db44 100644 --- a/service/test/_device_method_test.js +++ b/service/test/_device_method_test.js @@ -160,6 +160,28 @@ describe('DeviceMethod', function() { method.invokeOn(fakeDeviceId, testCallback); }); + /*Tests_SRS_NODE_IOTHUB_DEVICE_METHOD_16_017: [The `invokeOn` method shall uri-encode the device id.]*/ + it('URI-encodes the device id', function(testCallback) { + var fakeMethodParams = { + methodName: 'method', + payload: { foo: 'bar' }, + responseTimeoutInSeconds: 42 + }; + + var fakeDeviceId = 'device#'; + var uriEncodedDeviceId = encodeURIComponent(fakeDeviceId); + + var fakeRestClient = { + executeApiCall: function(method, path, headers, body, timeout, callback) { + assert.equal(path, '/twins/' + uriEncodedDeviceId + '/methods' + endpoint.versionQueryString()); + callback(); + } + }; + + var method = new DeviceMethod(fakeMethodParams, fakeRestClient); + method.invokeOn(fakeDeviceId, testCallback); + }); + [-1, 0, '', {}, { foo: 'bar' }, 'one line', new Buffer([0xDE, 0xAD, 0xBE, 0xEF])].forEach(function(goodPayload) { it('builds a correct request when the payload is ' + goodPayload.toString(), function(testCallback) { var fakeMethodParams = {