1 /* jshint browser: true */
2 /* global define, module */
3 ( // Module boilerplate to support browser globals and browserify and AMD.
4 typeof define === "function" ? function (m) { define("kismet-js", m); } :
5 typeof exports === "object" ? function (m) { module.exports = m(); } :
6 function(m){ this.kismet = m(); }
12 var local_uri_prefix = "";
13 if (typeof(KISMET_URI_PREFIX) !== 'undefined')
14 local_uri_prefix = KISMET_URI_PREFIX;
16 exports.timestamp_sec = 0;
17 exports.timestamp_usec = 0;
19 // Make a universal HTML5 storage handler
20 exports.storage = Storages.localStorage;
22 // Simple handler for getting stored values with defaults
23 exports.getStorage = function(key, def = undefined) {
24 if (exports.storage.isSet(key))
25 return exports.storage.get(key);
30 exports.putStorage = function(key, data) {
31 exports.storage.set(key, data);
34 // From http://stackoverflow.com/a/6491621
35 exports.ObjectByString = function(o, s) {
36 if (typeof(o) === 'undefined')
39 s = s.replace(/\[('?"?-?[\w:]+'?"?)\]/g, '\/$1');
40 s = s.replace(/^\//, '');
41 s = s.replace(/\/$/, '');
42 s = s.replace(/\/+/, '\/');
44 for (var i = 0, n = a.length; i < n; ++i) {
47 if (typeof(o) !== 'object')
60 exports.HumanReadableSize = function(sz) {
61 if (typeof(sz) === 'undefined')
64 if (typeof(sz) !== 'number')
69 } else if (sz < 1024 * 1024) {
70 return (sz / 1024).toFixed(2) + " KB";
71 } else if (sz < 1024 * 1024 * 1024) {
72 return (sz / 1024 / 1024).toFixed(2) + " MB";
73 } else if (sz < 1024 * 1024 * 1024 * 1024) {
74 return (sz / 1024 / 1024 / 1024).toFixed(2) + " GB";
80 exports.HumanReadableFrequency = function(f) {
81 // Kismet reports in *kHz* so all these values are scaled down by an order
85 else if (f < 1000 * 1000)
86 return (f / 1000).toFixed(3) + " MHz";
88 return (f / 1000 / 1000).toFixed(3) + " GHz";
91 // Modify a RRD minute record by fast-forwarding it to 'now', and optionally
92 // applying a transform function which could do something like average it
94 // Conversion factors / type definitions for RRD data arrays
95 exports.RRD_SECOND = 1;
96 exports.RRD_MINUTE = 60;
97 exports.RRD_HOUR = 3600;
99 // exports.RRD_DAY = 86400
101 exports.RecalcRrdData = function(start, now, type, data, opt = {}) {
102 if (data == undefined) {
103 if (type == exports.RRD_SECOND || type == exports.RRD_MINUTE) {
105 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
106 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
107 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
108 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
109 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
110 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
111 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
113 } else if (type == exports.RRD_HOUR) {
115 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
116 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
122 var rrd_len = data.length;
124 // Each type value is the number of seconds in each bin of the array
126 // A bin for a given time is (time / type) % len
128 // A completely expired RRD is (now - start) > (type * len) and should
129 // be filled with only zeroes
131 // To zero the array between "then" and "now", we simply calculate
132 // the bin for "then", the bin for "now", and increment-with-modulo
133 // until we reach "now".
135 // Adjusted data we return
136 var adj_data = new Array();
138 // Check if we're past the recording bounds of the rrd for this type, if we
139 // are, we don't have to do any shifting or any manipulation, we just fill
140 // the array with zeroes.
141 if ((now - start) > (type * rrd_len)) {
142 for (var ri = 0; ri < rrd_len; ri++) {
146 // Otherwise, we're valid inside the range of the array. We know we got
147 // no data between the time of the RRD and now, because if we had, the
148 // time would be more current. Figure out how many bins lie between
149 // 'then' and 'now', rescale the array to start at 'now', and fill
150 // in the time we got no data with zeroes
152 var start_bin = (Math.floor(start / type) % rrd_len) + 1;
153 var now_bin = (Math.floor(now / type) % rrd_len) + 1;
154 var sec_offt = Math.max(0, now - start);
157 console.log("we think we start in bin" + start_bin);
158 console.log("we think now is bin" + now_bin);
161 // Walk the entire array, starting with 'now', and copy zeroes
162 // when we fall into the blank spot between 'start' and 'now' when we
163 // know we received no data
164 for (var ri = 0; ri < rrd_len; ri++) {
165 var slot = (now_bin + ri) % rrd_len;
167 if (slot >= start_bin && slot < now_bin)
170 adj_data.push(data[slot]);
174 // If we have a transform function in the options, call it, otherwise
175 // return the shifted RRD entry
176 if ('transform' in opt && typeof(opt.transform) === 'function') {
179 if ('transformopt' in opt)
182 return opt.transform(adj_data, cbopt);
188 exports.RecalcRrdData2 = function(rrddata, type, opt = {}) {
191 if (type == exports.RRD_SECOND)
192 record = "kismet.common.rrd.minute_vec";
193 else if (type == exports.RRD_MINUTE)
194 record = "kismet.common.rrd.hour_vec";
195 else if (type == exports.RRD_HOUR)
196 record = "kismet.common.rrd.day_vec";
198 record = "kismet.common.rrd.minute_vec";
206 data = rrddata[record];
208 if (typeof(data) === 'number')
211 now = rrddata['kismet.common.rrd.serial_time'];
212 start = rrddata['kismet.common.rrd.last_time'];
217 if (type == exports.RRD_SECOND || type == exports.RRD_MINUTE) {
219 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
220 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
221 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
222 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
223 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
224 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
225 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
227 } else if (type == exports.RRD_HOUR) {
229 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
230 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
236 rrd_len = data.length;
238 // Each type value is the number of seconds in each bin of the array
240 // A bin for a given time is (time / type) % len
242 // A completely expired RRD is (now - start) > (type * len) and should
243 // be filled with only zeroes
245 // To zero the array between "then" and "now", we simply calculate
246 // the bin for "then", the bin for "now", and increment-with-modulo
247 // until we reach "now".
249 // Adjusted data we return
250 var adj_data = new Array();
252 // Check if we're past the recording bounds of the rrd for this type, if we
253 // are, we don't have to do any shifting or any manipulation, we just fill
254 // the array with zeroes.
255 if ((now - start) > (type * rrd_len)) {
256 for (var ri = 0; ri < rrd_len; ri++) {
260 // Otherwise, we're valid inside the range of the array. We know we got
261 // no data between the time of the RRD and now, because if we had, the
262 // time would be more current. Figure out how many bins lie between
263 // 'then' and 'now', rescale the array to start at 'now', and fill
264 // in the time we got no data with zeroes
266 var start_bin = (Math.floor(start / type) % rrd_len) + 1;
267 var now_bin = (Math.floor(now / type) % rrd_len) + 1;
268 var sec_offt = Math.max(0, now - start);
271 console.log("we think we start in bin" + start_bin);
272 console.log("we think now is bin" + now_bin);
275 // Walk the entire array, starting with 'now', and copy zeroes
276 // when we fall into the blank spot between 'start' and 'now' when we
277 // know we received no data
278 for (var ri = 0; ri < rrd_len; ri++) {
279 var slot = (now_bin + ri) % rrd_len;
281 if (slot >= start_bin && slot < now_bin)
284 adj_data.push(data[slot]);
288 // If we have a transform function in the options, call it, otherwise
289 // return the shifted RRD entry
290 if ('transform' in opt && typeof(opt.transform) === 'function') {
293 if ('transformopt' in opt)
296 return opt.transform(adj_data, cbopt);
302 exports.sanitizeId = function(s) {
303 return String(s).replace(/[:.&<>"'`=\/\(\)\[\] ]/g, function (s) {
308 exports.sanitizeHTML = function(s) {
320 return String(s).replace(/[&<>"'`=\/]/g, function (s) {
325 /* Censor a mac-like string, if the global censor_macs option is turned on; must be called by each
326 * display component */
327 exports.censorMAC = function(t) {
329 if (window['censor_macs'])
330 return t.replace(/([a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}):[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}/g, "$1:XX:XX:XX");
338 /* Censor a location by rounding */
339 exports.censorLocation = function(t) {
341 if (window['censor_location'])
342 return `${Math.round(t)}.XXXXX`;
350 // utf8 to unicode converter (used below in deoctalize()).
351 // "fatal: true" means that the converter will throw an
352 // exception if the input is not valid utf8.
353 exports.decoder = new TextDecoder('utf8', {fatal: true});
355 /* De-octalize an escaped string, and decode it from utf8.
356 * If the input string contains anything unexpected (control
357 * characters, invalid values after the backslash, character
358 * sequences that are not valid utf8), return the input string.
360 exports.deoctalize = function(str) {
361 var ret = new Array();
363 for (var i = 0; i < str.length; i++) {
364 // If the current character is not a backslash,
366 if (str[i] != '\\') {
367 ret.push(str.charCodeAt(i))
368 // If the current character (a backslash) is followed by a
369 // second backslash, remove the second backslash;
370 // no other modification needed.
371 } else if (i+1 < str.length && str[i+1] == '\\') {
372 ret.push(str.charCodeAt(i));
374 // If the backslash is followed by a 3-digit octal number
375 // in the range 000 to 377, replace the backslash and
376 // numerals by the corresponding octal character
377 } else if (i + 3 < str.length && str[i + 1] >= '0' && str[i+1] <= '3' &&
378 str[i + 2] >= '0' && str[i+2] <= '7' &&
379 str[i + 3] >= '0' && str[i+3] <= '7') {
382 ((str[i + 1] - '0') * 64) +
383 ((str[i + 2] - '0') * 8) +
384 ((str[i + 3] - '0'));
386 // If the octal character is less than 32 decimal,
387 // then it is a control (non-printing) character.
388 // In this case, dont' de-octalize the input string;
389 // immediately return the entire input string.
397 // This clause is reached only if a backslash was encountered,
398 // but the backslash was not followed by either another
399 // backslash or by 3 valid octal digits. This means that
400 // the input string is not a valid octalized string, so we
401 // don't know how to de-octalize it. In this case, return
409 // Try to convert the de-octalized string from utf8 to
411 return exports.decoder.decode(Uint8Array.from(ret))
413 // The de-octalized string was not valid utf8, so we don't
414 // know how to convert it. In this case, return the input
421 /* Recurse over a complete object (such as from json), finding all strings,
422 * and escaping them to be 'safe' */
423 exports.sanitizeObject = function(o) {
428 if (typeof(o) === 'string') {
429 return exports.sanitizeHTML(exports.deoctalize(o));
432 Object.keys(o).forEach(function(key) {
433 o[key] = exports.sanitizeObject(o[key]);
439 String.prototype.escapeSpecialChars = function() {
440 var s = this.replace(/\n/g, "\\n")
441 .replace(/\r/g, "\\r")
442 .replace(/\t/g, "\\t");
447 String.prototype.convertNewlines = function() {
448 var s = this.replace(/\\n/g, "\n");
449 s = s.replace(/\\r/g, "");