Compare commits

...

41 Commits

Author SHA1 Message Date
Dave Conway-Jones
2fe78cf971 Update packages/node_modules/@node-red/util/lib/i18n.js
Co-authored-by: Nick O'Leary <nick.oleary@gmail.com>
2021-09-25 14:51:47 +01:00
Dave Conway-Jones
bbf066f030 Fixes to fs promises so that electron version runs 2021-09-10 08:33:07 +01:00
Dave Conway-Jones
17f9829498 Fix for incorrect tcpout connection count
to Close #3098
seems to need dummy data receiver in order to recognise other callbacks.
2021-09-03 13:00:06 +01:00
Nick O'Leary
3b460fb8fa Bump for 2.0.6 2021-09-02 09:00:54 +01:00
Nick O'Leary
ee15e9acc5 Update tar to latest 2021-09-02 08:21:10 +01:00
Nick O'Leary
48fce35fb3 Merge pull request #3128 from aksswami/master
Update tar to 6.1.9 to resolve vulnerability issue
2021-09-02 08:20:15 +01:00
Amit Kumar Swami
78899378c2 Update tar to 6.1.9 to resolve vulnerability issue 2021-09-02 13:36:41 +08:00
Nick O'Leary
2144407e41 Merge pull request #3117 from dschmidt/passport-callback-arity
Give passport verfiy callback wrapper the same arity as the original callback
2021-08-31 16:45:43 +01:00
Nick O'Leary
26d83bb9ea Ensure treeList row has suitable min-height when no content
Fixes #3109
2021-08-27 16:32:43 +01:00
Nick O'Leary
8e89b1bdf2 Fix typo in ko editor.json
Fixes #3119
2021-08-27 16:22:58 +01:00
Dominik Schmidt
630d2ca926 Give passport verfiy callback wrapper the same arity as the original callback passed in via options 2021-08-23 15:57:44 +02:00
Nick O'Leary
34cb93794c Merge pull request #3106 from bonanitech/fade-on-hover
Fix fade colors when hovering over tabs
2021-08-23 12:34:42 +01:00
Nick O'Leary
122b5ba468 Merge pull request #3115 from bartbutenaers/https-settings-as-string
Key and certificate as string or buffer
2021-08-23 12:25:56 +01:00
bartbutenaers
7f2627dbc8 Key and certificate as string or buffer 2021-08-21 22:21:58 +02:00
Mauricio Bonani
e93734b209 Change fade color when hovering an inactive tab 2021-08-10 14:26:41 -04:00
Mauricio Bonani
9e5218f6b4 Place close tab link in front of fade 2021-08-10 14:21:54 -04:00
Nick O'Leary
f1e7ec0c6b Bump for 2.0.5 2021-07-30 13:28:52 +01:00
Nick O'Leary
23765d9139 Update tar dependency 2021-07-30 13:20:26 +01:00
Nick O'Leary
43febe269c Add support for maintenance streams in generate-publish-script 2021-07-30 13:19:36 +01:00
Nick O'Leary
40233c7702 Fix regression in Join node when manual joining array with msg.parts present
Fixes #3096
2021-07-30 13:17:05 +01:00
Nick O'Leary
27ed81614b Remove default ctrl-enter keybinding from monaco editor
Fixes #3093
2021-07-30 11:50:21 +01:00
Nick O'Leary
889d23e9bd Update changelog 2021-07-28 11:02:11 +01:00
Nick O'Leary
f8571023f6 Fix inject now button unable to send empty props 2021-07-28 10:59:16 +01:00
Nick O'Leary
6364e00202 Merge pull request #3092 from hardillb/http-req-ca-fix
Copy tls.cert to tls.certificate for GOT
2021-07-28 10:05:19 +01:00
Ben Hardill
a76c6f86c6 Add Testcase & Fix typo 2021-07-28 08:52:35 +01:00
Ben Hardill
555e815402 Copy tls.cert to tls.certificate for GOT 2021-07-27 22:19:35 +01:00
Nick O'Leary
ee9234b2c6 Bump for 2.0.4 2021-07-26 17:09:26 +01:00
Nick O'Leary
735b9c5844 Handle just-copied-but-not-deployed node with credentials in editor
Fixes #3090
2021-07-26 14:50:00 +01:00
Nick O'Leary
064f3eb3bc Fix RBE node handling of default topi property
Fixes #3087
2021-07-26 14:36:09 +01:00
Nick O'Leary
f1775d4fd1 Handle partially encoded url query strings in request node 2021-07-26 14:21:52 +01:00
Nick O'Leary
a9bc111c4f Merge pull request #3089 from hardillb/http-req-ca-fix
Fix support for supplied CA certs
2021-07-26 14:20:08 +01:00
Nick O'Leary
c100612473 Merge pull request #3085 from bonanitech/fade-colors
Fix tab fade CSS for when using themes
2021-07-26 14:08:15 +01:00
Ben Hardill
26087f8dc7 Fix support for supplied CA certs 2021-07-26 10:25:06 +01:00
Mauricio Bonani
36e75cb728 Fix tab fade CSS for when using themes 2021-07-25 11:08:55 -04:00
Nick O'Leary
142176f194 Bump for 2.0.3 2021-07-23 14:38:43 +01:00
Nick O'Leary
c5892fc17e Fix HTML parsing when body is included in the select tag
Fixes #3079
2021-07-23 10:09:00 +01:00
Nick O'Leary
6e69cfbca4 Preserve case of user-provided http headers in request node
Fixes #3081
2021-07-23 09:55:32 +01:00
Nick O'Leary
775181f761 Set decompress to false for HTTP Request to keep 1.x compatibility
Fixes #3083
2021-07-23 08:57:44 +01:00
Nick O'Leary
36e83d628e Add unit tests for HTTP Request encodeURI and error response 2021-07-23 00:10:17 +01:00
Nick O'Leary
5f6fcb2bc0 Do not throw HTTP errors in request node
Fixes #3082

GOT will throw errors for non-successful http responses by default. We need to turn that
off to be consistent with the 1.x behaviour using the request module
2021-07-22 23:48:30 +01:00
Nick O'Leary
7b106e5650 Ensure uri is properly encoded before passing to got module
Fixes #3080
2021-07-22 23:47:32 +01:00
30 changed files with 508 additions and 115 deletions

View File

@@ -1,3 +1,60 @@
#### 2.0.6: Maintenance Release
Editor
- Fix typo in ko editor.json Fixes #3119
- Change fade color when hovering an inactive tab (#3106) @bonanitech
- Ensure treeList row has suitable min-height when no content Fixes #3109
Runtime
- Update tar to latest (#3128) @aksswami
- Give passport verify callback the same arity as the original callback (#3117) @dschmidt
- Handle HTTPS Key and certificate as string or buffer (#3115) @bartbutenaers
#### 2.0.5: Maintenance Release
Editor
- Remove default ctrl-enter keybinding from monaco editor Fixes #3093
Runtime
- Update tar dependency
- Add support for maintenance streams in generate-publish-script
Nodes
- Fix regression in Join node when manual joining array with msg.parts present Fixes #3096
#### 2.0.4: Maintenance Release
Editor
- Fix tab fade CSS for when using themes (#3085) @bonanitech
- Handle just-copied-but-not-deployed node with credentials in editor Fixes #3090
Nodes
- Filter: Fix RBE node handling of default topi property Fixes #3087
- HTTP Request: Handle partially encoded url query strings in request node
- HTTP Request: Fix support for supplied CA certs (#3089) @hardillb
- HTTP Request: Ensure TLS Cert is used (#3092) @hardillb
- Inject: Fix inject now button unable to send empty props
- Inject: Inject now button success notification should use label with updated props
#### 2.0.3: Maintenance Release
Nodes
- HTML: Fix HTML parsing when body is included in the select tag Fixes #3079
- HTTP Request: Preserve case of user-provided http headers in request node Fixes #3081
- HTTP Request: Set decompress to false for HTTP Request to keep 1.x compatibility Fixes #3083
- HTTP Request: Add unit tests for HTTP Request encodeURI and error response
- HTTP Request: Do not throw HTTP errors in request node Fixes #3082
- HTTP Request: Ensure uri is properly encoded before passing to got module Fixes #3080
#### 2.0.2: Maintenance Release
Runtime

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "2.0.2",
"version": "2.0.6",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -28,8 +28,8 @@
"dependencies": {
"acorn": "8.4.1",
"acorn-walk": "8.1.1",
"ajv": "8.6.0",
"async-mutex": "0.3.1",
"ajv": "8.6.2",
"async-mutex": "0.3.2",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
@@ -55,14 +55,14 @@
"is-utf8": "0.2.1",
"js-yaml": "3.14.1",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.4",
"jsonata": "1.8.5",
"lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0",
"memorystore": "1.6.6",
"mime": "2.5.2",
"moment-timezone": "0.5.33",
"mqtt": "4.2.8",
"multer": "1.4.2",
"multer": "1.4.3",
"mustache": "4.2.0",
"node-red-admin": "^2.2.0",
"nopt": "5.0.0",
@@ -73,9 +73,9 @@
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.4.1",
"semver": "7.3.5",
"tar": "6.1.0",
"tar": "6.1.11",
"tough-cookie": "4.0.0",
"uglify-js": "3.13.10",
"uglify-js": "3.14.1",
"uuid": "8.3.2",
"ws": "7.5.1",
"xml2js": "0.4.23"
@@ -84,7 +84,7 @@
"bcrypt": "5.0.1"
},
"devDependencies": {
"dompurify": "2.2.9",
"dompurify": "2.3.1",
"grunt": "1.4.1",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.4.3",
@@ -109,15 +109,15 @@
"jsdoc-nr-template": "github:node-red/jsdoc-nr-template",
"marked": "2.1.3",
"minami": "1.2.3",
"mocha": "9.0.1",
"mocha": "9.1.1",
"node-red-node-test-helper": "^0.2.7",
"nodemon": "2.0.8",
"nodemon": "2.0.12",
"proxy": "^1.0.2",
"sass": "1.35.1",
"sass": "1.39.0",
"should": "13.2.3",
"sinon": "11.1.1",
"sinon": "11.1.2",
"stoppable": "^1.1.0",
"supertest": "6.1.3"
"supertest": "6.1.6"
},
"engines": {
"node": ">=12"

View File

@@ -173,27 +173,30 @@ function genericStrategy(adminApp,strategy) {
adminApp.use(passport.session());
var options = strategy.options;
var verify = function() {
var originalDone = arguments[arguments.length-1];
if (options.verify) {
var args = Array.from(arguments);
args[args.length-1] = function(err,profile) {
if (err) {
return originalDone(err);
} else {
return completeVerify(profile,originalDone);
}
};
passport.use(new strategy.strategy(options,
function() {
var originalDone = arguments[arguments.length-1];
if (options.verify) {
var args = Array.from(arguments);
args[args.length-1] = function(err,profile) {
if (err) {
return originalDone(err);
} else {
return completeVerify(profile,originalDone);
}
};
options.verify.apply(null,args);
} else {
var profile = arguments[arguments.length - 2];
return completeVerify(profile,originalDone);
}
options.verify.apply(null,args);
} else {
var profile = arguments[arguments.length - 2];
return completeVerify(profile,originalDone);
}
));
};
// Give our callback the same arity as the original one from options
if (options.verify) {
Object.defineProperty(verify, "length", { value: options.verify.length })
}
passport.use(new strategy.strategy(options, verify));
adminApp.get('/auth/strategy',
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "2.0.2",
"version": "2.0.6",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "2.0.2",
"@node-red/editor-client": "2.0.2",
"@node-red/util": "2.0.6",
"@node-red/editor-client": "2.0.6",
"bcryptjs": "2.4.3",
"body-parser": "1.19.0",
"clone": "2.1.2",
@@ -26,7 +26,7 @@
"express": "4.17.1",
"memorystore": "1.6.6",
"mime": "2.5.2",
"multer": "1.4.2",
"multer": "1.4.3",
"mustache": "4.2.0",
"oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1",

View File

@@ -56,7 +56,7 @@
"displayConfig": "설정노드 보기",
"import": "가져오기",
"export": "내보내기",
"search": "플로우 색",
"search": "플로우 색",
"searchInput": "플로우 검색",
"subflows": "보조 플로우",
"createSubflow": "보조 플로우 생성",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "2.0.2",
"version": "2.0.6",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -664,6 +664,8 @@ RED.tabs = (function() {
link.on("dblclick", function(evt) { evt.stopPropagation(); evt.preventDefault(); })
$('<span class="red-ui-tabs-fade"></span>').appendTo(li);
if (tab.closeable) {
li.addClass("red-ui-tabs-closeable")
var closeLink = $("<a/>",{href:"#",class:"red-ui-tab-close"}).appendTo(li);
@@ -674,8 +676,6 @@ RED.tabs = (function() {
});
}
$('<span class="red-ui-tabs-fade"></span>').appendTo(li);
var badges = $('<span class="red-ui-tabs-badges"></span>').appendTo(li);
if (options.onselect) {
$('<i class="red-ui-tabs-badge-changed fa fa-circle"></i>').appendTo(badges);

View File

@@ -407,8 +407,10 @@ RED.editor = (function() {
*/
function updateNodeCredentials(node, credDefinition, prefix) {
var changed = false;
if(!node.credentials) {
if (!node.credentials) {
node.credentials = {_:{}};
} else if (!node.credentials._) {
node.credentials._ = {};
}
for (var cred in credDefinition) {

View File

@@ -31,7 +31,7 @@
function setMode(mode, cb)
function getRange();
function replace(range, text)
function selectAll
function selectAll
function clearSelection
function getSelectedText()
function destroy()
@@ -153,9 +153,9 @@ RED.editor.codeEditor.monaco = (function() {
function init(options) {
//Handles orphaned models
//Handles orphaned models
//ensure loaded models that are not explicitly destroyed by a call to .destroy() are disposed
RED.events.on("editor:close",function() {
RED.events.on("editor:close",function() {
let models = window.monaco ? monaco.editor.getModels() : null;
if(models && models.length) {
console.warn("Cleaning up monaco models left behind. Any node that calls createEditor() should call .destroy().")
@@ -744,10 +744,10 @@ RED.editor.codeEditor.monaco = (function() {
//by default, set javascript editors to text mode.
//when element becomes visible, it will be (re) set to javascript mode
//this is to ensure multiple editors sharing the model dont present its
//this is to ensure multiple editors sharing the model dont present its
//consts & lets to each other
if(editorOptions.language == "javascript") {
editorOptions._language = editorOptions.language;
editorOptions._language = editorOptions.language;
editorOptions.language = "text"
}
@@ -921,6 +921,15 @@ RED.editor.codeEditor.monaco = (function() {
/*********** Create the monaco editor ***************/
var ed = monaco.editor.create(el, editorOptions);
//Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray.
try {
ed._standaloneKeybindingService.addDynamicKeybinding(
'-editor.action.insertLineAfter', // command ID prefixed by '-'
null, // keybinding
() => {} // need to pass an empty handler
);
} catch (error) { }
ed.nodered = {
refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh
}
@@ -967,7 +976,7 @@ RED.editor.codeEditor.monaco = (function() {
if (cb && typeof cb == "function") {
cb();
}
if(resize) {
if(resize) {
this.resize(); //cause a call to layout()
}
}
@@ -1218,7 +1227,7 @@ RED.editor.codeEditor.monaco = (function() {
}
ed._mode = editorOptions.language;
//as models are signleton, consts and let are avialable to other javascript instances
//as models are signleton, consts and let are avialable to other javascript instances
//so when not focused, set editor mode to text temporarily to avoid multiple defs
if(editorOptions._language) {
@@ -1240,15 +1249,15 @@ RED.editor.codeEditor.monaco = (function() {
try {
var options = {
root: $(element).closest("div.red-ui-tray-content")[0] || document,
attributes: true,
childList: true,
attributes: true,
childList: true,
};
var observer = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
callback(entry.intersectionRatio > 0, 5, entry.target);
});
}, options);
observer.observe(element);
observer.observe(element);
} catch (e1) {
//browser not supporting IntersectionObserver? then fall back to polling!
try {
@@ -1267,7 +1276,7 @@ RED.editor.codeEditor.monaco = (function() {
function onVisibilityChange(visible, delay, element) {
if(visible) {
if(ed._mode == "javascript" && ed._tempMode == "text") {
ed._tempMode = "";
ed._tempMode = "";
setTimeout(function() {
if(element.parentElement) { //ensure el is still in DOM
ed.setMode('javascript', undefined, false);
@@ -1277,7 +1286,7 @@ RED.editor.codeEditor.monaco = (function() {
} else if(ed._mode == "javascript" && ed._tempMode != "text") {
if(element.parentElement) { //ensure el is still in DOM
ed.setMode('text', undefined, false);
ed._tempMode = "text";
ed._tempMode = "text";
}
}
}
@@ -1353,4 +1362,4 @@ RED.editor.codeEditor.monaco = (function() {
*/
create: create
}
})();
})();

View File

@@ -104,7 +104,7 @@
}
.red-ui-tabs-fade {
background-image: linear-gradient(to right, rgba(255,255,255,0.001), $tab-background-active);
background-image: linear-gradient(to right, change-color($tab-background-active, $alpha: 0.001), $tab-background-active);
}
}
@@ -112,7 +112,7 @@
&:not(.active) {
background: $tab-background-selected;
.red-ui-tabs-fade {
background-image: linear-gradient(to right, rgba(255,255,255,0.001), $tab-background-selected);
background-image: linear-gradient(to right, change-color($tab-background-selected, $alpha: 0.001), $tab-background-selected);
}
.red-ui-tabs-badge-selected {
background: $tab-background-selected;
@@ -131,6 +131,9 @@
&:not(.active) a:hover {
color: $workspace-button-color-hover;
background: $tab-background-hover;
&+.red-ui-tabs-fade {
background-image: linear-gradient(to right, change-color($tab-background-hover, $alpha: 0.001), $tab-background-hover);
}
}
}
}
@@ -308,7 +311,7 @@
top: 0;
right: 0;
width: 15px;
background-image: linear-gradient(to right, rgba(255,255,255,0.001), $tab-background-inactive);
background-image: linear-gradient(to right, change-color($tab-background-inactive, $alpha: 0.001), $tab-background-inactive);
pointer-events: none;
}

View File

@@ -101,6 +101,9 @@
}
.red-ui-treeList-label-text {
margin-left: 4px;
&:empty {
min-height: 20px;
}
}
.red-ui-treeList-sublabel-text {
top: 0;

View File

@@ -193,7 +193,7 @@
}
/** Perform inject, optionally sending a custom msg (refactored for re-use in the form inject button)*/
function doInject(node, customMsg) {
var label = node._def.label.call(node);
var label = node._def.label.call(node,customMsg?customMsg.__user_inject_props__:undefined);
if (label.length > 30) {
label = label.substring(0, 50) + "...";
}
@@ -201,7 +201,8 @@
$.ajax({
url: "inject/" + node.id,
type: "POST",
data: customMsg,
data: JSON.stringify(customMsg||{}),
contentType: "application/json; charset=utf-8",
success: function (resp) {
RED.notify(node._("inject.success", { label: label }), { type: "success", id: "inject", timeout: 2000 });
},
@@ -291,7 +292,7 @@
}
return lab;
},
label: function() {
label: function(customProps) {
var suffix = "";
// if fire once then add small indication
if (this.once) {
@@ -304,11 +305,23 @@
if (this.name) {
return this.name+suffix;
}
var payload = this.payload || "";
var payloadType = this.payloadType || "str";
var topic = this.topic || "";
var payload = "";
var payloadType = "str";
var topic = "";
if (customProps) {
for (var i=0;i<customProps.length;i++) {
if (customProps[i].p === "payload") {
payload = customProps[i].v;
payloadType = customProps[i].vt;
} else if (customProps[i].p === "topic") {
topic = customProps[i].v;
}
}
} else {
payload = this.payload || "";
payloadType = this.payloadType || "str";
topic = this.topic || "";
}
if (payloadType === "string" ||
payloadType === "str" ||
payloadType === "num" ||
@@ -496,11 +509,8 @@
label: node._("inject.injectNow"),
click: function(e) {
var items = eList.editableList('items');
var result = getProps(items);
var m = {__user_inject_props__: []};
if (result && result.props && result.props.length) {
m.__user_inject_props__ = result.props;
}
var props = getProps(items);
var m = {__user_inject_props__: props.props};
doInject(node, m);
}
}

View File

@@ -101,7 +101,7 @@ module.exports = function(RED) {
this.on("input", function(msg, send, done) {
var errors = [];
var props = this.props;
if(msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
if (msg.__user_inject_props__ && Array.isArray(msg.__user_inject_props__)) {
props = msg.__user_inject_props__;
}
delete msg.__user_inject_props__;

View File

@@ -27,10 +27,14 @@
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row">
<div class="form-row" style="margin-bottom: 0px;">
<label> </label>
<input type="checkbox" id="node-input-septopics" style="display:inline-block; width:20px; vertical-align:baseline;">
<span data-i18n="rbe.label.septopics"></span> <input type="text" id="node-input-topi" style="width:27%;"/>
<label style="width: auto" for="node-input-septopics" data-i18n="rbe.label.septopics"></label>
</div>
<div class="form-row">
<label> </label>
<input type="text" id="node-input-topi" style="width:70%;"/>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="rbe.label.name"></span></label>
@@ -70,6 +74,10 @@
if (this.septopics === undefined) {
$("#node-input-septopics").prop('checked', true);
}
if (this.topi === undefined) {
$("#node-input-topi").val("topic");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
$("#node-input-topi").typedInput({default:'msg',types:['msg']});
//$( "#node-input-gap" ).spinner({min:0});
@@ -88,6 +96,11 @@
$("#node-startvalue").hide();
}
});
$("#node-input-septopics").on("change", function() {
$("#node-input-topi").typedInput("disable",!this.checked);
})
$("#node-input-topi").typedInput("disable",!!!this.septopics);
}
});
</script>

View File

@@ -135,6 +135,28 @@ in your Node-RED user directory (${RED.settings.userDir}).
}
}
// The Request module used in Node-RED 1.x was tolerant of query strings that
// were partially encoded. For example - "?a=hello%20there&b=20%"
// The GOT module doesn't like that.
// The following is an attempt to normalise the url to ensure it is properly
// encoded. We cannot just encode it directly as we don't want any valid
// encoded entity to end up doubly encoded.
if (url.indexOf("?") > -1) {
// Only do this if there is a query string to deal with
const [hostPath, ...queryString] = url.split("?")
const query = queryString.join("?");
if (query) {
// Look for any instance of % not followed by two hex chars.
// Replace any we find with %25.
const escapedQueryString = query.replace(/(%.?.?)/g, function(v) {
if (/^%[a-f0-9]{2}/i.test(v)) {
return v;
}
return v.replace(/%/,"%25")
})
url = hostPath+"?"+escapedQueryString;
}
}
var method = nodeMethod.toUpperCase() || "GET";
if (msg.method && n.method && (n.method !== "use")) { // warn if override option not set
@@ -151,6 +173,9 @@ in your Node-RED user directory (${RED.settings.userDir}).
// Had to remove this to get http->https redirect to work
// opts.defaultPort = isHttps?443:80;
opts.timeout = node.reqTimeout;
opts.throwHttpErrors = false;
// TODO: add UI option to auto decompress. Setting to false for 1.x compatibility
opts.decompress = false;
opts.method = method;
opts.headers = {};
opts.retry = 0;
@@ -168,8 +193,23 @@ in your Node-RED user directory (${RED.settings.userDir}).
opts.timeout = msg.requestTimeout;
}
}
const originalHeaderMap = {};
opts.hooks = {
beforeRequest: [
options => {
// Whilst HTTP headers are meant to be case-insensitive,
// in the real world, there are servers that aren't so compliant.
// GOT will lower case all headers given a chance, so we need
// to restore the case of any headers the user has set.
Object.keys(options.headers).forEach(h => {
if (originalHeaderMap[h] && originalHeaderMap[h] !== h) {
options.headers[originalHeaderMap[h]] = options.headers[h];
delete options.headers[h];
}
})
}
],
beforeRedirect: [
(options, response) => {
let redirectInfo = {
@@ -280,7 +320,7 @@ in your Node-RED user directory (${RED.settings.userDir}).
})
if (normalisedHeaders['www-authenticate']) {
let authHeader = buildDigestHeader(digestCreds.user,digestCreds.password, options.method, requestUrl.pathname, normalisedHeaders['www-authenticate'])
options.headers.authorization = authHeader;
options.headers.Authorization = authHeader;
}
sentCreds = true;
return retry(options);
@@ -431,11 +471,27 @@ in your Node-RED user directory (${RED.settings.userDir}).
if (tlsNode) {
opts.https = {};
tlsNode.addTLSOptions(opts.https);
if (opts.https.ca) {
opts.https.certificateAuthority = opts.https.ca;
delete opts.https.ca;
}
if (opts.https.cert) {
opts.https.certificate = opts.https.cert;
delete opts.https.cert;
}
} else {
if (msg.hasOwnProperty('rejectUnauthorized')) {
opts.https = { rejectUnauthorized: msg.rejectUnauthorized };
}
}
// Now we have established all of our own headers, take a snapshot
// of their case so we can restore it prior to the request being sent.
if (opts.headers) {
Object.keys(opts.headers).forEach(h => {
originalHeaderMap[h.toLowerCase()] = h
})
}
got(url,opts).then(res => {
msg.statusCode = res.statusCode;
msg.headers = res.headers;

View File

@@ -372,12 +372,13 @@ module.exports = function(RED) {
socket.setKeepAlive(true,120000);
if (socketTimeout !== null) { socket.setTimeout(socketTimeout); }
node.log(RED._("tcpin.status.connection-from",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.push(socket);
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
socket.on('timeout', function() {
node.log(RED._("tcpin.errors.timeout",{port:node.port}));
socket.end();
});
socket.on('data', function(d) {
// console.log("DATA",d)
});
socket.on('close',function() {
node.log(RED._("tcpin.status.connection-closed",{host:socket.remoteAddress, port:socket.remotePort}));
connectedSockets.splice(connectedSockets.indexOf(socket),1);
@@ -388,6 +389,8 @@ module.exports = function(RED) {
connectedSockets.splice(connectedSockets.indexOf(socket),1);
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
});
connectedSockets.push(socket);
node.status({text:RED._("tcpin.status.connections",{count:connectedSockets.length})});
});
node.on("input", function(msg, nodeSend, nodeDone) {

View File

@@ -32,7 +32,7 @@ module.exports = function(RED) {
var tag = node.tag;
if (msg.hasOwnProperty("select")) { tag = node.tag || msg.select; }
try {
var $ = cheerio.load(value,null,false);
var $ = cheerio.load(value);
var pay = [];
var count = 0;
$(tag).each(function() {

View File

@@ -629,9 +629,6 @@ module.exports = function(RED) {
if (node.build === 'object') {
propertyKey = RED.util.getMessageProperty(msg,node.key);
}
if (msg.hasOwnProperty("parts")) {
propertyIndex = msg.parts.index;
}
}
if (msg.hasOwnProperty("reset")) {

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "2.0.2",
"version": "2.0.6",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -17,7 +17,7 @@
"dependencies": {
"acorn": "8.4.1",
"acorn-walk": "8.1.1",
"ajv": "8.6.0",
"ajv": "8.6.2",
"body-parser": "1.19.0",
"cheerio": "1.0.0-rc.10",
"content-type": "1.0.4",
@@ -37,7 +37,7 @@
"js-yaml": "3.14.1",
"media-typer": "1.1.0",
"mqtt": "4.2.8",
"multer": "1.4.2",
"multer": "1.4.3",
"mustache": "4.2.0",
"on-headers": "1.0.2",
"raw-body": "2.4.1",

View File

@@ -14,7 +14,7 @@
* limitations under the License.
**/
var fs = require('fs-extra');
var fs = require('fs');
var fspath = require('path');
var runtime;
@@ -25,7 +25,7 @@ var exampleFlows = null;
async function getFlowsFromPath(path) {
var result = {};
var validFiles = [];
return fs.readdir(path).then(files => {
return fs.promises.readdir(path).then(files => {
var promises = [];
if (files) {
files.forEach(function(file) {

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "2.0.2",
"version": "2.0.6",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,11 +16,11 @@
}
],
"dependencies": {
"@node-red/util": "2.0.2",
"@node-red/util": "2.0.6",
"clone": "2.1.2",
"fs-extra": "10.0.0",
"semver": "7.3.5",
"tar": "6.1.0",
"uglify-js": "3.13.10"
"tar": "6.1.11",
"uglify-js": "3.14.1"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "2.0.2",
"version": "2.0.6",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,9 +16,9 @@
}
],
"dependencies": {
"@node-red/registry": "2.0.2",
"@node-red/util": "2.0.2",
"async-mutex": "0.3.1",
"@node-red/registry": "2.0.6",
"@node-red/util": "2.0.6",
"async-mutex": "0.3.2",
"clone": "2.1.2",
"express": "4.17.1",
"fs-extra": "10.0.0",

View File

@@ -23,7 +23,7 @@
var i18n = require("i18next");
var path = require("path");
var fs = require("fs-extra");
var fs = require("fs");
var defaultLang = "en-US";
@@ -89,7 +89,7 @@ async function readFile(lng, ns) {
return resourceCache[ns][lng];
} else if (resourceMap[ns]) {
const file = path.join(resourceMap[ns].basedir, lng, resourceMap[ns].file);
const content = await fs.readFile(file, "utf8");
const content = await fs.promises.readFile(file, "utf8");
resourceCache[ns] = resourceCache[ns] || {};
resourceCache[ns][lng] = JSON.parse(content.replace(/^\uFEFF/, ''));
var baseLng = lng.split('-')[0];

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "2.0.2",
"version": "2.0.6",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -18,7 +18,7 @@
"fs-extra": "10.0.0",
"i18next": "20.3.2",
"json-stringify-safe": "5.0.1",
"jsonata": "1.8.4",
"jsonata": "1.8.5",
"lodash.clonedeep": "^4.5.0",
"moment-timezone": "0.5.33"
}

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "2.0.2",
"version": "2.0.6",
"description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "2.0.2",
"@node-red/runtime": "2.0.2",
"@node-red/util": "2.0.2",
"@node-red/nodes": "2.0.2",
"@node-red/editor-api": "2.0.6",
"@node-red/runtime": "2.0.6",
"@node-red/util": "2.0.6",
"@node-red/nodes": "2.0.6",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.17.1",

View File

@@ -234,8 +234,13 @@ httpsPromise.then(function(startupHttps) {
// Get the result of the function, because createServer doesn't accept functions as input
Promise.resolve(settings.https()).then(function(refreshedHttps) {
if (refreshedHttps) {
// The key/cert needs to be updated in the NodeJs http(s) server, when no key/cert is yet available or when the key/cert has changed.
// Note that the refreshed key/cert can be supplied as a string or a buffer.
var updateKey = (server.key == undefined || (Buffer.isBuffer(server.key) && !server.key.equals(refreshedHttps.key)) || (typeof server.key == "string" && server.key != refreshedHttps.key));
var updateCert = (server.cert == undefined || (Buffer.isBuffer(server.cert) && !server.cert.equals(refreshedHttps.cert)) || (typeof server.cert == "string" && server.cert != refreshedHttps.cert));
// Only update the credentials in the server when key or cert has changed
if(!server.key || !server.cert || !server.key.equals(refreshedHttps.key) || !server.cert.equals(refreshedHttps.cert)) {
if(updateKey || updateCert) {
server.setSecureContext(refreshedHttps);
RED.log.info(RED.log._("server.https.settings-refreshed"));
}

View File

@@ -4,6 +4,8 @@ const path = require("path");
const fs = require("fs-extra");
const should = require("should");
const LATEST = "2";
function generateScript() {
return new Promise((resolve, reject) => {
const packages = [
@@ -18,7 +20,13 @@ function generateScript() {
const rootPackage = require(path.join(__dirname,"..","package.json"));
const version = rootPackage.version;
const tagArg = /-/.test(version) ? "--tag next" : ""
const versionParts = version.split(".");
let tagArg = "";
if (versionParts[0] !== LATEST) {
tagArg = `--tag v${versionParts[0]}-maintenance`
} else if (/-/.test(version)) {
tagArg = "--tag next"
}
const lines = [];

View File

@@ -42,6 +42,8 @@ describe('HTTP Request Node', function() {
var testProxyPort = 10444;
var testProxyServerAuth;
var testProxyAuthPort = 10554;
var testSslClientServer;
var testSslClientPort = 10664;
//save environment variables
var preEnvHttpProxyLowerCase;
@@ -57,6 +59,7 @@ describe('HTTP Request Node', function() {
testServer = stoppable(http.createServer(testApp));
testServer.listen(testPort,function(err) {
testSslPort += 1;
console.log("ssl port", testSslPort);
var sslOptions = {
key: fs.readFileSync('test/resources/ssl/server.key'),
cert: fs.readFileSync('test/resources/ssl/server.crt')
@@ -75,11 +78,29 @@ describe('HTTP Request Node', function() {
*/
};
testSslServer = stoppable(https.createServer(sslOptions,testApp));
testSslServer.listen(testSslPort);
testSslServer.listen(testSslPort, function(err){
if (err) {
console.log(err);
} else {
console.log("started testSslServer");
}
});
testSslClientPort += 1;
var sslClientOptions = {
key: fs.readFileSync('test/resources/ssl/server.key'),
cert: fs.readFileSync('test/resources/ssl/server.crt'),
ca: fs.readFileSync('test/resources/ssl/server.crt'),
requestCert: true
};
testSslClientServer = stoppable(https.createServer(sslClientOptions, testApp));
testSslClientServer.listen(testSslClientPort, function(err){
console.log("ssl-client", err)
});
testProxyPort += 1;
testProxyServer = stoppable(httpProxy(http.createServer()))
testProxyServer = stoppable(httpProxy(http.createServer()))
testProxyServer.on('request', function(req,res){
if (!res.headersSent) {
res.setHeader("x-testproxy-header", "foobar")
@@ -121,6 +142,10 @@ describe('HTTP Request Node', function() {
return "https://localhost:"+testSslPort+url;
}
function getSslClientTestURL(url) {
return "https://localhost:"+testSslClientPort+url;
}
function getDifferentTestURL(url) {
return "http://127.0.0.1:"+testPort+url;
}
@@ -267,6 +292,27 @@ describe('HTTP Request Node', function() {
url: req.originalUrl
});
})
testApp.get('/returnError/:code', function(req,res) {
res.status(parseInt(req.params.code)).json({gotError:req.params.code});
})
testApp.get('/rawHeaders', function(req,res) {
const result = {};
for (let i=0;i<req.rawHeaders.length;i++) {
result[req.rawHeaders[i]] = req.rawHeaders[i+1]
}
res.json({
headers:result
});
})
testApp.get('/getClientCert', function(req,res) {
if (req.client.authorized) {
res.send('hello');
} else {
res.status(401).send();
}
})
startServer(function(err) {
if (err) {
done(err);
@@ -280,7 +326,9 @@ describe('HTTP Request Node', function() {
testProxyServer.stop(() => {
testProxyServerAuth.stop(() => {
testSslServer.stop(() => {
helper.stopServer(done);
testSslClientServer.stop(() => {
helper.stopServer(done);
})
});
});
});
@@ -1044,8 +1092,6 @@ describe('HTTP Request Node', function() {
n1.receive({payload:"foo", requestTimeout: 100});
});
});
it('should append query params to url - obj', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",paytoqs:true,ret:"obj",url:getTestURL('/getQueryParams')},
{id:"n2", type:"helper"}];
@@ -1068,6 +1114,84 @@ describe('HTTP Request Node', function() {
n1.receive({payload:{a:1,b:2,c:3}});
});
});
it('should send a msg for non-2xx response status - 400', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/returnError/400')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload',{ gotError: '400' });
msg.should.have.property('statusCode',400);
done();
} catch(err) {
done(err);
}
});
n1.receive({});
})
});
it('should send a msg for non-2xx response status - 404', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/returnError/404')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload',{ gotError: '404' });
msg.should.have.property('statusCode',404);
done();
} catch(err) {
done(err);
}
});
n1.receive({});
})
});
it('should send a msg for non-2xx response status - 500', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/returnError/500')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload',{ gotError: '500' });
msg.should.have.property('statusCode',500);
done();
} catch(err) {
done(err);
}
});
n1.receive({});
})
});
it('should encode the url to handle special characters', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj"},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload',{
query:{ a: 'b', c:[ 'T24,0°|H80%|W S8,3m/s' ] },
url: '/getQueryParams?a=b&c[0].Text=T24,0%C2%B0|H80%25|W%20S8,3m/s'
});
msg.should.have.property('statusCode',200);
msg.should.have.property('headers');
done();
} catch(err) {
done(err);
}
});
n1.receive({url: getTestURL('/getQueryParams')+"?a=b&c[0].Text=T24,0°|H80%|W%20S8,3m/s"});
});
})
});
describe('HTTP header', function() {
@@ -1269,6 +1393,8 @@ describe('HTTP Request Node', function() {
});
it('should convert all HTTP headers into lower case', function(done) {
// This is a bad test. Express lower-cases headers in the `req.headers` object,
// so this is actually testing express, not the original request.
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
@@ -1290,6 +1416,26 @@ describe('HTTP Request Node', function() {
});
});
it('should keep HTTP header case as provided by the user', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/rawHeaders')},
{id:"n2", type:"helper"}];
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property('statusCode',200);
msg.payload.should.have.property('headers');
msg.payload.headers.should.have.property('Content-Type').which.startWith('text/plain');
msg.payload.headers.should.have.property('X-Test-HEAD', "foo");
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo", headers: { 'Content-Type':'text/plain', "X-Test-HEAD": "foo"}});
});
});
it('should receive HTTP header', function(done) {
var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/headersInspect')},
{id:"n2", type:"helper"}];
@@ -1407,6 +1553,60 @@ describe('HTTP Request Node', function() {
});
});
it('should use tls-config and verify serverCert', function(done) {
var flow = [
{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getSslTestURL('/text'),tls:"n3"},
{id:"n2", type:"helper"},
{id:"n3", type:"tls-config", cert:"test/resources/ssl/server.crt", key:"test/resources/ssl/server.key", ca:"test/resources/ssl/server.crt", verifyservercert:true}];
var testNodes = [httpRequestNode, tlsNode];
helper.load(testNodes, flow, function() {
var n3 = helper.getNode("n3");
var n2 = helper.getNode("n2");
var n1 = helper.getNode("n1");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload','hello');
msg.should.have.property('statusCode',200);
msg.should.have.property('headers');
msg.headers.should.have.property('content-length',''+('hello'.length));
msg.headers.should.have.property('content-type').which.startWith('text/html');
msg.should.have.property('responseUrl').which.startWith('https://');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
});
});
it('should use tls-config and send client cert', function(done) {
var flow = [
{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getSslClientTestURL('/getClientCert'),tls:"n3"},
{id:"n2", type:"helper"},
{id:"n3", type:"tls-config", cert:"test/resources/ssl/server.crt", key:"test/resources/ssl/server.key", ca:"test/resources/ssl/server.crt", verifyservercert:false}];
var testNodes = [httpRequestNode,tlsNode];
helper.load(testNodes, flow, function() {
var n3 = helper.getNode("n3");
var n2 = helper.getNode("n2");
var n1 = helper.getNode("n1");
n2.on("input", function(msg) {
try {
msg.should.have.property('payload','hello');
msg.should.have.property('statusCode',200);
msg.should.have.property('headers');
msg.headers.should.have.property('content-length',''+('hello'.length));
msg.headers.should.have.property('content-type').which.startWith('text/html');
msg.should.have.property('responseUrl').which.startWith('https://');
done();
} catch(err) {
done(err);
}
});
n1.receive({payload:"foo"});
})
});
//Removing HTTP Proxy testcases as GOT + Proxy_Agent doesn't work with mock'd proxy
/* */
it('should use http_proxy', function(done) {
@@ -1600,7 +1800,7 @@ describe('HTTP Request Node', function() {
n1.receive({payload:"foo"});
});
});
});
describe('authentication', function() {
@@ -1664,7 +1864,7 @@ describe('HTTP Request Node', function() {
});
});
// Removed the Proxy Tests until a new mock proxy can be replaced with
// Removed the Proxy Tests until a new mock proxy can be replaced with
// one that supports HTTP Connect verb
/* */
it('should authenticate on proxy server', function(done) {
@@ -1771,7 +1971,7 @@ describe('HTTP Request Node', function() {
});
});
*/
});
describe('file-upload', function() {

View File

@@ -68,7 +68,9 @@ describe('HTML node', function() {
done(err)
}
});
n1.receive({payload:data,topic:"bar",select:"h1"});
// include 'body' in the select to verify we're in document mode
// for the parser. See https://github.com/node-red/node-red/issues/3079
n1.receive({payload:data,topic:"bar",select:"body h1"});
});
});
});

View File

@@ -516,6 +516,28 @@ describe('JOIN node', function() {
n1.receive({payload:{a:1}});
});
});
it('should join things into an array ignoring msg.parts.index in manual mode', function(done) {
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joiner:",",mode:"custom"},
{id:"n2", type:"helper"}];
helper.load(joinNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
try {
msg.should.have.property("payload");
msg.payload.should.be.an.Array();
msg.payload[0].should.equal(1);
msg.payload[1].should.equal(true);
//msg.payload[2].a.should.equal(1);
done();
}
catch(e) {done(e);}
});
n1.receive({payload:1, parts: {index: 3}});
n1.receive({payload:true, parts: {index: 0}});
n1.receive({payload:{a:1}, parts: {index: 9}});
});
});
it('should join things into an array after a count with a buffer join set', function(done) {
var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joinerType:"bin", joiner:"" ,mode:"custom"},
@@ -1646,7 +1668,7 @@ describe('JOIN node', function() {
});
});
it('should handle join an array when using msg.parts and duplicate indexed parts arrive', function (done) {
it('should handle join an array when mode is auto and duplicate indexed parts arrive', function (done) {
var flow = [{ id: "n1", type: "join", wires: [["n2"]], joiner: "[44]", joinerType: "bin", build: "array", mode: "auto" },
{ id: "n2", type: "helper" }];
helper.load(joinNode, flow, function () {