frooky 0.1.0__py3-none-any.whl

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.
frooky/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Frooky package."""
2
+
3
+ __all__ = ["__version__"]
4
+ __version__ = "0.1.0"
frooky/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Makes a hex dump of a byte array. The dump is limited by the length parameter.
3
+ * @param {Uint8Array} bytes - Byte array to be decoded to hexadecimal.
4
+ * @param {number} length - Number of bytes which will be decoded.
5
+ * @returns {string} The hexadecimal decoded bytes (e.g., "0x22aa3482ef...")
6
+ */
7
+ function byteArrayHexDump(bytes, length) {
8
+ let appendix = "...";
9
+ if (bytes.length < length) {
10
+ length = bytes.length;
11
+ appendix = "";
12
+ }
13
+
14
+ let hexString = "0x";
15
+ for (let i = 0; i < length; i++) {
16
+ hexString =
17
+ hexString + ("0" + (bytes[i] & 0xff).toString(16)).slice(-2);
18
+ }
19
+
20
+ return hexString + appendix;
21
+ }
22
+
23
+ /**
24
+ * Converts a byte value to its uri-encoded representation
25
+ * @param {number} byte - The byte value to encode (0-255)
26
+ * @returns {string} The uri-encoded string (e.g., "%20", "%0A")
27
+ */
28
+ function getUriCode(byte) {
29
+ const text = byte.toString(16);
30
+ if (byte < 16) {
31
+ return "%0" + text;
32
+ }
33
+ return "%" + text;
34
+ }
35
+
36
+ /**
37
+ * Tries to decode a byte array to either a string or a hex dump depending on the content of the array.
38
+ * @param {Uint8Array} bytes - Byte array to be decoded to hexadecimal.
39
+ * @param {number} length - Number of bytes which will be decoded.
40
+ * @returns {string} The decoded bytes (e.g., "This is some decoded string." or "0x22aa3482ef...")
41
+ */
42
+ function byteToString(bytes, length) {
43
+ if (bytes.length < length) {
44
+ length = bytes.length;
45
+ }
46
+
47
+ try {
48
+ // try to decode strings
49
+ let result = "";
50
+ for (let i = 0; i < length; ++i) {
51
+ result += getUriCode(bytes[i]);
52
+ }
53
+ return decodeURIComponent(result).replace(/\0.*$/g, "");
54
+ } catch (e) {
55
+ // make a hex dump in case, the byte array contains raw binary data
56
+ return byteArrayHexDump(bytes, length);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Generates a simple hash from a string.
62
+ * @param {string} str - String to hash.
63
+ * @returns {number} Hash value as a 32-bit integer.
64
+ */
65
+ function simpleHash(str) {
66
+ let h = 0;
67
+ for (let i = 0; i < str.length; i++) {
68
+ h = (h << 5) - h + str.charCodeAt(i);
69
+ h = h | 0;
70
+ }
71
+ return h;
72
+ }
73
+
74
+ /**
75
+ * Decodes an RSA key (public or private) and extracts key parameters.
76
+ * @param {Object} value - Reference to the Java key object.
77
+ * @returns {Object} Object containing key parameters (modulusHex, modulusBitLength, publicExponentDec, privateExponentDec, keyHash).
78
+ */
79
+ function decodeRSAKey(value) {
80
+ let out = {};
81
+
82
+ try {
83
+ // Load RSA interfaces
84
+ let RSAKey = Java.use('java.security.interfaces.RSAKey');
85
+ let RSAPub = Java.use('java.security.interfaces.RSAPublicKey');
86
+ let RSAPriv = Java.use('java.security.interfaces.RSAPrivateKey');
87
+ let RSAPrivateCrt = null;
88
+ try {
89
+ RSAPrivateCrt = Java.use('java.security.interfaces.RSAPrivateCrtKey');
90
+ } catch (_) {
91
+ RSAPrivateCrt = null;
92
+ }
93
+
94
+ // Any RSA key, public or private, for modulus
95
+ try {
96
+ let anyRsa = Java.cast(value, RSAKey);
97
+ let modBI = anyRsa.getModulus();
98
+ out.modulusHex = modBI.toString(16);
99
+ out.modulusBitLength = modBI.bitLength();
100
+ } catch (_) {
101
+ // not an RSAKey or keystore backend hides it, ignore
102
+ }
103
+
104
+ // Public key exponent
105
+ try {
106
+ let vpub = Java.cast(value, RSAPub);
107
+ let expBI = vpub.getPublicExponent();
108
+ if (expBI) {
109
+ out.publicExponentDec = expBI.toString(10);
110
+ }
111
+ } catch (_) {
112
+ // not an RSAPublicKey
113
+ }
114
+
115
+ // Private key exponents, may be unavailable for keystore backed keys
116
+ if (RSAPrivateCrt !== null) {
117
+ try {
118
+ let vprivCrt = Java.cast(value, RSAPrivateCrt);
119
+ let dBI = vprivCrt.getPrivateExponent();
120
+ let eBI = vprivCrt.getPublicExponent();
121
+ if (dBI) {
122
+ out.privateExponentDec = dBI.toString(10);
123
+ }
124
+ if (eBI) {
125
+ out.publicExponentDec = eBI.toString(10);
126
+ }
127
+ } catch (_) {
128
+ // not an RSAPrivateCrtKey
129
+ }
130
+ } else {
131
+ try {
132
+ let vpriv = Java.cast(value, RSAPriv);
133
+ let dBI2 = vpriv.getPrivateExponent();
134
+ if (dBI2) {
135
+ out.privateExponentDec = dBI2.toString(10);
136
+ }
137
+ } catch (_) {
138
+ // not an RSAPrivateKey
139
+ }
140
+ }
141
+ } catch (_) {
142
+ // key interface logic failed, out remains minimal
143
+ }
144
+
145
+ if (out.modulusHex != null) {
146
+ out.keyHash = simpleHash(out.modulusHex);
147
+ }
148
+
149
+ return out;
150
+ }
151
+
152
+ /**
153
+ * Decodes a Java object according to its type.
154
+ * @param {string} type - Java type of the value (e.g., "java.util.Set", "java.lang.String" or "int")
155
+ * @param {Object} value - Reference to the object.
156
+ * @returns {string} The type-appropriate decoded string (e.g., "[1,50,21]", "Hello World" or "-12")
157
+ */
158
+ function decodeValue(type, value) {
159
+ let readableValue = "";
160
+
161
+ try {
162
+ if (value == null) {
163
+ readableValue = "void";
164
+ } else {
165
+ switch (type) {
166
+ case "java.util.Set":
167
+ readableValue = value.toArray().toString();
168
+ break;
169
+
170
+ case "java.util.Map":
171
+ let entrySet = value.entrySet();
172
+ readableValue = entrySet.toArray().toString();
173
+ break;
174
+
175
+ case "[B":
176
+ // for performance reasons only decode the first 256 bytes of the full byte array
177
+ readableValue = byteToString(value, 256);
178
+ break;
179
+
180
+ case "[C":
181
+ readableValue = "";
182
+ for (let i in value) {
183
+ readableValue = readableValue + value[i];
184
+ }
185
+ break;
186
+
187
+ case "java.io.File":
188
+ readableValue = value.getAbsolutePath();
189
+ break;
190
+
191
+ case "java.util.Date":
192
+ let DateFormat = Java.use('java.text.DateFormat');
193
+ let formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM.value, DateFormat.SHORT.value);
194
+ readableValue = formatter.format(value);
195
+ break;
196
+
197
+ case "androidx.sqlite.db.SupportSQLiteQuery":
198
+ readableValue = value.getSql();
199
+ break;
200
+
201
+ case "android.content.ClipData$Item":
202
+ readableValue = value.getText().toString();
203
+ break;
204
+
205
+ case "androidx.datastore.preferences.core.Preferences$Key":
206
+ case "java.lang.Object":
207
+ case "android.net.Uri":
208
+ case "java.lang.CharSequence":
209
+ readableValue = value.toString();
210
+ break;
211
+
212
+ /*
213
+ 1. No `RSAKey.class.isAssignableFrom` or `Java.isInstanceOf` for the RSA interfaces, instead each RSA interface is tried with `Java.cast` inside a `try` block. If the object does not implement that interface, the cast throws and is ignored. If it does, the cast succeeds and you can call the RSA methods.
214
+ 2. Modulus is obtained through `RSAKey.getModulus()`, which should cover both `OpenSSLRSAPublicKey` and `AndroidKeyStoreRSAPrivateKey` as long as the backend exposes the modulus.
215
+ 3. Exponents come from `RSAPublicKey.getPublicExponent()` and `RSAPrivateKey` or `RSAPrivateCrtKey` for the private exponent, but note that keystore backed private keys may refuse to expose the private exponent, so missing private exponent is expected in that case, while the public key parameters should still be visible.
216
+ */
217
+
218
+ case "java.security.PrivateKey":
219
+ case "java.security.PublicKey":
220
+ case "java.security.Key":
221
+ try {
222
+ readableValue = decodeRSAKey(value);
223
+ } catch (e) {
224
+ readableValue = value;
225
+ }
226
+ break;
227
+
228
+ case "[Ljava.lang.Object;":
229
+ let out = "";
230
+ for (let i in value) {
231
+ out = out + value[i] + ", ";
232
+ }
233
+ readableValue = out;
234
+ break;
235
+
236
+ case "java.util.Enumeration":
237
+ let elements = [];
238
+ while (value.hasMoreElements()) {
239
+ elements.push(value.nextElement().toString());
240
+ }
241
+ readableValue = JSON.stringify(elements);
242
+ break;
243
+
244
+ case "android.database.Cursor":
245
+ readableValue = decodeCursor(value);
246
+ break;
247
+
248
+ default:
249
+ readableValue = value;
250
+ break;
251
+ }
252
+ }
253
+ } catch (e) {
254
+ console.error("Value decoding exception: " + e);
255
+ readableValue = value;
256
+ }
257
+
258
+ return readableValue;
259
+ }
260
+
261
+ /**
262
+ * Decodes a `android.database.Cursor` object.
263
+ * @param {object} value - Reference to the object.
264
+ * @returns {string} The decoded rows and columns.
265
+ */
266
+ function decodeCursor(value){
267
+ let out = "";
268
+ let cursor = value;
269
+ let originalCursorPosition = cursor.getPosition();
270
+
271
+ // rows
272
+ for (let i = 0; i < cursor.getColumnCount(); i++) {
273
+ let columnName = cursor.getColumnName(i);
274
+ out = out + columnName + " | ";
275
+ }
276
+
277
+ out = out + "\n----------------------\n";
278
+
279
+ // columns
280
+ if (cursor.moveToFirst()) {
281
+ do {
282
+ for (let i = 0; i < cursor.getColumnCount(); i++) {
283
+ try {
284
+ let columnValue = cursor.getString(i);
285
+ out = out + columnValue + " | ";
286
+ } catch (e) {
287
+ out = out + " | ";
288
+ }
289
+ }
290
+ out = out + "\n";
291
+ } while (cursor.moveToNext());
292
+
293
+ cursor.moveToPosition(originalCursorPosition);
294
+ }
295
+ return out;
296
+ }
297
+
298
+ /**
299
+ * Decodes Java values according to their types.
300
+ * @param {[string]} types - Java types of the value (e.g., ["java.util.Set", "java.lang.String", "int"])
301
+ * @param {[string]]} value - Reference to the objects.
302
+ * @returns {[string]} The type-appropriate decoded strings (e.g., ["java.util.Set":"[1,50,21]", "java.lang.String":"Hello World", "int":"-12"])
303
+ */
304
+
305
+ // Module-level cached references for performance
306
+ let _toStringMethod = null;
307
+ let _toStringMethodInitialized = false;
308
+ let _SystemCls = null;
309
+ let _SystemClsInitialized = false;
310
+
311
+ function getToStringMethod() {
312
+ if (!_toStringMethodInitialized) {
313
+ try {
314
+ let ObjCls = Java.use('java.lang.Object');
315
+ _toStringMethod = ObjCls.class.getDeclaredMethod('toString', []);
316
+ _toStringMethod.setAccessible(true);
317
+ } catch (_) {
318
+ _toStringMethod = null;
319
+ }
320
+ _toStringMethodInitialized = true;
321
+ }
322
+ return _toStringMethod;
323
+ }
324
+
325
+ function getSystemCls() {
326
+ if (!_SystemClsInitialized) {
327
+ try {
328
+ _SystemCls = Java.use('java.lang.System');
329
+ } catch (_) {
330
+ _SystemCls = null;
331
+ }
332
+ _SystemClsInitialized = true;
333
+ }
334
+ return _SystemCls;
335
+ }
336
+
337
+ function decodeArguments(types, args) {
338
+ let parameters = [];
339
+ let toStringMethod = getToStringMethod();
340
+ let SystemCls = getSystemCls();
341
+
342
+ for (let i in types) {
343
+ let declaredType = types[i];
344
+ let argVal = args[i];
345
+ let entry = { declaredType: declaredType, value: decodeValue(declaredType, argVal) };
346
+
347
+ // Attach runtime info if this is a Java object
348
+ if (argVal && typeof argVal === 'object') {
349
+ let runtimeType = null;
350
+ try { runtimeType = argVal.$className || (argVal.getClass ? argVal.getClass().getName() : null); } catch (_) {}
351
+ if (runtimeType) {
352
+ entry.runtimeType = runtimeType;
353
+ if (SystemCls) {
354
+ try {
355
+ entry.instanceId = '' + SystemCls.identityHashCode(argVal);
356
+ } catch (_) {}
357
+ }
358
+ // Robust toString retrieval: prefer reflected method, fallback to direct call
359
+ try {
360
+ if (toStringMethod) {
361
+ entry.instanceToString = '' + toStringMethod.invoke(argVal, []);
362
+ } else {
363
+ entry.instanceToString = '' + argVal.toString();
364
+ }
365
+ } catch (e1) {
366
+ try { entry.instanceToString = '' + argVal.toString(); } catch (e2) { entry.instanceToString = '<toString-unavailable>'; }
367
+ }
368
+ }
369
+ }
370
+
371
+ parameters.push(entry);
372
+ }
373
+ return parameters;
374
+ }
375
+