@yuneta/gobj-js 7.26.24 → 7.26.25

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.
@@ -869,12 +869,48 @@ var JSON_NUMBER_TOKEN = /-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/y;
869
869
  function is_surrogate_pair(high, low) {
870
870
  return high >= 55296 && high <= 56319 && low >= 56320 && low <= 57343;
871
871
  }
872
+ var JSON_MAX_DEPTH = 2048;
872
873
  function json_text_refused_by_c(s) {
873
874
  const n = s.length;
874
875
  let i = 0;
876
+ let depth = 0;
877
+ const in_object = [];
878
+ let key_next = false;
875
879
  while (i < n) {
876
880
  const c = s.charCodeAt(i);
881
+ if (c === 123 || c === 91) {
882
+ depth++;
883
+ if (depth > JSON_MAX_DEPTH) return true;
884
+ in_object.push(c === 123);
885
+ key_next = c === 123;
886
+ i++;
887
+ continue;
888
+ }
889
+ if (c === 125 || c === 93) {
890
+ depth--;
891
+ in_object.pop();
892
+ key_next = false;
893
+ i++;
894
+ continue;
895
+ }
896
+ if (c === 44) {
897
+ key_next = in_object.length > 0 && in_object[in_object.length - 1];
898
+ i++;
899
+ continue;
900
+ }
901
+ if (c === 58) {
902
+ key_next = false;
903
+ i++;
904
+ continue;
905
+ }
906
+ if (c === 116 || c === 102 || c === 110) {
907
+ if (depth + 1 > JSON_MAX_DEPTH) return true;
908
+ i += c === 102 ? 5 : 4;
909
+ continue;
910
+ }
877
911
  if (c === 34) {
912
+ if (!key_next && depth + 1 > JSON_MAX_DEPTH) return true;
913
+ key_next = false;
878
914
  i++;
879
915
  while (i < n) {
880
916
  const d = s.charCodeAt(i);
@@ -907,6 +943,7 @@ function json_text_refused_by_c(s) {
907
943
  continue;
908
944
  }
909
945
  if (c === 45 || c >= 48 && c <= 57) {
946
+ if (depth + 1 > JSON_MAX_DEPTH) return true;
910
947
  JSON_NUMBER_TOKEN.lastIndex = i;
911
948
  const m = JSON_NUMBER_TOKEN.exec(s);
912
949
  if (!m) return true;
@@ -945,7 +982,7 @@ function shape_of_value(value) {
945
982
  if (is_array(value)) return "an array";
946
983
  if (is_object(value)) return "an object";
947
984
  if (is_string(value)) return REFUSED_TEXT;
948
- if (typeof value === "number") return Number.isInteger(value) ? "an integer" : "a real";
985
+ if (typeof value === "number") return Number.isInteger(value) && Math.abs(value) <= 0x8000000000000000 ? "an integer" : "a real";
949
986
  if (typeof value === "boolean") return "a boolean";
950
987
  return "null";
951
988
  }
@@ -966,7 +1003,7 @@ function number_as_type(type, value) {
966
1003
  value: value ? 1 : 0,
967
1004
  refused: null
968
1005
  };
969
- if (!(value >= -0x8000000000000000 && value < 0x8000000000000000)) return {
1006
+ if (!(value >= -0x8000000000000000 && value <= 0x8000000000000000)) return {
970
1007
  value: void 0,
971
1008
  refused: "a number out of its range"
972
1009
  };
@@ -1127,6 +1164,10 @@ function command_parameter_from_json(type, value) {
1127
1164
  refused: shape_of_value(value)
1128
1165
  };
1129
1166
  if (DTP_IS_NUMBER(type)) return number_as_type(type, value);
1167
+ if (!is_json_value(value)) return {
1168
+ value: void 0,
1169
+ refused: NO_JSON_TEXT
1170
+ };
1130
1171
  return {
1131
1172
  value,
1132
1173
  refused: null
@@ -1145,8 +1186,34 @@ var TYPES_READ = [
1145
1186
  data_type_t.DTP_LIST,
1146
1187
  data_type_t.DTP_DICT
1147
1188
  ];
1189
+ var LEAVE_NODE = Symbol("leave");
1190
+ function is_json_value(value) {
1191
+ const on_path = /* @__PURE__ */ new Set();
1192
+ const stack = [value];
1193
+ while (stack.length) {
1194
+ const v = stack.pop();
1195
+ if (v === LEAVE_NODE) {
1196
+ on_path.delete(stack.pop());
1197
+ continue;
1198
+ }
1199
+ if (typeof v === "bigint") return false;
1200
+ if (v === null || typeof v !== "object") continue;
1201
+ if (on_path.has(v)) return false;
1202
+ const array = Array.isArray(v);
1203
+ if (!array) {
1204
+ const proto = Object.getPrototypeOf(v);
1205
+ if (proto !== Object.prototype && proto !== null) return false;
1206
+ }
1207
+ on_path.add(v);
1208
+ stack.push(v, LEAVE_NODE);
1209
+ if (array) for (let i = 0; i < v.length; i++) stack.push(v[i]);
1210
+ else for (const key of Object.keys(v)) stack.push(v[key]);
1211
+ }
1212
+ return true;
1213
+ }
1148
1214
  var NO_JSON_TEXT = "a value with no json text";
1149
1215
  function json_text_of(value) {
1216
+ if (!is_json_value(value)) return;
1150
1217
  try {
1151
1218
  const s = JSON.stringify(value);
1152
1219
  return is_string(s) ? s : void 0;
@@ -4105,6 +4172,35 @@ function is_secret_name_tail(str, from, to, names) {
4105
4172
  }
4106
4173
  return secret_name_check(str, i, to, true, by_segments);
4107
4174
  }
4175
+ function param_name_tail_from(str, from, to) {
4176
+ let bytes = 0;
4177
+ let i = to;
4178
+ while (i > from) {
4179
+ const code = str.charCodeAt(i - 1);
4180
+ let n = 3;
4181
+ let units = 1;
4182
+ if (code < 128) n = 1;
4183
+ else if (code < 2048) n = 2;
4184
+ else if (code >= 56320 && code <= 57343 && i - 2 >= from) {
4185
+ const high = str.charCodeAt(i - 2);
4186
+ if (high >= 55296 && high <= 56319) {
4187
+ n = 4;
4188
+ units = 2;
4189
+ }
4190
+ }
4191
+ if (bytes + n > MASK_MAX_NAME) return -1;
4192
+ if (code === 38 || code === 63 || code === 59) return i;
4193
+ bytes += n;
4194
+ i -= units;
4195
+ }
4196
+ return -1;
4197
+ }
4198
+ function is_param_name_secret(str, from, to, names) {
4199
+ const tail = names === NAMES_NOW ? param_name_tail_from(str, from, to) : -1;
4200
+ if (tail < 0) return is_secret_name_tail(str, from, to, names);
4201
+ if (is_secret_name_tail(str, from, to, NAMES_7_26_9)) return true;
4202
+ return is_secret_name_tail(str, from, to, NAMES_NOW) && secret_name_check(str, tail, to, false, true);
4203
+ }
4108
4204
  /************************************************************
4109
4205
  * Where an unquoted secret value that begins at `p` ends: at
4110
4206
  * the end, at the quote that closes an outer quoted value, or
@@ -4402,7 +4498,7 @@ function mark_params(str, value_is_secret, reading, marks, judged) {
4402
4498
  }
4403
4499
  const name_from = word;
4404
4500
  word = p + 1;
4405
- const secret = is_secret_name_tail(str, name_from, p, names);
4501
+ const secret = is_param_name_secret(str, name_from, p, names);
4406
4502
  if (secret && names === NAMES_NOW && !not_7_26_9 && !is_secret_name_tail(str, name_from, p, NAMES_7_26_9)) not_7_26_9 = true;
4407
4503
  if (!(secret || value_is_secret && p - name_from === 5 && ascii_case_equal(str, name_from, "value"))) {
4408
4504
  if (judged && query_name_from(str, p) === name_from) judged[p] = JUDGED_NOT_SECRET;
@@ -4589,7 +4685,7 @@ function mark_labels(str, marks) {
4589
4685
  p++;
4590
4686
  continue;
4591
4687
  }
4592
- if (!is_secret_name_tail(str, word, p, NAMES_NOW)) {
4688
+ if (!is_param_name_secret(str, word, p, NAMES_NOW)) {
4593
4689
  p++;
4594
4690
  word = p;
4595
4691
  continue;
@@ -4618,6 +4714,50 @@ function inline_value_is_secret(str, names) {
4618
4714
  }
4619
4715
  return false;
4620
4716
  }
4717
+ function mark_inline_rules_of_now(str, marks) {
4718
+ const value_is_secret = inline_value_is_secret(str, NAMES_NOW);
4719
+ const judged = new Uint8Array(str.length);
4720
+ mark_params(str, value_is_secret, READ_NOW, marks, judged);
4721
+ mark_query_params(str, marks, judged);
4722
+ mark_url_passwords(str, marks);
4723
+ mark_shapes(str, marks);
4724
+ mark_labels(str, marks);
4725
+ }
4726
+ var MASK_MAX_DECODINGS = 2;
4727
+ function encoded_ascii_at(str, p) {
4728
+ if (p + 3 > str.length || str.charCodeAt(p) !== 37) return -1;
4729
+ const hi = hex_digit_value(str.charCodeAt(p + 1));
4730
+ const lo = hi >= 0 ? hex_digit_value(str.charCodeAt(p + 2)) : -1;
4731
+ if (lo < 0) return -1;
4732
+ const c = hi * 16 + lo;
4733
+ return c > 0 && c < 128 ? c : -1;
4734
+ }
4735
+ function mark_decoded_view(str, marks, decodings) {
4736
+ if (decodings >= MASK_MAX_DECODINGS) return;
4737
+ const pieces = [];
4738
+ let last = 0;
4739
+ let p = str.indexOf("%");
4740
+ while (p >= 0) {
4741
+ const c = encoded_ascii_at(str, p);
4742
+ if (c >= 0) {
4743
+ pieces.push(str.substring(last, p), String.fromCharCode(c));
4744
+ last = p + 3;
4745
+ p = str.indexOf("%", last);
4746
+ } else p = str.indexOf("%", p + 1);
4747
+ }
4748
+ if (pieces.length === 0) return;
4749
+ pieces.push(str.substring(last));
4750
+ const view = pieces.join("");
4751
+ const view_marks = new Uint8Array(view.length);
4752
+ mark_inline_rules_of_now(view, view_marks);
4753
+ mark_decoded_view(view, view_marks, decodings + 1);
4754
+ let q = 0;
4755
+ for (let i = 0; i < view.length; i++) {
4756
+ const n = encoded_ascii_at(str, q) >= 0 ? 3 : 1;
4757
+ if (view_marks[i]) mark_range(marks, q, q + n);
4758
+ q += n;
4759
+ }
4760
+ }
4621
4761
  function mark_inline_secrets(str, marks) {
4622
4762
  const value_is_secret = inline_value_is_secret(str, NAMES_NOW);
4623
4763
  const value_is_secret_7_26_9 = inline_value_is_secret(str, NAMES_7_26_9);
@@ -4628,6 +4768,7 @@ function mark_inline_secrets(str, marks) {
4628
4768
  mark_url_passwords(str, marks);
4629
4769
  mark_shapes(str, marks);
4630
4770
  mark_labels(str, marks);
4771
+ mark_decoded_view(str, marks, 0);
4631
4772
  }
4632
4773
  /************************************************************
4633
4774
  * A text (a command line) with the secrets that can be told
@@ -4639,13 +4780,14 @@ function mark_inline_secrets(str, marks) {
4639
4780
  * same for a "name: value", its name in quotes or not (a json, a
4640
4781
  * header, a label: "password": x, Authorization: Basic x, bearer:
4641
4782
  * tok); and a JWT or the credential after "Bearer " or "Basic ",
4642
- * wherever they are, whatever names them. Each rule judges the text
4643
- * as it came. Return null when there was nothing to mask. As the C
4644
- * kernel's mask_secrets_inline().
4783
+ * wherever they are, whatever names them; in a URL or a query
4784
+ * %-encoded inside a value, the same read decoded (mark_decoded_view()).
4785
+ * Each rule judges the text as it came. Return null when there was
4786
+ * nothing to mask. As the C kernel's mask_secrets_inline().
4645
4787
  ************************************************************/
4646
4788
  function mask_secrets_inline(str) {
4647
4789
  if (typeof str !== "string") return null;
4648
- if (str.indexOf("=") < 0 && str.indexOf(":") < 0 && str.indexOf("eyJ") < 0 && !/bearer|basic/i.test(str)) return null;
4790
+ if (str.indexOf("=") < 0 && str.indexOf(":") < 0 && str.indexOf("%") < 0 && str.indexOf("eyJ") < 0 && !/bearer|basic/i.test(str)) return null;
4649
4791
  if (str.length > MASK_MAX_BYTES) return MASK_TOO_LARGE;
4650
4792
  const marks = new Uint8Array(str.length);
4651
4793
  mark_inline_secrets(str, marks);
@@ -8408,7 +8550,8 @@ var PRIVATE_DATA = {
8408
8550
  inform_on_close: false,
8409
8551
  websocket: null,
8410
8552
  inside_on_open: false,
8411
- retry_ms: 0
8553
+ retry_ms: 0,
8554
+ retry_ms_before_session: 0
8412
8555
  };
8413
8556
  var __gclass__ = null;
8414
8557
  /******************************
@@ -8459,6 +8602,7 @@ function mt_writing(gobj, path) {
8459
8602
  function mt_start(gobj) {
8460
8603
  let priv = gobj.priv;
8461
8604
  priv.retry_ms = 0;
8605
+ priv.retry_ms_before_session = 0;
8462
8606
  if (gobj_current_state(gobj) !== "ST_DISCONNECTED") {
8463
8607
  gobj_send_event(gobj, "EV_ON_CLOSE", {
8464
8608
  url: priv.url,
@@ -8658,6 +8802,26 @@ function close_websocket(gobj, code = 1e3, reason = "") {
8658
8802
  }
8659
8803
  }
8660
8804
  /***************************************************************
8805
+ * Close the channel for a fault of the server: a message for a role
8806
+ * or a yuno that is not this one, an event out of its place.
8807
+ *
8808
+ * Not with 1000: EV_ON_CLOSE would say the session ended well. A
8809
+ * browser closes a WebSocket only with 1000 or 3000-4999 (1002, the
8810
+ * protocol error, throws there), so it is 4002 -- and the server
8811
+ * echoes it, so EV_ON_CLOSE carries it.
8812
+ *
8813
+ * And a session the server broke was not a session reached: the
8814
+ * backoff the ack reset is put back, or a server that breaks every
8815
+ * session is reconnected at the first delay for ever, with an
8816
+ * EV_ON_OPEN and an EV_ON_CLOSE in the app per round.
8817
+ ***************************************************************/
8818
+ var CLOSE_PEER_FAULT = 4002;
8819
+ function close_for_peer_fault(gobj, reason) {
8820
+ let priv = gobj.priv;
8821
+ if (gobj_current_state(gobj) === "ST_SESSION") priv.retry_ms = priv.retry_ms_before_session;
8822
+ close_websocket(gobj, CLOSE_PEER_FAULT, reason);
8823
+ }
8824
+ /***************************************************************
8661
8825
  * A text of the server -- a frame, the name of an event, of a
8662
8826
  * service, of a role -- as a log shows it: masked, then capped on a
8663
8827
  * whole character (capped first, a credential the cap cut would
@@ -8939,7 +9103,8 @@ function ac_on_close(gobj, event, kw, src) {
8939
9103
  *
8940
9104
  * Reset to zero when a session is actually reached
8941
9105
  * (ac_identity_card_ack), so a link that flaps once does not carry a
8942
- * minute-long penalty into its next real drop.
9106
+ * minute-long penalty into its next real drop -- and put back when the
9107
+ * server breaks that session (close_for_peer_fault()).
8943
9108
  ***************************************************************/
8944
9109
  function next_retry(gobj) {
8945
9110
  let priv = gobj.priv;
@@ -8989,6 +9154,7 @@ function ac_identity_card_ack(gobj, event, kw, src) {
8989
9154
  } else {
8990
9155
  gobj_change_state(gobj, "ST_SESSION");
8991
9156
  priv.inside_on_open = true;
9157
+ priv.retry_ms_before_session = priv.retry_ms;
8992
9158
  priv.retry_ms = 0;
8993
9159
  if (!priv.inform_on_close) {
8994
9160
  priv.inform_on_close = true;
@@ -9028,7 +9194,7 @@ function ac_on_message(gobj, event, kw, src) {
9028
9194
  if (String(iev_event).toLowerCase() === "ev_identity_card_ack") {
9029
9195
  if (!(gobj_send_event(gobj, "EV_IDENTITY_CARD_ACK", iev_kw, gobj) < 0)) return 0;
9030
9196
  } else log_warning(`${gobj_short_name(gobj)}: event UNKNOWN in not-session state ${peer_text_shown(iev_event, PEER_NAME_SHOWN)}`);
9031
- close_websocket(gobj);
9197
+ close_for_peer_fault(gobj, "event out of session");
9032
9198
  return -1;
9033
9199
  }
9034
9200
  iev_msg = null;
@@ -9037,13 +9203,13 @@ function ac_on_message(gobj, event, kw, src) {
9037
9203
  let iev_dst_role = kw_get_str(gobj, event_id, "dst_role", "");
9038
9204
  if (!empty_string(iev_dst_role) && iev_dst_role.toLowerCase() !== String(gobj_yuno_role()).toLowerCase()) {
9039
9205
  log_warning(`${gobj_short_name(gobj)}: It's not my role, dst_role: ${peer_text_shown(iev_dst_role, PEER_NAME_SHOWN)}, my_role: ${gobj_yuno_role()}, event: ${peer_text_shown(iev_event, PEER_NAME_SHOWN)}`);
9040
- close_websocket(gobj);
9206
+ close_for_peer_fault(gobj, "not my role");
9041
9207
  return -1;
9042
9208
  }
9043
9209
  let iev_dst_yuno = kw_get_str(gobj, event_id, "dst_yuno", "");
9044
9210
  if (!empty_string(iev_dst_yuno) && iev_dst_yuno.toLowerCase() !== String(gobj_yuno_name()).toLowerCase()) {
9045
9211
  log_warning(`${gobj_short_name(gobj)}: It's not my name, dst_yuno: ${peer_text_shown(iev_dst_yuno, PEER_NAME_SHOWN)}, my_name: ${gobj_yuno_name()}, event: ${peer_text_shown(iev_event, PEER_NAME_SHOWN)}`);
9046
- close_websocket(gobj);
9212
+ close_for_peer_fault(gobj, "not my name");
9047
9213
  return -1;
9048
9214
  }
9049
9215
  let iev_dst_service = kw_get_str(gobj, event_id, "dst_service", "");
@@ -9055,8 +9221,14 @@ function ac_on_message(gobj, event, kw, src) {
9055
9221
  log_warning(`${gobj_short_name(gobj)}: unsubscription event ignored, I'm client (service: ${peer_text_shown(iev_dst_service, PEER_NAME_SHOWN)}, event: ${peer_text_shown(iev_event, PEER_NAME_SHOWN)})`);
9056
9222
  return -1;
9057
9223
  }
9058
- if (gobj_has_event(gobj, iev_event, event_flag_t.EVF_PUBLIC_EVENT)) {
9059
- gobj_send_event(gobj, iev_event, iev_kw, gobj);
9224
+ const declared = gobj_find_event_type(iev_event, 0, false);
9225
+ if (declared && gobj_has_event(gobj, declared.event_name, 0)) {
9226
+ const own_event = declared.event_name;
9227
+ if (!gobj_has_event(gobj, own_event, event_flag_t.EVF_PUBLIC_EVENT) || own_event === "EV_IDENTITY_CARD_ACK") {
9228
+ log_warning(`${gobj_short_name(gobj)}: event of the transport itself, or out of its place, DROPPED: ev '${peer_text_shown(iev_event, PEER_NAME_SHOWN)}'`);
9229
+ return -1;
9230
+ }
9231
+ gobj_send_event(gobj, own_event, iev_kw, gobj);
9060
9232
  return 0;
9061
9233
  }
9062
9234
  if (!empty_string(iev_dst_service)) {
@@ -9072,9 +9244,9 @@ function ac_on_message(gobj, event, kw, src) {
9072
9244
  gobj_send_event(gobj_service, iev_event, iev_kw, gobj);
9073
9245
  return 0;
9074
9246
  }
9075
- let event_type = gobj_find_event_type(iev_event, 0, false);
9247
+ let event_type = gobj_find_event_type(iev_event, event_flag_t.EVF_PUBLIC_EVENT, false);
9076
9248
  if (!event_type) {
9077
- log_warning(`${gobj_short_name(gobj)}: event that no gclass declares, DROPPED: ev '${peer_text_shown(iev_event, PEER_NAME_SHOWN)}'`);
9249
+ log_warning(`${gobj_short_name(gobj)}: event that no gclass declares public, DROPPED: ev '${peer_text_shown(iev_event, PEER_NAME_SHOWN)}'`);
9078
9250
  return -1;
9079
9251
  }
9080
9252
  gobj_publish_event(gobj, event_type.event_name, iev_kw);
@@ -9197,7 +9369,7 @@ function create_gclass(gclass_name) {
9197
9369
  __gclass__ = gclass_create(gclass_name, [
9198
9370
  ["EV_ON_MESSAGE", event_flag_t.EVF_OUTPUT_EVENT],
9199
9371
  ["EV_IDENTITY_CARD_ACK", event_flag_t.EVF_PUBLIC_EVENT],
9200
- ["EV_ON_ID_NAK", event_flag_t.EVF_PUBLIC_EVENT | event_flag_t.EVF_OUTPUT_EVENT],
9372
+ ["EV_ON_ID_NAK", event_flag_t.EVF_OUTPUT_EVENT],
9201
9373
  ["EV_MT_STATS", event_flag_t.EVF_PUBLIC_EVENT],
9202
9374
  ["EV_MT_COMMAND", event_flag_t.EVF_PUBLIC_EVENT],
9203
9375
  ["EV_ON_OPEN", event_flag_t.EVF_OUTPUT_EVENT | event_flag_t.EVF_NO_WARN_SUBS],