Repository: vpulim/node-soap

Issue:
Problem decoding special characters on endpoint response
Currently, I am trying to decode some strings such as “FÉRIAS” and it is returning “F�RIAS” when encoding it incorrectly to UTF-8 when the endpoint is returning “ISO-8859-1”;

I did some debugging and managed to solve this by using the “parseReponseAttachments” option so that it sends the headers with:
```
{
responseType: ‘arraybuffer’
responseEncoding: ‘binary’
}
```

and when receiving the data in the http file, on the req.then() function, in the line 233, I add an option to receive an encoding: 
```return handleBody(res.data.toString(_this.options.encoding || ‘utf8’));```

allowing me to handle the encoding as needed.

Discussion:
@w666: Related MR has been merged. Will try to release it soon. Closing this issue. Feels free to reopen if needed.

Solution patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..53ffdc8
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+  - package-ecosystem: "npm" # See documentation for possible values
+    directory: "/" # Location of package manifests
+    schedule:
+      interval: "weekly"
+
+  - package-ecosystem: "github-actions" # See documentation for possible values
+    directory: "/" # Location of package manifests
+    schedule:
+      interval: "weekly"
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 49235ab..a093d38 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -4,8 +4,8 @@ jobs:
   code-quality:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-node@v2
+      - uses: actions/checkout@v4
+      - uses: actions/setup-node@v4
         with:
           node-version: lts/*
       - run: npm ci
diff --git a/Readme.md b/Readme.md
index d73f9d5..2aac009 100644
--- a/Readme.md
+++ b/Readme.md
@@ -115,6 +115,7 @@ Paid support can be provided as well, please contact one of the active maintaine
   - `stream` (*boolean*): Use streams to parse the XML SOAP responses. (**Default:** `false`)
   - `returnSaxStream` (*boolean*): Return the SAX stream, transferring responsibility of parsing XML to the end user. Only valid when the *stream* option is set to `true`. (**Default:** `false`)
   - `parseReponseAttachments` (*boolean*): Treat response as multipart/related response with MTOM attachment. Reach attachments on the `lastResponseAttachments` property of SoapClient. (**Default:** `false`)
+  - `encoding` (_string_): response data enconding, used with `parseReponseAttachments`. (**Default:** `utf8`)
 - `callback` (*Function*):
   - `err` (*Error* | *<AggregateError>*)
   - `result` (*Any*)
diff --git a/package-lock.json b/package-lock.json
index 24a55de..57a7fa3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,7 +39,7 @@
         "nyc": "^17.1.0",
         "readable-stream": "^4.7.0",
         "semver": "^7.7.1",
-        "serve-static": "^1.15.0",
+        "serve-static": "^2.2.0",
         "should": "^13.2.3",
         "sinon": "^20.0.0",
         "source-map-support": "^0.5.21",
@@ -1868,6 +1868,21 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/express/node_modules/serve-static": {
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+      "dev": true,
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.19.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
     "node_modules/extend": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -4460,11 +4475,10 @@
       "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
     },
     "node_modules/semver": {
-      "version": "7.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
-      "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
       "dev": true,
-      "license": "ISC",
       "bin": {
         "semver": "bin/semver.js"
       },
@@ -4535,19 +4549,70 @@
       }
     },
     "node_modules/serve-static": {
-      "version": "1.16.2",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
-      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+      "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
       "dev": true,
-      "license": "MIT",
       "dependencies": {
-        "encodeurl": "~2.0.0",
-        "escape-html": "~1.0.3",
-        "parseurl": "~1.3.3",
-        "send": "0.19.0"
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "parseurl": "^1.3.3",
+        "send": "^1.2.0"
       },
       "engines": {
-        "node": ">= 0.8.0"
+        "node": ">= 18"
+      }
+    },
+    "node_modules/serve-static/node_modules/fresh": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+      "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/serve-static/node_modules/mime-db": {
+      "version": "1.54.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+      "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/serve-static/node_modules/mime-types": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+      "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+      "dev": true,
+      "dependencies": {
+        "mime-db": "^1.54.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/serve-static/node_modules/send": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+      "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+      "dev": true,
+      "dependencies": {
+        "debug": "^4.3.5",
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "etag": "^1.8.1",
+        "fresh": "^2.0.0",
+        "http-errors": "^2.0.0",
+        "mime-types": "^3.0.1",
+        "ms": "^2.1.3",
+        "on-finished": "^2.4.1",
+        "range-parser": "^1.2.1",
+        "statuses": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 18"
       }
     },
     "node_modules/set-blocking": {
@@ -7063,6 +7128,18 @@
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
           "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
           "dev": true
+        },
+        "serve-static": {
+          "version": "1.16.2",
+          "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+          "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+          "dev": true,
+          "requires": {
+            "encodeurl": "~2.0.0",
+            "escape-html": "~1.0.3",
+            "parseurl": "~1.3.3",
+            "send": "0.19.0"
+          }
         }
       }
     },
@@ -8865,9 +8942,9 @@
       "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
     },
     "semver": {
-      "version": "7.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
-      "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
       "dev": true
     },
     "send": {
@@ -8926,15 +9003,57 @@
       }
     },
     "serve-static": {
-      "version": "1.16.2",
-      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
-      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+      "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
       "dev": true,
       "requires": {
-        "encodeurl": "~2.0.0",
-        "escape-html": "~1.0.3",
-        "parseurl": "~1.3.3",
-        "send": "0.19.0"
+        "encodeurl": "^2.0.0",
+        "escape-html": "^1.0.3",
+        "parseurl": "^1.3.3",
+        "send": "^1.2.0"
+      },
+      "dependencies": {
+        "fresh": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+          "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+          "dev": true
+        },
+        "mime-db": {
+          "version": "1.54.0",
+          "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+          "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+          "dev": true
+        },
+        "mime-types": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+          "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+          "dev": true,
+          "requires": {
+            "mime-db": "^1.54.0"
+          }
+        },
+        "send": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+          "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+          "dev": true,
+          "requires": {
+            "debug": "^4.3.5",
+            "encodeurl": "^2.0.0",
+            "escape-html": "^1.0.3",
+            "etag": "^1.8.1",
+            "fresh": "^2.0.0",
+            "http-errors": "^2.0.0",
+            "mime-types": "^3.0.1",
+            "ms": "^2.1.3",
+            "on-finished": "^2.4.1",
+            "range-parser": "^1.2.1",
+            "statuses": "^2.0.1"
+          }
+        }
       }
     },
     "set-blocking": {
diff --git a/package.json b/package.json
index d6dc00e..7482636 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
     "nyc": "^17.1.0",
     "readable-stream": "^4.7.0",
     "semver": "^7.7.1",
-    "serve-static": "^1.15.0",
+    "serve-static": "^2.2.0",
     "should": "^13.2.3",
     "sinon": "^20.0.0",
     "source-map-support": "^0.5.21",
diff --git a/src/http.ts b/src/http.ts
index 439ac60..301d337 100644
--- a/src/http.ts
+++ b/src/http.ts
@@ -236,10 +236,10 @@ export class HttpClient implements IHttpClient {
               return callback(new Error('Cannot parse multipart response'));
             }
             (res as any).mtomResponseAttachments = multipartResponse;
-            return handleBody(firstPart.body.toString('utf8'));
+            return handleBody(firstPart.body.toString(_this.options.encoding || 'utf8'));
           });
         } else {
-          return handleBody(res.data.toString('utf8'));
+          return handleBody(res.data.toString(_this.options.encoding || 'utf8'));
         }
       } else {
         return handleBody();
diff --git a/src/types.ts b/src/types.ts
index 518f8e7..2dcb3e5 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -141,6 +141,8 @@ export interface IOptions extends IWsdlBaseOptions {
   WSDL_CACHE?;
   /** handle MTOM soapAttachments in response */
   parseReponseAttachments?: boolean;
+  /** handle endpoint response.data enconding when using parseReponseAttachments */
+  encoding?: BufferEncoding;
 }
 
 export interface IOneWayOptions {



Test patch:
diff --git a/test/response-encoding-test.js b/test/response-encoding-test.js
new file mode 100644
index 0000000..853c565
--- /dev/null
+++ b/test/response-encoding-test.js
@@ -0,0 +1,106 @@
+'use strict';
+
+var assert = require('assert');
+var http = require('http');
+var soap = require('../lib/soap');
+var server;
+
+describe('Preserse data encoding from endpoint response', function() {
+  var wsdl = __dirname + '/wsdl/hello.wsdl';
+  var expectedString = "àáÁÉÈÀçãü";
+  var xml = `<?xml version=\"1.0\" encoding=\"iso-8859-1\"?><soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"  xmlns:tns=\"http://www.examples.com/wsdl/HelloService.wsdl\"><soap:Body><tns:sayHelloResponse>${expectedString}</tns:sayHelloResponse></soap:Body></soap:Envelope>`;
+  var xmlEncoded = Buffer.from(
+    xml,
+    "binary"
+  );
+
+  before(function(done) {
+    server = http.createServer(function(req, res) {
+      res.statusCode = 200;
+      res.end(xmlEncoded);
+    }).listen(51515, done);
+  });
+
+  after(function() {
+    server.close();
+  });
+
+  it('Should read special characters with enconding option with success',
+    function(done) {
+      var url = 'http://' + server.address().address + ':' + server.address().port;
+
+      if (server.address().address === '0.0.0.0' || server.address().address === '::') {
+        url = 'http://127.0.0.1:' + server.address().port;
+      }
+
+      soap.createClient(
+        wsdl,
+        {
+          endpoint: url,
+          disableCache: true, // disable wsdl cache, otherwise 'mocha test/client-response-options-test.js test/response-preserve-whitespace-test.js' will fail.
+          parseReponseAttachments:true,
+          encoding:'latin1',
+        },
+        function(err, client) {
+          if (err) {
+            console.log(err);
+            throw err;
+          }
+
+          client.sayHello(
+            {
+              firstName: 'hello world'
+            },
+            function(err, result, rawResponse, soapHeader, rawRequest) {
+              if (err) {
+                console.log(err);
+                throw err;
+              }
+              
+              assert.strictEqual(expectedString, result);
+              done();
+            }
+          );
+        }
+      );
+    });
+
+  it('Should read special characters with enconding option with error',
+  function(done) {
+    var url = 'http://' + server.address().address + ':' + server.address().port;
+
+    if (server.address().address === '0.0.0.0' || server.address().address === '::') {
+      url = 'http://127.0.0.1:' + server.address().port;
+    }
+
+    soap.createClient(
+      wsdl,
+      {
+        endpoint: url,
+        disableCache: true, // disable wsdl cache, otherwise 'mocha test/client-response-options-test.js test/response-preserve-whitespace-test.js' will fail.
+        parseReponseAttachments:true,
+      },
+      function(err, client) {
+        if (err) {
+          console.log(err);
+          throw err;
+        }
+
+        client.sayHello(
+          {
+            firstName: 'hello world'
+          },
+          function(err, result, rawResponse, soapHeader, rawRequest) {
+            if (err) {
+              console.log(err);
+              throw err;
+            }
+            assert.strictEqual('���������', result);
+            done();
+          }
+        );
+      }
+    );
+  });
+
+});