mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge branch 'pr_2952' into dev
This commit is contained in:
commit
7bf938901a
@ -43,9 +43,12 @@
|
|||||||
"denque": "1.5.0",
|
"denque": "1.5.0",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"express-session": "1.17.2",
|
"express-session": "1.17.2",
|
||||||
|
"form-data": "4.0.0",
|
||||||
"fs-extra": "10.0.0",
|
"fs-extra": "10.0.0",
|
||||||
"fs.notify": "0.0.4",
|
"fs.notify": "0.0.4",
|
||||||
|
"got": "11.8.2",
|
||||||
"hash-sum": "2.0.0",
|
"hash-sum": "2.0.0",
|
||||||
|
"hpagent": "0.1.1",
|
||||||
"https-proxy-agent": "5.0.0",
|
"https-proxy-agent": "5.0.0",
|
||||||
"i18next": "20.3.1",
|
"i18next": "20.3.1",
|
||||||
"iconv-lite": "0.6.3",
|
"iconv-lite": "0.6.3",
|
||||||
@ -72,8 +75,14 @@
|
|||||||
"request": "2.88.0",
|
"request": "2.88.0",
|
||||||
"semver": "7.3.5",
|
"semver": "7.3.5",
|
||||||
"tar": "6.1.0",
|
"tar": "6.1.0",
|
||||||
|
"tough-cookie": "4.0.0",
|
||||||
"uglify-js": "3.13.8",
|
"uglify-js": "3.13.8",
|
||||||
|
"uuid": "8.3.2",
|
||||||
"ws": "7.4.6",
|
"ws": "7.4.6",
|
||||||
|
"tough-cookie": "4.0.0",
|
||||||
|
"uglify-js": "3.13.3",
|
||||||
|
"uuid": "8.3.2",
|
||||||
|
"ws": "6.2.1",
|
||||||
"xml2js": "0.4.23"
|
"xml2js": "0.4.23"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
@ -16,7 +16,13 @@
|
|||||||
|
|
||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var request = require("request");
|
const got = require("got");
|
||||||
|
const {CookieJar} = require("tough-cookie");
|
||||||
|
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
const { v4: uuid } = require('uuid');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const URL = require("url").URL
|
||||||
var mustache = require("mustache");
|
var mustache = require("mustache");
|
||||||
var querystring = require("querystring");
|
var querystring = require("querystring");
|
||||||
var cookie = require("cookie");
|
var cookie = require("cookie");
|
||||||
@ -30,6 +36,8 @@ module.exports = function(RED) {
|
|||||||
var nodeMethod = n.method || "GET";
|
var nodeMethod = n.method || "GET";
|
||||||
var paytoqs = false;
|
var paytoqs = false;
|
||||||
var paytobody = false;
|
var paytobody = false;
|
||||||
|
var redirectList = [];
|
||||||
|
|
||||||
var nodeHTTPPersistent = n["persist"];
|
var nodeHTTPPersistent = n["persist"];
|
||||||
if (n.tls) {
|
if (n.tls) {
|
||||||
var tlsNode = RED.nodes.getNode(n.tls);
|
var tlsNode = RED.nodes.getNode(n.tls);
|
||||||
@ -57,12 +65,15 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.on("input",function(msg,nodeSend,nodeDone) {
|
this.on("input",function(msg,nodeSend,nodeDone) {
|
||||||
|
//reset redirectList on each request
|
||||||
|
redirectList = [];
|
||||||
var preRequestTimestamp = process.hrtime();
|
var preRequestTimestamp = process.hrtime();
|
||||||
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
|
node.status({fill:"blue",shape:"dot",text:"httpin.status.requesting"});
|
||||||
var url = nodeUrl || msg.url;
|
var url = nodeUrl || msg.url;
|
||||||
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
|
if (msg.url && nodeUrl && (nodeUrl !== msg.url)) { // revert change below when warning is finally removed
|
||||||
node.warn(RED._("common.errors.nooverride"));
|
node.warn(RED._("common.errors.nooverride"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTemplatedUrl) {
|
if (isTemplatedUrl) {
|
||||||
url = mustache.render(nodeUrl,msg);
|
url = mustache.render(nodeUrl,msg);
|
||||||
}
|
}
|
||||||
@ -71,6 +82,8 @@ module.exports = function(RED) {
|
|||||||
nodeDone();
|
nodeDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// url must start http:// or https:// so assume http:// if not set
|
// url must start http:// or https:// so assume http:// if not set
|
||||||
if (url.indexOf("://") !== -1 && url.indexOf("http") !== 0) {
|
if (url.indexOf("://") !== -1 && url.indexOf("http") !== 0) {
|
||||||
node.warn(RED._("httpin.errors.invalid-transport"));
|
node.warn(RED._("httpin.errors.invalid-transport"));
|
||||||
@ -86,6 +99,7 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var method = nodeMethod.toUpperCase() || "GET";
|
var method = nodeMethod.toUpperCase() || "GET";
|
||||||
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
|
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
|
||||||
node.warn(RED._("common.errors.nooverride"));
|
node.warn(RED._("common.errors.nooverride"));
|
||||||
@ -94,19 +108,19 @@ module.exports = function(RED) {
|
|||||||
method = msg.method.toUpperCase(); // use the msg parameter
|
method = msg.method.toUpperCase(); // use the msg parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
var isHttps = (/^https/i.test(url));
|
// var isHttps = (/^https/i.test(url));
|
||||||
|
|
||||||
var opts = {};
|
var opts = {};
|
||||||
opts.url = url;
|
|
||||||
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
|
// set defaultport, else when using HttpsProxyAgent, it's defaultPort of 443 will be used :(.
|
||||||
opts.defaultPort = isHttps?443:80;
|
// Had to remove this to get http->https redirect to work
|
||||||
|
// opts.defaultPort = isHttps?443:80;
|
||||||
opts.timeout = node.reqTimeout;
|
opts.timeout = node.reqTimeout;
|
||||||
opts.method = method;
|
opts.method = method;
|
||||||
opts.headers = {};
|
opts.headers = {};
|
||||||
opts.encoding = null; // Force NodeJs to return a Buffer (instead of a string)
|
opts.retry = 0;
|
||||||
|
opts.responseType = 'buffer';
|
||||||
opts.maxRedirects = 21;
|
opts.maxRedirects = 21;
|
||||||
opts.jar = request.jar();
|
opts.cookieJar = new CookieJar();
|
||||||
opts.proxy = null;
|
|
||||||
opts.forever = nodeHTTPPersistent;
|
opts.forever = nodeHTTPPersistent;
|
||||||
if (msg.requestTimeout !== undefined) {
|
if (msg.requestTimeout !== undefined) {
|
||||||
if (isNaN(msg.requestTimeout)) {
|
if (isNaN(msg.requestTimeout)) {
|
||||||
@ -117,6 +131,21 @@ module.exports = function(RED) {
|
|||||||
opts.timeout = msg.requestTimeout;
|
opts.timeout = msg.requestTimeout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts.hooks = {
|
||||||
|
beforeRedirect: [
|
||||||
|
(options, response) => {
|
||||||
|
let redirectInfo = {
|
||||||
|
location: response.headers.location
|
||||||
|
}
|
||||||
|
if (response.headers.hasOwnProperty('set-cookie')) {
|
||||||
|
redirectInfo.cookies = extractCookies(response.headers['set-cookie']);
|
||||||
|
}
|
||||||
|
redirectList.push(redirectInfo)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
var ctSet = "Content-Type"; // set default camel case
|
var ctSet = "Content-Type"; // set default camel case
|
||||||
var clSet = "Content-Length";
|
var clSet = "Content-Length";
|
||||||
if (msg.headers) {
|
if (msg.headers) {
|
||||||
@ -144,30 +173,15 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.hasOwnProperty('followRedirects')) {
|
if (msg.hasOwnProperty('followRedirects')) {
|
||||||
opts.followRedirect = msg.followRedirects;
|
opts.followRedirect = !!msg.followRedirects;
|
||||||
opts.followAllRedirects = !!opts.followRedirect;
|
|
||||||
}
|
|
||||||
var redirectList = [];
|
|
||||||
if (!opts.hasOwnProperty('followRedirect') || opts.followRedirect) {
|
|
||||||
opts.followRedirect = function(res) {
|
|
||||||
var redirectInfo = {
|
|
||||||
location: res.headers.location,
|
|
||||||
};
|
|
||||||
if (res.headers.hasOwnProperty('set-cookie')) {
|
|
||||||
redirectInfo.cookies = extractCookies(res.headers['set-cookie']);
|
|
||||||
}
|
|
||||||
redirectList.push(redirectInfo);
|
|
||||||
if (this.headers.cookie) {
|
|
||||||
delete this.headers.cookie;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.headers.hasOwnProperty('cookie')) {
|
if (opts.headers.hasOwnProperty('cookie')) {
|
||||||
var cookies = cookie.parse(opts.headers.cookie, {decode:String});
|
var cookies = cookie.parse(opts.headers.cookie, {decode:String});
|
||||||
for (var name in cookies) {
|
for (var name in cookies) {
|
||||||
opts.jar.setCookie(cookie.serialize(name, cookies[name], {encode:String}), url);
|
opts.cookieJar.setCookie(cookie.serialize(name, cookies[name], {encode:String}), url);
|
||||||
}
|
}
|
||||||
delete opts.headers.cookie;
|
delete opts.headers.cookie;
|
||||||
}
|
}
|
||||||
@ -180,13 +194,13 @@ module.exports = function(RED) {
|
|||||||
} else if (typeof msg.cookies[name] === 'object') {
|
} else if (typeof msg.cookies[name] === 'object') {
|
||||||
if(msg.cookies[name].encode === false){
|
if(msg.cookies[name].encode === false){
|
||||||
// If the encode option is false, the value is not encoded.
|
// If the encode option is false, the value is not encoded.
|
||||||
opts.jar.setCookie(cookie.serialize(name, msg.cookies[name].value, {encode: String}), url);
|
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value, {encode: String}), url);
|
||||||
} else {
|
} else {
|
||||||
// The value is encoded by encodeURIComponent().
|
// The value is encoded by encodeURIComponent().
|
||||||
opts.jar.setCookie(cookie.serialize(name, msg.cookies[name].value), url);
|
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name].value), url);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
opts.jar.setCookie(cookie.serialize(name, msg.cookies[name]), url);
|
opts.cookieJar.setCookie(cookie.serialize(name, msg.cookies[name]), url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,52 +208,62 @@ module.exports = function(RED) {
|
|||||||
if (this.credentials) {
|
if (this.credentials) {
|
||||||
if (this.authType === "basic") {
|
if (this.authType === "basic") {
|
||||||
if (this.credentials.user) {
|
if (this.credentials.user) {
|
||||||
opts.auth = {
|
opts.username = this.credentials.user;
|
||||||
user: this.credentials.user,
|
}
|
||||||
pass: this.credentials.password || ""
|
if (this.credentials.password) {
|
||||||
};
|
opts.password = this.credentials.password;
|
||||||
}
|
}
|
||||||
} else if (this.authType === "digest") {
|
} else if (this.authType === "digest") {
|
||||||
if (this.credentials.user) {
|
let digestCreds = this.credentials;
|
||||||
// The first request will be sent without auth information. Based on the 401 response, the library can determine
|
let sentCreds = false;
|
||||||
// which auth type is required by the server. Then the request is resubmitted with the appropriate auth header.
|
opts.hooks.afterResponse = [(response, retry) => {
|
||||||
opts.auth = {
|
if (response.statusCode === 401) {
|
||||||
user: this.credentials.user,
|
if (sentCreds) {
|
||||||
pass: this.credentials.password || "",
|
return response
|
||||||
sendImmediately: false
|
}
|
||||||
};
|
const requestUrl = new URL(response.request.requestUrl);
|
||||||
}
|
const options = response.request.options;
|
||||||
|
const normalisedHeaders = {};
|
||||||
|
Object.keys(response.headers).forEach(k => {
|
||||||
|
normalisedHeaders[k.toLowerCase()] = response.headers[k]
|
||||||
|
})
|
||||||
|
if (normalisedHeaders['www-authenticate']) {
|
||||||
|
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
|
||||||
|
options.headers.authorization = authHeader;
|
||||||
|
}
|
||||||
|
sentCreds = true;
|
||||||
|
return retry(options);
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}];
|
||||||
} else if (this.authType === "bearer") {
|
} else if (this.authType === "bearer") {
|
||||||
opts.auth = {
|
opts.headers.Authorization = `Bearer ${this.credentials.password||""}`
|
||||||
bearer: this.credentials.password || ""
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var payload = null;
|
var payload = null;
|
||||||
|
|
||||||
|
|
||||||
if (method !== 'GET' && method !== 'HEAD' && typeof msg.payload !== "undefined") {
|
if (method !== 'GET' && method !== 'HEAD' && typeof msg.payload !== "undefined") {
|
||||||
if (opts.headers['content-type'] == 'multipart/form-data' && typeof msg.payload === "object") {
|
if (opts.headers['content-type'] == 'multipart/form-data' && typeof msg.payload === "object") {
|
||||||
opts.formData = {};
|
let formData = new FormData();
|
||||||
|
|
||||||
for (var opt in msg.payload) {
|
for (var opt in msg.payload) {
|
||||||
if (msg.payload.hasOwnProperty(opt)) {
|
if (msg.payload.hasOwnProperty(opt)) {
|
||||||
var val = msg.payload[opt];
|
var val = msg.payload[opt];
|
||||||
if (val !== undefined && val !== null) {
|
if (val !== undefined && val !== null) {
|
||||||
if (typeof val === 'string' || Buffer.isBuffer(val)) {
|
if (typeof val === 'string' || Buffer.isBuffer(val)) {
|
||||||
opts.formData[opt] = val;
|
formData.append(opt, val);
|
||||||
} else if (typeof val === 'object' && val.hasOwnProperty('value')) {
|
} else if (typeof val === 'object' && val.hasOwnProperty('value')) {
|
||||||
// Treat as file to upload - ensure it has an options object
|
formData.append(opt,val.value,val.options || {});
|
||||||
// as request complains if it doesn't
|
|
||||||
if (!val.hasOwnProperty('options')) {
|
|
||||||
val.options = {};
|
|
||||||
}
|
|
||||||
opts.formData[opt] = val;
|
|
||||||
} else {
|
} else {
|
||||||
opts.formData[opt] = JSON.stringify(val);
|
formData.append(opt,JSON.stringify(val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// GOT will only set the content-type header with the correct boundary
|
||||||
|
// if the header isn't set. So we delete it here, for GOT to reset it.
|
||||||
|
delete opts.headers['content-type'];
|
||||||
|
opts.body = formData;
|
||||||
} else {
|
} else {
|
||||||
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
|
if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) {
|
||||||
payload = msg.payload;
|
payload = msg.payload;
|
||||||
@ -266,25 +290,29 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (method == 'GET' && typeof msg.payload !== "undefined" && paytoqs) {
|
if (method == 'GET' && typeof msg.payload !== "undefined" && paytoqs) {
|
||||||
if (typeof msg.payload === "object") {
|
if (typeof msg.payload === "object") {
|
||||||
try {
|
try {
|
||||||
if (opts.url.indexOf("?") !== -1) {
|
if (url.indexOf("?") !== -1) {
|
||||||
opts.url += (opts.url.endsWith("?")?"":"&") + querystring.stringify(msg.payload);
|
url += (url.endsWith("?")?"":"&") + querystring.stringify(msg.payload);
|
||||||
} else {
|
} else {
|
||||||
opts.url += "?" + querystring.stringify(msg.payload);
|
url += "?" + querystring.stringify(msg.payload);
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
|
||||||
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
||||||
nodeDone();
|
nodeDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
node.error(RED._("httpin.errors.invalid-payload"),msg);
|
||||||
nodeDone();
|
nodeDone();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) {
|
} else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) {
|
||||||
|
opts.allowGetBody = true;
|
||||||
if (typeof msg.payload === "object") {
|
if (typeof msg.payload === "object") {
|
||||||
opts.body = JSON.stringify(msg.payload);
|
opts.body = JSON.stringify(msg.payload);
|
||||||
} else if (typeof msg.payload == "number") {
|
} else if (typeof msg.payload == "number") {
|
||||||
@ -311,79 +339,97 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prox && !noproxy) {
|
if (prox && !noproxy) {
|
||||||
var match = prox.match(/^(http:\/\/)?(.+)?:([0-9]+)?/i);
|
var match = prox.match(/^(https?:\/\/)?(.+)?:([0-9]+)?/i);
|
||||||
if (match) {
|
if (match) {
|
||||||
opts.proxy = prox;
|
let proxyAgent;
|
||||||
|
let proxyURL = new URL(prox);
|
||||||
|
//set username/password to null to stop empty creds header
|
||||||
|
let proxyOptions = {
|
||||||
|
proxy: {
|
||||||
|
protocol: proxyURL.protocol,
|
||||||
|
hostname: proxyURL.hostname,
|
||||||
|
port: proxyURL.port,
|
||||||
|
username: null,
|
||||||
|
password: null
|
||||||
|
},
|
||||||
|
maxFreeSockets: 256,
|
||||||
|
maxSockets: 256,
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
if (proxyConfig && proxyConfig.credentials) {
|
||||||
|
let proxyUsername = proxyConfig.credentials.username || '';
|
||||||
|
let proxyPassword = proxyConfig.credentials.password || '';
|
||||||
|
if (proxyUsername || proxyPassword) {
|
||||||
|
proxyOptions.proxy.username = proxyUsername;
|
||||||
|
proxyOptions.proxy.password = proxyPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//need both incase of http -> https redirect
|
||||||
|
opts.agent = {
|
||||||
|
http: new HttpProxyAgent(proxyOptions),
|
||||||
|
https: new HttpsProxyAgent(proxyOptions)
|
||||||
|
};
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
node.warn("Bad proxy url: "+ prox);
|
node.warn("Bad proxy url: "+ prox);
|
||||||
opts.proxy = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (proxyConfig && proxyConfig.credentials && opts.proxy == proxyConfig.url) {
|
|
||||||
var proxyUsername = proxyConfig.credentials.username || '';
|
|
||||||
var proxyPassword = proxyConfig.credentials.password || '';
|
|
||||||
if (proxyUsername || proxyPassword) {
|
|
||||||
opts.headers['proxy-authorization'] =
|
|
||||||
'Basic ' +
|
|
||||||
Buffer.from(proxyUsername + ':' + proxyPassword).toString('base64');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tlsNode) {
|
if (tlsNode) {
|
||||||
tlsNode.addTLSOptions(opts);
|
opts.https = {};
|
||||||
|
tlsNode.addTLSOptions(opts.https);
|
||||||
} else {
|
} else {
|
||||||
if (msg.hasOwnProperty('rejectUnauthorized')) {
|
if (msg.hasOwnProperty('rejectUnauthorized')) {
|
||||||
opts.rejectUnauthorized = msg.rejectUnauthorized;
|
opts.https = { rejectUnauthorized: msg.rejectUnauthorized };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request(opts, function(err, res, body) {
|
got(url,opts).then(res => {
|
||||||
if(err){
|
msg.statusCode = res.statusCode;
|
||||||
if(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
|
msg.headers = res.headers;
|
||||||
node.error(RED._("common.notification.errors.no-response"), msg);
|
msg.responseUrl = res.url;
|
||||||
node.status({fill:"red", shape:"ring", text:"common.notification.errors.no-response"});
|
msg.payload = res.body;
|
||||||
}else{
|
msg.redirectList = redirectList;
|
||||||
node.error(err,msg);
|
msg.retry = 0;
|
||||||
node.status({fill:"red", shape:"ring", text:err.code});
|
|
||||||
}
|
|
||||||
msg.payload = err.toString() + " : " + url;
|
|
||||||
msg.statusCode = err.code;
|
|
||||||
nodeSend(msg);
|
|
||||||
nodeDone();
|
|
||||||
}else{
|
|
||||||
msg.statusCode = res.statusCode;
|
|
||||||
msg.headers = res.headers;
|
|
||||||
msg.responseUrl = res.request.uri.href;
|
|
||||||
msg.payload = body;
|
|
||||||
msg.redirectList = redirectList;
|
|
||||||
|
|
||||||
if (msg.headers.hasOwnProperty('set-cookie')) {
|
if (msg.headers.hasOwnProperty('set-cookie')) {
|
||||||
msg.responseCookies = extractCookies(msg.headers['set-cookie']);
|
msg.responseCookies = extractCookies(msg.headers['set-cookie']);
|
||||||
}
|
|
||||||
msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
|
|
||||||
// msg.url = url; // revert when warning above finally removed
|
|
||||||
if (node.metric()) {
|
|
||||||
// Calculate request time
|
|
||||||
var diff = process.hrtime(preRequestTimestamp);
|
|
||||||
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
|
||||||
var metricRequestDurationMillis = ms.toFixed(3);
|
|
||||||
node.metric("duration.millis", msg, metricRequestDurationMillis);
|
|
||||||
if (res.client && res.client.bytesRead) {
|
|
||||||
node.metric("size.bytes", msg, res.client.bytesRead);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the payload to the required return type
|
|
||||||
if (node.ret !== "bin") {
|
|
||||||
msg.payload = msg.payload.toString('utf8'); // txt
|
|
||||||
|
|
||||||
if (node.ret === "obj") {
|
|
||||||
try { msg.payload = JSON.parse(msg.payload); } // obj
|
|
||||||
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.status({});
|
|
||||||
nodeSend(msg);
|
|
||||||
nodeDone();
|
|
||||||
}
|
}
|
||||||
|
msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
|
||||||
|
// msg.url = url; // revert when warning above finally removed
|
||||||
|
if (node.metric()) {
|
||||||
|
// Calculate request time
|
||||||
|
var diff = process.hrtime(preRequestTimestamp);
|
||||||
|
var ms = diff[0] * 1e3 + diff[1] * 1e-6;
|
||||||
|
var metricRequestDurationMillis = ms.toFixed(3);
|
||||||
|
node.metric("duration.millis", msg, metricRequestDurationMillis);
|
||||||
|
if (res.client && res.client.bytesRead) {
|
||||||
|
node.metric("size.bytes", msg, res.client.bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the payload to the required return type
|
||||||
|
if (node.ret !== "bin") {
|
||||||
|
msg.payload = msg.payload.toString('utf8'); // txt
|
||||||
|
|
||||||
|
if (node.ret === "obj") {
|
||||||
|
try { msg.payload = JSON.parse(msg.payload); } // obj
|
||||||
|
catch(e) { node.warn(RED._("httpin.errors.json-error")); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.status({});
|
||||||
|
nodeSend(msg);
|
||||||
|
nodeDone();
|
||||||
|
}).catch(err => {
|
||||||
|
if(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
|
||||||
|
node.error(RED._("common.notification.errors.no-response"), msg);
|
||||||
|
node.status({fill:"red", shape:"ring", text:"common.notification.errors.no-response"});
|
||||||
|
}else{
|
||||||
|
node.error(err,msg);
|
||||||
|
node.status({fill:"red", shape:"ring", text:err.code});
|
||||||
|
}
|
||||||
|
msg.payload = err.toString() + " : " + url;
|
||||||
|
msg.statusCode = err.code || (err.response?err.response.statusCode:undefined);
|
||||||
|
nodeSend(msg);
|
||||||
|
nodeDone();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -411,4 +457,69 @@ module.exports = function(RED) {
|
|||||||
password: {type: "password"}
|
password: {type: "password"}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const md5 = (value) => { return crypto.createHash('md5').update(value).digest('hex') }
|
||||||
|
|
||||||
|
function ha1Compute(algorithm, user, realm, pass, nonce, cnonce) {
|
||||||
|
/**
|
||||||
|
* RFC 2617: handle both MD5 and MD5-sess algorithms.
|
||||||
|
*
|
||||||
|
* If the algorithm directive's value is "MD5" or unspecified, then HA1 is
|
||||||
|
* HA1=MD5(username:realm:password)
|
||||||
|
* If the algorithm directive's value is "MD5-sess", then HA1 is
|
||||||
|
* HA1=MD5(MD5(username:realm:password):nonce:cnonce)
|
||||||
|
*/
|
||||||
|
var ha1 = md5(user + ':' + realm + ':' + pass)
|
||||||
|
if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
|
||||||
|
return md5(ha1 + ':' + nonce + ':' + cnonce)
|
||||||
|
} else {
|
||||||
|
return ha1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function buildDigestHeader(user, pass, method, path, authHeader) {
|
||||||
|
var challenge = {}
|
||||||
|
var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
|
||||||
|
for (;;) {
|
||||||
|
var match = re.exec(authHeader)
|
||||||
|
if (!match) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
challenge[match[1]] = match[2] || match[3]
|
||||||
|
}
|
||||||
|
var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
|
||||||
|
var nc = qop && '00000001'
|
||||||
|
var cnonce = qop && uuid().replace(/-/g, '')
|
||||||
|
var ha1 = ha1Compute(challenge.algorithm, user, challenge.realm, pass, challenge.nonce, cnonce)
|
||||||
|
var ha2 = md5(method + ':' + path)
|
||||||
|
var digestResponse = qop
|
||||||
|
? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
|
||||||
|
: md5(ha1 + ':' + challenge.nonce + ':' + ha2)
|
||||||
|
var authValues = {
|
||||||
|
username: user,
|
||||||
|
realm: challenge.realm,
|
||||||
|
nonce: challenge.nonce,
|
||||||
|
uri: path,
|
||||||
|
qop: qop,
|
||||||
|
response: digestResponse,
|
||||||
|
nc: nc,
|
||||||
|
cnonce: cnonce,
|
||||||
|
algorithm: challenge.algorithm,
|
||||||
|
opaque: challenge.opaque
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader = []
|
||||||
|
for (var k in authValues) {
|
||||||
|
if (authValues[k]) {
|
||||||
|
if (k === 'qop' || k === 'nc' || k === 'algorithm') {
|
||||||
|
authHeader.push(k + '=' + authValues[k])
|
||||||
|
} else {
|
||||||
|
authHeader.push(k + '="' + authValues[k] + '"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authHeader = 'Digest ' + authHeader.join(', ')
|
||||||
|
return authHeader
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,12 @@
|
|||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"cronosjs": "1.7.1",
|
"cronosjs": "1.7.1",
|
||||||
"denque": "1.5.0",
|
"denque": "1.5.0",
|
||||||
|
"form-data": "4.0.0",
|
||||||
"fs-extra": "10.0.0",
|
"fs-extra": "10.0.0",
|
||||||
"fs.notify": "0.0.4",
|
"fs.notify": "0.0.4",
|
||||||
|
"got": "11.8.2",
|
||||||
"hash-sum": "2.0.0",
|
"hash-sum": "2.0.0",
|
||||||
|
"hpagent": "0.1.1",
|
||||||
"https-proxy-agent": "5.0.0",
|
"https-proxy-agent": "5.0.0",
|
||||||
"is-utf8": "0.2.1",
|
"is-utf8": "0.2.1",
|
||||||
"js-yaml": "3.14.0",
|
"js-yaml": "3.14.0",
|
||||||
@ -39,6 +42,8 @@
|
|||||||
"on-headers": "1.0.2",
|
"on-headers": "1.0.2",
|
||||||
"raw-body": "2.4.1",
|
"raw-body": "2.4.1",
|
||||||
"request": "2.88.0",
|
"request": "2.88.0",
|
||||||
|
"tough-cookie": "4.0.0",
|
||||||
|
"uuid":"8.3.2",
|
||||||
"ws": "7.4.6",
|
"ws": "7.4.6",
|
||||||
"xml2js": "0.4.23",
|
"xml2js": "0.4.23",
|
||||||
"iconv-lite": "0.6.3"
|
"iconv-lite": "0.6.3"
|
||||||
|
@ -138,8 +138,6 @@ describe('HTTP Request Node', function() {
|
|||||||
});
|
});
|
||||||
testApp.use(fileUploadApp);
|
testApp.use(fileUploadApp);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
testApp.use(bodyParser.raw({type:"*/*"}));
|
testApp.use(bodyParser.raw({type:"*/*"}));
|
||||||
testApp.use(cookieParser(undefined,{decode:String}));
|
testApp.use(cookieParser(undefined,{decode:String}));
|
||||||
testApp.get('/statusCode204', function(req,res) { res.status(204).end();});
|
testApp.get('/statusCode204', function(req,res) { res.status(204).end();});
|
||||||
@ -166,11 +164,16 @@ describe('HTTP Request Node', function() {
|
|||||||
res.send("");
|
res.send("");
|
||||||
});
|
});
|
||||||
testApp.get('/authenticate', function(req, res){
|
testApp.get('/authenticate', function(req, res){
|
||||||
var user = auth.parse(req.headers['authorization']);
|
let result;
|
||||||
var result = {
|
let authHeader = req.headers['authorization'];
|
||||||
user: user.name,
|
if (/^Basic/.test(authHeader)) {
|
||||||
pass: user.pass,
|
result = auth.parse(authHeader);
|
||||||
};
|
result.user = result.name;
|
||||||
|
} else if (/^Bearer/.test(authHeader)) {
|
||||||
|
result = {
|
||||||
|
token: authHeader.substring(7)
|
||||||
|
}
|
||||||
|
}
|
||||||
res.json(result);
|
res.json(result);
|
||||||
});
|
});
|
||||||
testApp.get('/proxyAuthenticate', function(req, res){
|
testApp.get('/proxyAuthenticate', function(req, res){
|
||||||
@ -891,7 +894,8 @@ describe('HTTP Request Node', function() {
|
|||||||
var n2 = helper.getNode("n2");
|
var n2 = helper.getNode("n2");
|
||||||
n2.on("input", function(msg) {
|
n2.on("input", function(msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('statusCode','ESOCKETTIMEDOUT');
|
msg.should.have.property('statusCode');
|
||||||
|
/TIMEDOUT/.test(msg.statusCode).should.be.true();
|
||||||
var logEvents = helper.log().args.filter(function(evt) {
|
var logEvents = helper.log().args.filter(function(evt) {
|
||||||
return evt[0].type == 'http request';
|
return evt[0].type == 'http request';
|
||||||
});
|
});
|
||||||
@ -917,7 +921,8 @@ describe('HTTP Request Node', function() {
|
|||||||
var n2 = helper.getNode("n2");
|
var n2 = helper.getNode("n2");
|
||||||
n2.on("input", function(msg) {
|
n2.on("input", function(msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('statusCode','ESOCKETTIMEDOUT');
|
msg.should.have.property('statusCode');
|
||||||
|
/TIMEDOUT/.test(msg.statusCode).should.be.true();
|
||||||
var logEvents = helper.log().args.filter(function(evt) {
|
var logEvents = helper.log().args.filter(function(evt) {
|
||||||
return evt[0].type == 'http request';
|
return evt[0].type == 'http request';
|
||||||
});
|
});
|
||||||
@ -1380,7 +1385,6 @@ describe('HTTP Request Node', function() {
|
|||||||
n1.receive({payload:"foo"});
|
n1.receive({payload:"foo"});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use http_proxy', function(done) {
|
it('should use http_proxy', function(done) {
|
||||||
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')},
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')},
|
||||||
{id:"n2", type:"helper"}];
|
{id:"n2", type:"helper"}];
|
||||||
@ -1565,9 +1569,8 @@ describe('HTTP Request Node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('authentication', function() {
|
describe('authentication', function() {
|
||||||
it('should authenticate on server', function(done) {
|
it('should authenticate on server - basic', function(done) {
|
||||||
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/authenticate')},
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/authenticate')},
|
||||||
{id:"n2", type:"helper"}];
|
{id:"n2", type:"helper"}];
|
||||||
helper.load(httpRequestNode, flow, function() {
|
helper.load(httpRequestNode, flow, function() {
|
||||||
@ -1587,7 +1590,25 @@ describe('HTTP Request Node', function() {
|
|||||||
n1.receive({payload:"foo"});
|
n1.receive({payload:"foo"});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('should authenticate on server - bearer', function(done) {
|
||||||
|
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",authType:"bearer", url:getTestURL('/authenticate')},
|
||||||
|
{id:"n2", type:"helper"}];
|
||||||
|
helper.load(httpRequestNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("n1");
|
||||||
|
var n2 = helper.getNode("n2");
|
||||||
|
n1.credentials = {password:'passwordfoo'};
|
||||||
|
n2.on("input", function(msg) {
|
||||||
|
try {
|
||||||
|
msg.should.have.property('statusCode',200);
|
||||||
|
msg.payload.should.have.property('token', 'passwordfoo');
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
n1.receive({payload:"foo"});
|
||||||
|
});
|
||||||
|
});
|
||||||
it('should authenticate on proxy server', function(done) {
|
it('should authenticate on proxy server', function(done) {
|
||||||
var flow = [{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate')},
|
var flow = [{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate')},
|
||||||
{id:"n2", type:"helper"}];
|
{id:"n2", type:"helper"}];
|
||||||
@ -1740,22 +1761,24 @@ describe('HTTP Request Node', function() {
|
|||||||
var n1 = helper.getNode("n1");
|
var n1 = helper.getNode("n1");
|
||||||
var n2 = helper.getNode("n2");
|
var n2 = helper.getNode("n2");
|
||||||
n2.on("input", function(msg) {
|
n2.on("input", function(msg) {
|
||||||
var cookies1 = receivedCookies['localhost:'+testPort+'/redirectToSameDomain'];
|
try {
|
||||||
var cookies2 = receivedCookies['localhost:'+testPort+'/redirectReturn'];
|
var cookies1 = receivedCookies['localhost:'+testPort+'/redirectToSameDomain'];
|
||||||
if (cookies1 && Object.keys(cookies1).length != 0) {
|
var cookies2 = receivedCookies['localhost:'+testPort+'/redirectReturn'];
|
||||||
done(new Error('Invalid cookie(path:/rediectToSame)'));
|
if (cookies1 && Object.keys(cookies1).length != 0) {
|
||||||
return;
|
done(new Error('Invalid cookie(path:/rediectToSame)'));
|
||||||
}
|
return;
|
||||||
if ((cookies2 && Object.keys(cookies2).length != 1) ||
|
}
|
||||||
|
if ((cookies2 && Object.keys(cookies2).length != 1) ||
|
||||||
cookies2['redirectToSameDomainCookie'] !== 'same1') {
|
cookies2['redirectToSameDomainCookie'] !== 'same1') {
|
||||||
done(new Error('Invalid cookie(path:/rediectReurn)'));
|
done(new Error('Invalid cookie(path:/rediectReurn)'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var redirect1 = msg.redirectList[0];
|
var redirect1 = msg.redirectList[0];
|
||||||
redirect1.location.should.equal('http://localhost:'+testPort+'/redirectReturn');
|
redirect1.location.should.equal('http://localhost:'+testPort+'/redirectReturn');
|
||||||
redirect1.cookies.redirectToSameDomainCookie.Path.should.equal('/');
|
redirect1.cookies.redirectToSameDomainCookie.Path.should.equal('/');
|
||||||
redirect1.cookies.redirectToSameDomainCookie.value.should.equal('same1');
|
redirect1.cookies.redirectToSameDomainCookie.value.should.equal('same1');
|
||||||
done();
|
done();
|
||||||
|
} catch(err) { done(err)}
|
||||||
});
|
});
|
||||||
n1.receive({});
|
n1.receive({});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user