| ... |
... |
@@ -272,6 +272,11 @@ class AsyncSocket { |
|
272
|
272
|
*
|
|
273
|
273
|
* @typedef {string} NodeFingerprint
|
|
274
|
274
|
*/
|
|
|
275
|
+/**
|
|
|
276
|
+ * @typedef {object} CircuitInfo
|
|
|
277
|
+ * @property {CircuitID} id
|
|
|
278
|
+ * @property {NodeFingerprint[]} nodes
|
|
|
279
|
+ */
|
|
275
|
280
|
/**
|
|
276
|
281
|
* @typedef {object} Bridge
|
|
277
|
282
|
* @property {string} transport The transport of the bridge, or vanilla if not
|
| ... |
... |
@@ -729,12 +734,14 @@ export class TorController { |
|
729
|
734
|
/**
|
|
730
|
735
|
* Ask Tor a list of circuits.
|
|
731
|
736
|
*
|
|
732
|
|
- * @returns {string[]} An array with a string for each line
|
|
|
737
|
+ * @returns {CircuitInfo[]} An array with a string for each line
|
|
733
|
738
|
*/
|
|
734
|
739
|
async getCircuits() {
|
|
735
|
740
|
const circuits = await this.#getInfo("circuit-status");
|
|
736
|
|
- // TODO: Do more parsing once we move the event parsing to this class!
|
|
737
|
|
- return circuits.split(/\r?\n/);
|
|
|
741
|
+ return circuits
|
|
|
742
|
+ .split(/\r?\n/)
|
|
|
743
|
+ .map(this.#parseCircBuilt.bind(this))
|
|
|
744
|
+ .filter(circ => circ);
|
|
738
|
745
|
}
|
|
739
|
746
|
|
|
740
|
747
|
// Configuration
|
| ... |
... |
@@ -1022,25 +1029,15 @@ export class TorController { |
|
1022
|
1029
|
this.#eventHandler.onBootstrapStatus(status);
|
|
1023
|
1030
|
break;
|
|
1024
|
1031
|
case "CIRC":
|
|
1025
|
|
- const builtEvent =
|
|
1026
|
|
- /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
|
|
1027
|
|
- data.groups.data
|
|
1028
|
|
- );
|
|
|
1032
|
+ const maybeCircuit = this.#parseCircBuilt(data.groups.data);
|
|
1029
|
1033
|
const closedEvent = /^(?<ID>[a-zA-Z0-9]{1,16})\sCLOSED/.exec(
|
|
1030
|
1034
|
data.groups.data
|
|
1031
|
1035
|
);
|
|
1032
|
|
- if (builtEvent) {
|
|
1033
|
|
- const fp = /\$([0-9a-fA-F]{40})/g;
|
|
1034
|
|
- const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
|
|
1035
|
|
- g[1].toUpperCase()
|
|
|
1036
|
+ if (maybeCircuit) {
|
|
|
1037
|
+ this.#eventHandler.onCircuitBuilt(
|
|
|
1038
|
+ maybeCircuit.id,
|
|
|
1039
|
+ maybeCircuit.nodes
|
|
1036
|
1040
|
);
|
|
1037
|
|
- // In some cases, we might already receive SOCKS credentials in the
|
|
1038
|
|
- // line. However, this might be a problem with onion services: we get
|
|
1039
|
|
- // also a 4-hop circuit that we likely do not want to show to the
|
|
1040
|
|
- // user, especially because it is used only temporarily, and it would
|
|
1041
|
|
- // need a technical explaination.
|
|
1042
|
|
- // const credentials = this.#parseCredentials(data.groups.data);
|
|
1043
|
|
- this.#eventHandler.onCircuitBuilt(builtEvent.groups.ID, nodes);
|
|
1044
|
1041
|
} else if (closedEvent) {
|
|
1045
|
1042
|
this.#eventHandler.onCircuitClosed(closedEvent.groups.ID);
|
|
1046
|
1043
|
}
|
| ... |
... |
@@ -1068,7 +1065,7 @@ export class TorController { |
|
1068
|
1065
|
}
|
|
1069
|
1066
|
}
|
|
1070
|
1067
|
|
|
1071
|
|
- // Other helpers
|
|
|
1068
|
+ // Parsers
|
|
1072
|
1069
|
|
|
1073
|
1070
|
/**
|
|
1074
|
1071
|
* Parse a bootstrap status line.
|
| ... |
... |
@@ -1099,15 +1096,32 @@ export class TorController { |
|
1099
|
1096
|
}
|
|
1100
|
1097
|
|
|
1101
|
1098
|
/**
|
|
1102
|
|
- * Throw an exception when value is not a string.
|
|
|
1099
|
+ * Parse a CIRC BUILT event or a GETINFO circuit-status.
|
|
1103
|
1100
|
*
|
|
1104
|
|
- * @param {any} value The value to check
|
|
1105
|
|
- * @param {string} name The name of the `value` argument
|
|
|
1101
|
+ * @param {string} line The line to parse
|
|
|
1102
|
+ * @returns {CircuitInfo?} The ID and nodes of the circuit, or null if the
|
|
|
1103
|
+ * parsing failed.
|
|
1106
|
1104
|
*/
|
|
1107
|
|
- #expectString(value, name) {
|
|
1108
|
|
- if (typeof value !== "string" && !(value instanceof String)) {
|
|
1109
|
|
- throw new Error(`The ${name} argument is expected to be a string.`);
|
|
|
1105
|
+ #parseCircBuilt(line) {
|
|
|
1106
|
+ const builtEvent =
|
|
|
1107
|
+ /^(?<ID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(,?\$([0-9a-fA-F]{40})(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
|
|
|
1108
|
+ line
|
|
|
1109
|
+ );
|
|
|
1110
|
+ if (!builtEvent) {
|
|
|
1111
|
+ return null;
|
|
1110
|
1112
|
}
|
|
|
1113
|
+ const fp = /\$([0-9a-fA-F]{40})/g;
|
|
|
1114
|
+ const nodes = Array.from(builtEvent.groups.Path.matchAll(fp), g =>
|
|
|
1115
|
+ g[1].toUpperCase()
|
|
|
1116
|
+ );
|
|
|
1117
|
+ // In some cases, we might already receive SOCKS credentials in the
|
|
|
1118
|
+ // line. However, this might be a problem with Onion services: we get
|
|
|
1119
|
+ // also a 4-hop circuit that we likely do not want to show to the
|
|
|
1120
|
+ // user, especially because it is used only temporarily, and it would
|
|
|
1121
|
+ // need a technical explaination.
|
|
|
1122
|
+ // So we do not try to extract them for now. Otherwise, we could do
|
|
|
1123
|
+ // const credentials = this.#parseCredentials(line);
|
|
|
1124
|
+ return { id: builtEvent.groups.ID, nodes };
|
|
1111
|
1125
|
}
|
|
1112
|
1126
|
|
|
1113
|
1127
|
/**
|
| ... |
... |
@@ -1146,6 +1160,20 @@ export class TorController { |
|
1146
|
1160
|
)
|
|
1147
|
1161
|
);
|
|
1148
|
1162
|
}
|
|
|
1163
|
+
|
|
|
1164
|
+ // Other helpers
|
|
|
1165
|
+
|
|
|
1166
|
+ /**
|
|
|
1167
|
+ * Throw an exception when value is not a string.
|
|
|
1168
|
+ *
|
|
|
1169
|
+ * @param {any} value The value to check
|
|
|
1170
|
+ * @param {string} name The name of the `value` argument
|
|
|
1171
|
+ */
|
|
|
1172
|
+ #expectString(value, name) {
|
|
|
1173
|
+ if (typeof value !== "string" && !(value instanceof String)) {
|
|
|
1174
|
+ throw new Error(`The ${name} argument is expected to be a string.`);
|
|
|
1175
|
+ }
|
|
|
1176
|
+ }
|
|
1149
|
1177
|
}
|
|
1150
|
1178
|
|
|
1151
|
1179
|
/**
|