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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +35 -32
  2. package/package.json +1 -1
@@ -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: { 'Content-Type': 'application/xml', 'Content-Length': Buffer.byteLength(xmlBody) }
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 con regex
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
- let authString = `Digest username="${node.user}", realm="${realm}", nonce="${nonce}", uri="${urlPath}", response=""`;
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
- const cnonce = crypto.randomBytes(8).toString('hex');
91
- const nc = "00000001";
92
- const response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`);
93
- authString = `Digest username="${node.user}", realm="${realm}", nonce="${nonce}", uri="${urlPath}", qop=${qop}, nc=${nc}, cnonce="${cnonce}", response="${response}"`;
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
- const response = md5(`${ha1}:${nonce}:${ha2}`);
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'autenticazione calcolata al millimetro
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.86",
3
+ "version": "1.1.87",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",