node-red-contrib-hik-media-buffer 1.1.85 → 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 +34 -18
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -36,63 +36,79 @@ module.exports = function(RED) {
|
|
|
36
36
|
const nvrAuthAxios = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
37
37
|
|
|
38
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 ---
|
|
39
40
|
function nativeDigestPost(urlPath, xmlBody) {
|
|
40
41
|
return new Promise((resolve, reject) => {
|
|
41
|
-
// Primo invio a vuoto per scatenare il 401 dell'handshake Digest
|
|
42
42
|
const options = {
|
|
43
43
|
hostname: node.host,
|
|
44
44
|
port: node.port,
|
|
45
45
|
path: urlPath,
|
|
46
46
|
method: 'POST',
|
|
47
|
-
headers: {
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/xml; charset=UTF-8',
|
|
49
|
+
'Content-Length': Buffer.byteLength(xmlBody, 'utf8')
|
|
50
|
+
}
|
|
48
51
|
};
|
|
49
52
|
|
|
53
|
+
// Primo invio a vuoto per scatenare il 401 dell'NVR
|
|
50
54
|
const req = http.request(options, (res) => {
|
|
51
55
|
let chunks = [];
|
|
52
56
|
res.on('data', (chunk) => chunks.push(chunk));
|
|
53
57
|
res.on('end', () => {
|
|
54
58
|
const body = Buffer.concat(chunks).toString();
|
|
55
59
|
|
|
56
|
-
// Se l'NVR risponde 401 (normale al primo colpo), estraiamo i parametri Digest
|
|
57
60
|
if (res.statusCode === 401) {
|
|
58
61
|
const authHeader = res.headers['www-authenticate'];
|
|
59
62
|
if (!authHeader) return reject(new Error("Manca header WWW-Authenticate"));
|
|
60
63
|
|
|
61
|
-
// Estrazione parametri
|
|
64
|
+
// Estrazione parametri di sicurezza
|
|
62
65
|
const realm = authHeader.match(/realm="([^"]+)"/)[1];
|
|
63
66
|
const nonce = authHeader.match(/nonce="([^"]+)"/)[1];
|
|
64
67
|
const qopMatch = authHeader.match(/qop="([^"]+)"/);
|
|
65
68
|
const qop = qopMatch ? qopMatch[1] : null;
|
|
66
69
|
|
|
67
|
-
// Calcolo degli Hash MD5 standard dell'handshake (Come fa Chrome!)
|
|
68
70
|
const crypto = require('crypto');
|
|
69
|
-
const md5 = (str) => crypto.createHash('md5').update(str).digest('hex');
|
|
71
|
+
const md5 = (str) => crypto.createHash('md5').update(str, 'utf8').digest('hex');
|
|
70
72
|
|
|
73
|
+
// 1. Calcolo HA1 base
|
|
71
74
|
const ha1 = md5(`${node.user}:${realm}:${node.pass}`);
|
|
72
|
-
const ha2 = md5(`POST:${urlPath}`);
|
|
73
75
|
|
|
74
|
-
|
|
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
|
+
}
|
|
75
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
|
+
|
|
76
91
|
if (qop) {
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
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}"`;
|
|
81
96
|
} else {
|
|
82
|
-
|
|
97
|
+
// Altrimenti andiamo di Digest classico
|
|
98
|
+
response = md5(`${ha1}:${nonce}:${ha2}`);
|
|
83
99
|
authString = `Digest username="${node.user}", realm="${realm}", nonce="${nonce}", uri="${urlPath}", response="${response}"`;
|
|
84
100
|
}
|
|
85
101
|
|
|
86
|
-
// Secondo invio definitivo con l'
|
|
102
|
+
// Secondo invio definitivo con l'header Authorization corretto al millesimo
|
|
87
103
|
const finalOptions = {
|
|
88
104
|
hostname: node.host,
|
|
89
105
|
port: node.port,
|
|
90
106
|
path: urlPath,
|
|
91
107
|
method: 'POST',
|
|
92
108
|
headers: {
|
|
93
|
-
'Content-Type': 'application/xml',
|
|
109
|
+
'Content-Type': 'application/xml; charset=UTF-8',
|
|
94
110
|
'Authorization': authString,
|
|
95
|
-
'Content-Length': Buffer.byteLength(xmlBody)
|
|
111
|
+
'Content-Length': Buffer.byteLength(xmlBody, 'utf8')
|
|
96
112
|
}
|
|
97
113
|
};
|
|
98
114
|
|
|
@@ -107,7 +123,7 @@ module.exports = function(RED) {
|
|
|
107
123
|
});
|
|
108
124
|
});
|
|
109
125
|
finalReq.on('error', (err) => reject(err));
|
|
110
|
-
finalReq.write(xmlBody);
|
|
126
|
+
finalReq.write(xmlBody, 'utf8');
|
|
111
127
|
finalReq.end();
|
|
112
128
|
} else {
|
|
113
129
|
resolve({ statusCode: res.statusCode, data: body });
|
|
@@ -115,7 +131,7 @@ module.exports = function(RED) {
|
|
|
115
131
|
});
|
|
116
132
|
});
|
|
117
133
|
req.on('error', (err) => reject(err));
|
|
118
|
-
req.write(xmlBody);
|
|
134
|
+
req.write(xmlBody, 'utf8');
|
|
119
135
|
req.end();
|
|
120
136
|
});
|
|
121
137
|
}
|