node-red-contrib-hik-media-buffer 1.1.86 → 1.1.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hik-media-buffer.js +35 -32
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -30,82 +30,85 @@ module.exports = function(RED) {
|
|
|
30
30
|
|
|
31
31
|
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
32
32
|
|
|
33
|
-
function toHikDate(d) {
|
|
34
|
-
const pad = (num) => String(num).padStart(2, '0');
|
|
35
|
-
|
|
36
|
-
// Estraiamo i componenti dell'ora LOCALE della macchina su cui gira Node-RED
|
|
37
|
-
const year = d.getFullYear();
|
|
38
|
-
const month = pad(d.getMonth() + 1);
|
|
39
|
-
const day = pad(d.getDate());
|
|
40
|
-
const hours = pad(d.getHours());
|
|
41
|
-
const minutes = pad(d.getMinutes());
|
|
42
|
-
const seconds = pad(d.getSeconds());
|
|
43
|
-
|
|
44
|
-
// Formato finale richiesto dagli NVR Hikvision: YYYY-MM-DDTHH:mm:ss (SENZA "Z" E SENZA OFFSET!)
|
|
45
|
-
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
46
|
-
}
|
|
33
|
+
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
47
34
|
|
|
48
35
|
// Manteniamo AxiosDigest solo per lo stream degli allarmi e lo stato dei canali (dove va da dio)
|
|
49
36
|
const nvrAuthAxios = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
50
37
|
|
|
51
38
|
// --- FUNZIONE AUSILIARIA PER FARE LA POST DIGEST CON HTTP NATIVO (ELIMINA IL 400) ---
|
|
39
|
+
// --- FUNZIONE DEFINITIVA PER LA POST DIGEST CON CALCOLO INTEGRITÀ DEL BODY ---
|
|
52
40
|
function nativeDigestPost(urlPath, xmlBody) {
|
|
53
41
|
return new Promise((resolve, reject) => {
|
|
54
|
-
// Primo invio a vuoto per scatenare il 401 dell'handshake Digest
|
|
55
42
|
const options = {
|
|
56
43
|
hostname: node.host,
|
|
57
44
|
port: node.port,
|
|
58
45
|
path: urlPath,
|
|
59
46
|
method: 'POST',
|
|
60
|
-
headers: {
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/xml; charset=UTF-8',
|
|
49
|
+
'Content-Length': Buffer.byteLength(xmlBody, 'utf8')
|
|
50
|
+
}
|
|
61
51
|
};
|
|
62
52
|
|
|
53
|
+
// Primo invio a vuoto per scatenare il 401 dell'NVR
|
|
63
54
|
const req = http.request(options, (res) => {
|
|
64
55
|
let chunks = [];
|
|
65
56
|
res.on('data', (chunk) => chunks.push(chunk));
|
|
66
57
|
res.on('end', () => {
|
|
67
58
|
const body = Buffer.concat(chunks).toString();
|
|
68
59
|
|
|
69
|
-
// Se l'NVR risponde 401 (normale al primo colpo), estraiamo i parametri Digest
|
|
70
60
|
if (res.statusCode === 401) {
|
|
71
61
|
const authHeader = res.headers['www-authenticate'];
|
|
72
62
|
if (!authHeader) return reject(new Error("Manca header WWW-Authenticate"));
|
|
73
63
|
|
|
74
|
-
// Estrazione parametri
|
|
64
|
+
// Estrazione parametri di sicurezza
|
|
75
65
|
const realm = authHeader.match(/realm="([^"]+)"/)[1];
|
|
76
66
|
const nonce = authHeader.match(/nonce="([^"]+)"/)[1];
|
|
77
67
|
const qopMatch = authHeader.match(/qop="([^"]+)"/);
|
|
78
68
|
const qop = qopMatch ? qopMatch[1] : null;
|
|
79
69
|
|
|
80
|
-
// Calcolo degli Hash MD5 standard dell'handshake (Come fa Chrome!)
|
|
81
70
|
const crypto = require('crypto');
|
|
82
|
-
const md5 = (str) => crypto.createHash('md5').update(str).digest('hex');
|
|
71
|
+
const md5 = (str) => crypto.createHash('md5').update(str, 'utf8').digest('hex');
|
|
83
72
|
|
|
73
|
+
// 1. Calcolo HA1 base
|
|
84
74
|
const ha1 = md5(`${node.user}:${realm}:${node.pass}`);
|
|
85
|
-
const ha2 = md5(`POST:${urlPath}`);
|
|
86
75
|
|
|
87
|
-
|
|
76
|
+
// 2. Calcolo HA2: SE L'NVR CHIEDE AUTH-INT, INSERIAMO L'HASH DEL BODY XML!
|
|
77
|
+
let ha2;
|
|
78
|
+
if (qop && qop.includes('auth-int')) {
|
|
79
|
+
const bodyHash = md5(xmlBody);
|
|
80
|
+
ha2 = md5(`POST:${urlPath}:${bodyHash}`);
|
|
81
|
+
} else {
|
|
82
|
+
ha2 = md5(`POST:${urlPath}`);
|
|
83
|
+
}
|
|
88
84
|
|
|
85
|
+
// 3. Generazione parametri finali dell'handshake
|
|
86
|
+
const cnonce = crypto.randomBytes(8).toString('hex');
|
|
87
|
+
const nc = "00000001";
|
|
88
|
+
let response;
|
|
89
|
+
let authString;
|
|
90
|
+
|
|
89
91
|
if (qop) {
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
authString = `Digest username="${node.user}", realm="${realm}", nonce="${nonce}", uri="${urlPath}", qop
|
|
92
|
+
// Se c'è il qop (auth o auth-int), usiamo la formula estesa
|
|
93
|
+
const actualQop = qop.includes('auth-int') ? 'auth-int' : 'auth';
|
|
94
|
+
response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:${actualQop}:${ha2}`);
|
|
95
|
+
authString = `Digest username="${node.user}", realm="${realm}", nonce="${nonce}", uri="${urlPath}", qop="${actualQop}", nc=${nc}, cnonce="${cnonce}", response="${response}"`;
|
|
94
96
|
} else {
|
|
95
|
-
|
|
97
|
+
// Altrimenti andiamo di Digest classico
|
|
98
|
+
response = md5(`${ha1}:${nonce}:${ha2}`);
|
|
96
99
|
authString = `Digest username="${node.user}", realm="${realm}", nonce="${nonce}", uri="${urlPath}", response="${response}"`;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
// Secondo invio definitivo con l'
|
|
102
|
+
// Secondo invio definitivo con l'header Authorization corretto al millesimo
|
|
100
103
|
const finalOptions = {
|
|
101
104
|
hostname: node.host,
|
|
102
105
|
port: node.port,
|
|
103
106
|
path: urlPath,
|
|
104
107
|
method: 'POST',
|
|
105
108
|
headers: {
|
|
106
|
-
'Content-Type': 'application/xml',
|
|
109
|
+
'Content-Type': 'application/xml; charset=UTF-8',
|
|
107
110
|
'Authorization': authString,
|
|
108
|
-
'Content-Length': Buffer.byteLength(xmlBody)
|
|
111
|
+
'Content-Length': Buffer.byteLength(xmlBody, 'utf8')
|
|
109
112
|
}
|
|
110
113
|
};
|
|
111
114
|
|
|
@@ -120,7 +123,7 @@ module.exports = function(RED) {
|
|
|
120
123
|
});
|
|
121
124
|
});
|
|
122
125
|
finalReq.on('error', (err) => reject(err));
|
|
123
|
-
finalReq.write(xmlBody);
|
|
126
|
+
finalReq.write(xmlBody, 'utf8');
|
|
124
127
|
finalReq.end();
|
|
125
128
|
} else {
|
|
126
129
|
resolve({ statusCode: res.statusCode, data: body });
|
|
@@ -128,7 +131,7 @@ module.exports = function(RED) {
|
|
|
128
131
|
});
|
|
129
132
|
});
|
|
130
133
|
req.on('error', (err) => reject(err));
|
|
131
|
-
req.write(xmlBody);
|
|
134
|
+
req.write(xmlBody, 'utf8');
|
|
132
135
|
req.end();
|
|
133
136
|
});
|
|
134
137
|
}
|