appium-adb 16.1.2 → 16.3.0
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.
- package/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/build/lib/adb.d.ts +2 -0
- package/build/lib/adb.d.ts.map +1 -1
- package/build/lib/adb.js +5 -1
- package/build/lib/adb.js.map +1 -1
- package/build/lib/tools/android-manifest.js +1 -1
- package/build/lib/tools/android-manifest.js.map +1 -1
- package/build/lib/tools/apk-utils.js +3 -3
- package/build/lib/tools/apk-utils.js.map +1 -1
- package/build/lib/tools/app-commands.d.ts +4 -4
- package/build/lib/tools/app-commands.d.ts.map +1 -1
- package/build/lib/tools/app-commands.js +35 -36
- package/build/lib/tools/app-commands.js.map +1 -1
- package/build/lib/tools/device-settings.js +2 -2
- package/build/lib/tools/device-settings.js.map +1 -1
- package/build/lib/tools/fs-commands.js +2 -2
- package/build/lib/tools/fs-commands.js.map +1 -1
- package/build/lib/tools/lockmgmt.d.ts.map +1 -1
- package/build/lib/tools/lockmgmt.js +6 -2
- package/build/lib/tools/lockmgmt.js.map +1 -1
- package/build/lib/tools/system-calls.d.ts +14 -3
- package/build/lib/tools/system-calls.d.ts.map +1 -1
- package/build/lib/tools/system-calls.js +12 -6
- package/build/lib/tools/system-calls.js.map +1 -1
- package/build/lib/tools/types.d.ts +8 -0
- package/build/lib/tools/types.d.ts.map +1 -1
- package/build/lib/types.d.ts +2 -0
- package/build/lib/types.d.ts.map +1 -1
- package/lib/adb.ts +7 -1
- package/lib/tools/android-manifest.ts +1 -1
- package/lib/tools/apk-utils.ts +3 -3
- package/lib/tools/app-commands.ts +52 -50
- package/lib/tools/device-settings.ts +2 -2
- package/lib/tools/fs-commands.ts +2 -2
- package/lib/tools/lockmgmt.ts +6 -2
- package/lib/tools/system-calls.ts +29 -9
- package/lib/tools/types.ts +8 -0
- package/lib/types.ts +3 -0
- package/package.json +3 -3
|
@@ -80,7 +80,7 @@ export async function resolveLaunchableActivity(
|
|
|
80
80
|
): Promise<string> {
|
|
81
81
|
const {preferCmd = true} = opts;
|
|
82
82
|
if (!preferCmd || (await this.getApiLevel()) < 24) {
|
|
83
|
-
const stdout = await this.shell(['dumpsys', 'package', pkg]);
|
|
83
|
+
const stdout = await this.shell(['dumpsys', 'package', pkg], {quoteArgs: true});
|
|
84
84
|
const names = parseLaunchableActivityNames(stdout);
|
|
85
85
|
if (util.isEmpty(names)) {
|
|
86
86
|
log.debug(stdout);
|
|
@@ -110,6 +110,7 @@ export async function resolveLaunchableActivity(
|
|
|
110
110
|
}
|
|
111
111
|
const {stdout, stderr} = await this.shell(['cmd', 'package', 'resolve-activity', '--brief', pkg], {
|
|
112
112
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
113
|
+
quoteArgs: true,
|
|
113
114
|
});
|
|
114
115
|
for (const line of (stdout || '').split('\n').map((x) => x.trim())) {
|
|
115
116
|
if (this.isValidClass(line)) {
|
|
@@ -132,7 +133,7 @@ export async function resolveLaunchableActivity(
|
|
|
132
133
|
*/
|
|
133
134
|
export async function forceStop(this: ADB, pkg: string): Promise<string> {
|
|
134
135
|
assertSafeComponentName(pkg, 'application package name');
|
|
135
|
-
return await this.shell(['am', 'force-stop',
|
|
136
|
+
return await this.shell(['am', 'force-stop', pkg], {quoteArgs: true});
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
/**
|
|
@@ -147,7 +148,7 @@ export async function forceStop(this: ADB, pkg: string): Promise<string> {
|
|
|
147
148
|
* @returns The output of the corresponding adb command.
|
|
148
149
|
*/
|
|
149
150
|
export async function killPackage(this: ADB, pkg: string): Promise<string> {
|
|
150
|
-
return await this.shell(['am', 'kill', pkg]);
|
|
151
|
+
return await this.shell(['am', 'kill', pkg], {quoteArgs: true});
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
/**
|
|
@@ -158,7 +159,7 @@ export async function killPackage(this: ADB, pkg: string): Promise<string> {
|
|
|
158
159
|
* @returns The output of the corresponding adb command.
|
|
159
160
|
*/
|
|
160
161
|
export async function clear(this: ADB, pkg: string): Promise<string> {
|
|
161
|
-
return await this.shell(['pm', 'clear', pkg]);
|
|
162
|
+
return await this.shell(['pm', 'clear', pkg], {quoteArgs: true});
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
/**
|
|
@@ -180,7 +181,7 @@ export async function grantAllPermissions(this: ADB, pkg: string, apk?: string):
|
|
|
180
181
|
* If apk not provided, considering apk already installed on the device
|
|
181
182
|
* and fetching targetSdk using package name.
|
|
182
183
|
*/
|
|
183
|
-
dumpsysOutput = await this.shell(['dumpsys', 'package', pkg]);
|
|
184
|
+
dumpsysOutput = await this.shell(['dumpsys', 'package', pkg], {quoteArgs: true});
|
|
184
185
|
targetSdk = await this.targetSdkVersionUsingPKG(pkg, dumpsysOutput);
|
|
185
186
|
} else {
|
|
186
187
|
targetSdk = await this.targetSdkVersionFromManifest(apk);
|
|
@@ -195,7 +196,7 @@ export async function grantAllPermissions(this: ADB, pkg: string, apk?: string):
|
|
|
195
196
|
* The app has to list the permissions in the manifest.
|
|
196
197
|
* refer: https://developer.android.com/training/permissions/requesting.html
|
|
197
198
|
*/
|
|
198
|
-
dumpsysOutput = dumpsysOutput || (await this.shell(['dumpsys', 'package', pkg]));
|
|
199
|
+
dumpsysOutput = dumpsysOutput || (await this.shell(['dumpsys', 'package', pkg], {quoteArgs: true}));
|
|
199
200
|
const requestedPermissions = await this.getReqPermissions(pkg, dumpsysOutput);
|
|
200
201
|
const grantedPermissions = await this.getGrantedPermissions(pkg, dumpsysOutput);
|
|
201
202
|
const permissionsToGrant = requestedPermissions.filter((x) => !grantedPermissions.includes(x));
|
|
@@ -234,7 +235,7 @@ export async function grantPermissions(this: ADB, pkg: string, permissions: stri
|
|
|
234
235
|
// adb shell buffer is limited
|
|
235
236
|
log.debug(`Granting permissions ${JSON.stringify(permissions)} to '${pkg}'`);
|
|
236
237
|
try {
|
|
237
|
-
await this.shellChunks((perm) => ['pm', 'grant', pkg, perm], permissions);
|
|
238
|
+
await this.shellChunks((perm) => ['pm', 'grant', pkg, perm], permissions, {quoteArgs: true});
|
|
238
239
|
} catch (e) {
|
|
239
240
|
const err = e as ExecError;
|
|
240
241
|
if (!IGNORED_PERM_ERRORS.some((pattern) => pattern.test(err.stderr || err.message))) {
|
|
@@ -252,7 +253,7 @@ export async function grantPermissions(this: ADB, pkg: string, permissions: stri
|
|
|
252
253
|
*/
|
|
253
254
|
export async function grantPermission(this: ADB, pkg: string, permission: string): Promise<void> {
|
|
254
255
|
try {
|
|
255
|
-
await this.shell(['pm', 'grant', pkg, permission]);
|
|
256
|
+
await this.shell(['pm', 'grant', pkg, permission], {quoteArgs: true});
|
|
256
257
|
} catch (e) {
|
|
257
258
|
const err = e as ExecError;
|
|
258
259
|
if (!NOT_CHANGEABLE_PERM_ERROR.test(err.stderr || err.message)) {
|
|
@@ -270,7 +271,7 @@ export async function grantPermission(this: ADB, pkg: string, permission: string
|
|
|
270
271
|
*/
|
|
271
272
|
export async function revokePermission(this: ADB, pkg: string, permission: string): Promise<void> {
|
|
272
273
|
try {
|
|
273
|
-
await this.shell(['pm', 'revoke', pkg, permission]);
|
|
274
|
+
await this.shell(['pm', 'revoke', pkg, permission], {quoteArgs: true});
|
|
274
275
|
} catch (e) {
|
|
275
276
|
const err = e as ExecError;
|
|
276
277
|
if (!NOT_CHANGEABLE_PERM_ERROR.test(err.stderr || err.message)) {
|
|
@@ -294,7 +295,7 @@ export async function getGrantedPermissions(
|
|
|
294
295
|
cmdOutput: string | null = null,
|
|
295
296
|
): Promise<string[]> {
|
|
296
297
|
log.debug('Retrieving granted permissions');
|
|
297
|
-
const stdout = cmdOutput || (await this.shell(['dumpsys', 'package', pkg]));
|
|
298
|
+
const stdout = cmdOutput || (await this.shell(['dumpsys', 'package', pkg], {quoteArgs: true}));
|
|
298
299
|
return extractMatchingPermissions(stdout, ['install', 'runtime'], true);
|
|
299
300
|
}
|
|
300
301
|
|
|
@@ -308,7 +309,7 @@ export async function getGrantedPermissions(
|
|
|
308
309
|
*/
|
|
309
310
|
export async function getDeniedPermissions(this: ADB, pkg: string, cmdOutput: string | null = null): Promise<string[]> {
|
|
310
311
|
log.debug('Retrieving denied permissions');
|
|
311
|
-
const stdout = cmdOutput || (await this.shell(['dumpsys', 'package', pkg]));
|
|
312
|
+
const stdout = cmdOutput || (await this.shell(['dumpsys', 'package', pkg], {quoteArgs: true}));
|
|
312
313
|
return extractMatchingPermissions(stdout, ['install', 'runtime'], false);
|
|
313
314
|
}
|
|
314
315
|
|
|
@@ -322,7 +323,7 @@ export async function getDeniedPermissions(this: ADB, pkg: string, cmdOutput: st
|
|
|
322
323
|
*/
|
|
323
324
|
export async function getReqPermissions(this: ADB, pkg: string, cmdOutput: string | null = null): Promise<string[]> {
|
|
324
325
|
log.debug('Retrieving requested permissions');
|
|
325
|
-
const stdout = cmdOutput || (await this.shell(['dumpsys', 'package', pkg]));
|
|
326
|
+
const stdout = cmdOutput || (await this.shell(['dumpsys', 'package', pkg], {quoteArgs: true}));
|
|
326
327
|
return extractMatchingPermissions(stdout, ['requested']);
|
|
327
328
|
}
|
|
328
329
|
|
|
@@ -352,7 +353,7 @@ export async function getPackageInfo(this: ADB, pkg: string): Promise<AppInfo> {
|
|
|
352
353
|
const result: AppInfo = {name: pkg};
|
|
353
354
|
let stdout: string;
|
|
354
355
|
try {
|
|
355
|
-
stdout = await this.shell(['dumpsys', 'package', pkg]);
|
|
356
|
+
stdout = await this.shell(['dumpsys', 'package', pkg], {quoteArgs: true});
|
|
356
357
|
} catch (err) {
|
|
357
358
|
const error = err as Error;
|
|
358
359
|
log.debug(error.stack);
|
|
@@ -386,7 +387,7 @@ export async function getPackageInfo(this: ADB, pkg: string): Promise<AppInfo> {
|
|
|
386
387
|
* @throws {Error} If there was an error while fetching the .apk
|
|
387
388
|
*/
|
|
388
389
|
export async function pullApk(this: ADB, pkg: string, tmpDir: string): Promise<string> {
|
|
389
|
-
const stdout = (await this.shell(['pm', 'path', pkg])).trim();
|
|
390
|
+
const stdout = (await this.shell(['pm', 'path', pkg], {quoteArgs: true})).trim();
|
|
390
391
|
const packageMarker = 'package:';
|
|
391
392
|
if (!stdout.startsWith(packageMarker)) {
|
|
392
393
|
throw new Error(`Cannot pull the .apk package for '${pkg}'. Original error: ${stdout}`);
|
|
@@ -417,7 +418,7 @@ export async function activateApp(this: ADB, appId: string): Promise<void> {
|
|
|
417
418
|
const cmd = ['monkey', '-p', appId, '-c', 'android.intent.category.LAUNCHER', '1'];
|
|
418
419
|
let output: string;
|
|
419
420
|
try {
|
|
420
|
-
output = await this.shell(cmd);
|
|
421
|
+
output = await this.shell(cmd, {quoteArgs: true});
|
|
421
422
|
log.debug(`Command stdout: ${output}`);
|
|
422
423
|
} catch (e) {
|
|
423
424
|
const error = e as Error;
|
|
@@ -439,21 +440,24 @@ export async function activateApp(this: ADB, appId: string): Promise<void> {
|
|
|
439
440
|
activityName = await this.resolveLaunchableActivity(appId, {preferCmd: false});
|
|
440
441
|
}
|
|
441
442
|
|
|
442
|
-
const stdout = await this.shell(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
443
|
+
const stdout = await this.shell(
|
|
444
|
+
[
|
|
445
|
+
'am',
|
|
446
|
+
apiLevel < 26 ? 'start' : 'start-activity',
|
|
447
|
+
'-a',
|
|
448
|
+
'android.intent.action.MAIN',
|
|
449
|
+
'-c',
|
|
450
|
+
'android.intent.category.LAUNCHER',
|
|
451
|
+
// FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
|
452
|
+
// https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_NEW_TASK
|
|
453
|
+
// https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
|
454
|
+
'-f',
|
|
455
|
+
'0x10200000',
|
|
456
|
+
'-n',
|
|
457
|
+
activityName,
|
|
458
|
+
],
|
|
459
|
+
{quoteArgs: true},
|
|
460
|
+
);
|
|
457
461
|
log.debug(stdout);
|
|
458
462
|
if (/^error:/im.test(stdout)) {
|
|
459
463
|
throw new Error(`Cannot activate '${appId}'. Original error: ${stdout}`);
|
|
@@ -479,7 +483,7 @@ export async function isAppInstalled(this: ADB, pkg: string, opts: IsAppInstalle
|
|
|
479
483
|
cmd.push('--user', user);
|
|
480
484
|
}
|
|
481
485
|
cmd.push(pkg);
|
|
482
|
-
const stdout = await this.shell(cmd);
|
|
486
|
+
const stdout = await this.shell(cmd, {quoteArgs: true});
|
|
483
487
|
isInstalled = /^package:/m.test(stdout);
|
|
484
488
|
} catch {
|
|
485
489
|
isInstalled = false;
|
|
@@ -519,12 +523,12 @@ export async function listInstalledPackages(
|
|
|
519
523
|
|
|
520
524
|
let stdout: string;
|
|
521
525
|
try {
|
|
522
|
-
stdout = await this.shell(cmd);
|
|
526
|
+
stdout = await this.shell(cmd, {quoteArgs: true});
|
|
523
527
|
} catch (e) {
|
|
524
528
|
const error = e as ExecError;
|
|
525
529
|
// https://github.com/appium/appium-uiautomator2-driver/issues/810
|
|
526
530
|
if ((error.stderr || error.stdout || error.message)?.includes('access user') && util.isEmpty(user)) {
|
|
527
|
-
stdout = await this.shell([...cmd, '--user', '0']);
|
|
531
|
+
stdout = await this.shell([...cmd, '--user', '0'], {quoteArgs: true});
|
|
528
532
|
} else {
|
|
529
533
|
throw e;
|
|
530
534
|
}
|
|
@@ -561,16 +565,14 @@ export async function startUri(
|
|
|
561
565
|
if (waitForLaunch) {
|
|
562
566
|
args.push('-W');
|
|
563
567
|
}
|
|
564
|
-
|
|
565
|
-
// regardless of the host operating system.
|
|
566
|
-
args.push('-a', 'android.intent.action.VIEW', '-d', util.quote(uri));
|
|
568
|
+
args.push('-a', 'android.intent.action.VIEW', '-d', uri);
|
|
567
569
|
if (pkg) {
|
|
568
570
|
assertSafeComponentName(pkg, 'application package name');
|
|
569
|
-
args.push(
|
|
571
|
+
args.push(pkg);
|
|
570
572
|
}
|
|
571
573
|
|
|
572
574
|
try {
|
|
573
|
-
const res = await this.shell(args);
|
|
575
|
+
const res = await this.shell(args, {quoteArgs: true});
|
|
574
576
|
if (res.toLowerCase().includes('unable to resolve intent')) {
|
|
575
577
|
throw new Error(res);
|
|
576
578
|
}
|
|
@@ -616,7 +618,7 @@ export async function startApp(this: ADB, startAppOptions: StartAppOptions): Pro
|
|
|
616
618
|
if (options.waitDuration !== undefined && Number.isInteger(options.waitDuration) && options.waitDuration >= 0) {
|
|
617
619
|
shellOpts.timeout = options.waitDuration;
|
|
618
620
|
}
|
|
619
|
-
const stdout = await this.shell(cmd, shellOpts);
|
|
621
|
+
const stdout = await this.shell(cmd, {...shellOpts, quoteArgs: true});
|
|
620
622
|
if (stdout.includes('Error: Activity class') && stdout.includes('does not exist')) {
|
|
621
623
|
if (options.retry && options.activity && !options.activity.startsWith('.')) {
|
|
622
624
|
log.debug(
|
|
@@ -886,41 +888,41 @@ export async function waitForNotActivity(this: ADB, pkg: string, act: string, wa
|
|
|
886
888
|
}
|
|
887
889
|
|
|
888
890
|
/**
|
|
889
|
-
* Builds
|
|
890
|
-
*
|
|
891
|
+
* Builds literal arguments for the given application startup options.
|
|
892
|
+
* Execute the result with `shell` with `quoteArgs: true`, not `shell`.
|
|
891
893
|
*
|
|
892
894
|
* @param startAppOptions - Application options mapping
|
|
893
895
|
* @param apiLevel - The actual OS API level
|
|
894
|
-
* @returns
|
|
896
|
+
* @returns Unquoted command arguments
|
|
895
897
|
*/
|
|
896
898
|
export function buildStartCmd(startAppOptions: StartCmdOptions, apiLevel: number): string[] {
|
|
897
899
|
const {user, waitForLaunch, pkg, activity, action, category, stopApp, flags, optionalIntentArguments} =
|
|
898
900
|
startAppOptions;
|
|
899
901
|
const cmd = ['am', apiLevel < 26 ? 'start' : 'start-activity'];
|
|
900
902
|
if (util.hasValue(user)) {
|
|
901
|
-
cmd.push('--user',
|
|
903
|
+
cmd.push('--user', `${user}`);
|
|
902
904
|
}
|
|
903
905
|
if (waitForLaunch) {
|
|
904
906
|
cmd.push('-W');
|
|
905
907
|
}
|
|
906
908
|
if (activity && pkg) {
|
|
907
909
|
const component = activity.startsWith(`${pkg}/`) ? activity : `${pkg}/${activity}`;
|
|
908
|
-
cmd.push('-n',
|
|
910
|
+
cmd.push('-n', component);
|
|
909
911
|
}
|
|
910
912
|
if (stopApp && apiLevel >= 15) {
|
|
911
913
|
cmd.push('-S');
|
|
912
914
|
}
|
|
913
915
|
if (action) {
|
|
914
|
-
cmd.push('-a',
|
|
916
|
+
cmd.push('-a', action);
|
|
915
917
|
}
|
|
916
918
|
if (category) {
|
|
917
|
-
cmd.push('-c',
|
|
919
|
+
cmd.push('-c', category);
|
|
918
920
|
}
|
|
919
921
|
if (flags) {
|
|
920
|
-
cmd.push('-f',
|
|
922
|
+
cmd.push('-f', flags);
|
|
921
923
|
}
|
|
922
924
|
if (optionalIntentArguments) {
|
|
923
|
-
cmd.push(...parseOptionalIntentArguments(optionalIntentArguments)
|
|
925
|
+
cmd.push(...parseOptionalIntentArguments(optionalIntentArguments));
|
|
924
926
|
}
|
|
925
927
|
return cmd;
|
|
926
928
|
}
|
|
@@ -1023,7 +1025,7 @@ export function matchComponentName(classString: string): RegExpExecArray | null
|
|
|
1023
1025
|
/**
|
|
1024
1026
|
* Asserts a package/activity name has only legal component characters, rejecting shell
|
|
1025
1027
|
* metacharacters to prevent command injection. The permitted `$` and `*` are still
|
|
1026
|
-
* shell-expansion characters and are
|
|
1028
|
+
* shell-expansion characters and are quoted at execution time by `shell` with `quoteArgs: true`.
|
|
1027
1029
|
*
|
|
1028
1030
|
* @param value - The package or activity name to verify
|
|
1029
1031
|
* @param kind - A human-readable label for the value used in the error message
|
|
@@ -1112,7 +1114,7 @@ export async function broadcast(this: ADB, intent: string): Promise<void> {
|
|
|
1112
1114
|
throw new Error(`Invalid intent ${intent}`);
|
|
1113
1115
|
}
|
|
1114
1116
|
log.debug(`Broadcasting: ${intent}`);
|
|
1115
|
-
await this.shell(['am', 'broadcast', '-a', intent]);
|
|
1117
|
+
await this.shell(['am', 'broadcast', '-a', intent], {quoteArgs: true});
|
|
1116
1118
|
}
|
|
1117
1119
|
|
|
1118
1120
|
/**
|
|
@@ -195,7 +195,7 @@ export async function setSetting(
|
|
|
195
195
|
setting: string,
|
|
196
196
|
value: string | number,
|
|
197
197
|
): Promise<string> {
|
|
198
|
-
return await this.shell(['settings', 'put',
|
|
198
|
+
return await this.shell(['settings', 'put', namespace, setting, `${value}`], {quoteArgs: true});
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
|
@@ -514,7 +514,7 @@ export async function addToDeviceIdleWhitelist(this: ADB, ...packages: string[])
|
|
|
514
514
|
}
|
|
515
515
|
|
|
516
516
|
log.info(`Adding ${util.pluralize('package', packages.length)} ${JSON.stringify(packages)} to Doze whitelist`);
|
|
517
|
-
await this.shellChunks((pkg) => ['dumpsys', 'deviceidle', 'whitelist', `+${pkg}`], packages);
|
|
517
|
+
await this.shellChunks((pkg) => ['dumpsys', 'deviceidle', 'whitelist', `+${pkg}`], packages, {quoteArgs: true});
|
|
518
518
|
return true;
|
|
519
519
|
}
|
|
520
520
|
|
package/lib/tools/fs-commands.ts
CHANGED
|
@@ -32,7 +32,7 @@ export async function fileExists(this: ADB, remotePath: string): Promise<boolean
|
|
|
32
32
|
export async function ls(this: ADB, remotePath: string, opts: string[] = []): Promise<string[]> {
|
|
33
33
|
try {
|
|
34
34
|
const args = ['ls', ...opts, remotePath];
|
|
35
|
-
const stdout = await this.shell(args);
|
|
35
|
+
const stdout = await this.shell(args, {quoteArgs: true});
|
|
36
36
|
const lines = stdout.split('\n');
|
|
37
37
|
return lines
|
|
38
38
|
.map((l) => l.trim())
|
|
@@ -79,7 +79,7 @@ export async function fileSize(this: ADB, remotePath: string): Promise<number> {
|
|
|
79
79
|
* @param path - The path to be removed recursively.
|
|
80
80
|
*/
|
|
81
81
|
export async function rimraf(this: ADB, path: string): Promise<void> {
|
|
82
|
-
await this.shell(['rm', '-rf', path]);
|
|
82
|
+
await this.shell(['rm', '-rf', path], {quoteArgs: true});
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
package/lib/tools/lockmgmt.ts
CHANGED
|
@@ -41,6 +41,7 @@ export async function verifyLockCredential(this: ADB, credential: string | null
|
|
|
41
41
|
try {
|
|
42
42
|
const {stdout, stderr} = await this.shell(buildCommand('verify', credential), {
|
|
43
43
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
44
|
+
quoteArgs: true,
|
|
44
45
|
});
|
|
45
46
|
if (stdout?.includes('verified successfully')) {
|
|
46
47
|
return true;
|
|
@@ -78,6 +79,7 @@ export async function clearLockCredential(this: ADB, credential: string | null =
|
|
|
78
79
|
try {
|
|
79
80
|
const {stdout, stderr} = await this.shell(buildCommand('clear', credential), {
|
|
80
81
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
82
|
+
quoteArgs: true,
|
|
81
83
|
});
|
|
82
84
|
if (!['user has no password', 'Lock credential cleared'].some((x) => (stderr || stdout)?.includes(x))) {
|
|
83
85
|
throw new Error(stderr || stdout);
|
|
@@ -106,6 +108,7 @@ export async function isLockEnabled(this: ADB): Promise<boolean> {
|
|
|
106
108
|
try {
|
|
107
109
|
const {stdout, stderr} = await this.shell(buildCommand('get-disabled'), {
|
|
108
110
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
111
|
+
quoteArgs: true,
|
|
109
112
|
});
|
|
110
113
|
if (
|
|
111
114
|
/\bfalse\b/.test(stdout) ||
|
|
@@ -160,6 +163,7 @@ export async function setLockCredential(
|
|
|
160
163
|
try {
|
|
161
164
|
const {stdout, stderr} = await this.shell(cmd, {
|
|
162
165
|
outputFormat: this.EXEC_OUTPUT_FORMAT.FULL,
|
|
166
|
+
quoteArgs: true,
|
|
163
167
|
});
|
|
164
168
|
if (!stdout?.includes('set to')) {
|
|
165
169
|
throw new Error(stderr || stdout);
|
|
@@ -318,10 +322,10 @@ export function isScreenOnFully(dumpsys: string): boolean {
|
|
|
318
322
|
function buildCommand(verb: string, oldCredential: string | null = null, ...args: string[]): string[] {
|
|
319
323
|
const cmd = ['locksettings', verb];
|
|
320
324
|
if (oldCredential && !util.isEmpty(oldCredential)) {
|
|
321
|
-
cmd.push('--old',
|
|
325
|
+
cmd.push('--old', `${oldCredential}`);
|
|
322
326
|
}
|
|
323
327
|
if (args.length > 0) {
|
|
324
|
-
cmd.push(...args.map((arg) =>
|
|
328
|
+
cmd.push(...args.map((arg) => `${arg}`));
|
|
325
329
|
}
|
|
326
330
|
return cmd;
|
|
327
331
|
}
|
|
@@ -465,7 +465,12 @@ export const EXEC_OUTPUT_FORMAT = {
|
|
|
465
465
|
} as const;
|
|
466
466
|
|
|
467
467
|
/**
|
|
468
|
-
* Execute the given adb command.
|
|
468
|
+
* Execute the given adb command without automatically quoting its arguments.
|
|
469
|
+
*
|
|
470
|
+
* When invoking `adb shell`, the device shell interprets the command.
|
|
471
|
+
* Unquoted values may be expanded or interpreted as shell syntax.
|
|
472
|
+
* For literal shell arguments, prefer `shell(args, {quoteArgs: true})`
|
|
473
|
+
* and pass unquoted argument values. `adbExec` itself does not apply `quoteArgs`.
|
|
469
474
|
*
|
|
470
475
|
* @param cmd - Command string or array of command arguments
|
|
471
476
|
* @param opts - Execution options with `outputFormat: 'full'`
|
|
@@ -478,7 +483,12 @@ export async function adbExec(
|
|
|
478
483
|
opts: AdbExecOptions & {outputFormat: 'full'},
|
|
479
484
|
): Promise<ExecResult>;
|
|
480
485
|
/**
|
|
481
|
-
* Execute the given adb command.
|
|
486
|
+
* Execute the given adb command without automatically quoting its arguments.
|
|
487
|
+
*
|
|
488
|
+
* When invoking `adb shell`, the device shell interprets the command.
|
|
489
|
+
* Unquoted values may be expanded or interpreted as shell syntax.
|
|
490
|
+
* For literal shell arguments, prefer `shell(args, {quoteArgs: true})`
|
|
491
|
+
* and pass unquoted argument values. `adbExec` itself does not apply `quoteArgs`.
|
|
482
492
|
*
|
|
483
493
|
* @param cmd - Command string or array of command arguments
|
|
484
494
|
* @param opts - Execution options
|
|
@@ -593,9 +603,10 @@ export async function shell(
|
|
|
593
603
|
*/
|
|
594
604
|
export async function shell(this: ADB, cmd: string | string[], opts?: ShellExecOptions): Promise<string>;
|
|
595
605
|
export async function shell(this: ADB, cmd: string | string[], opts?: ShellExecOptions): Promise<string | ExecResult> {
|
|
596
|
-
const {
|
|
597
|
-
|
|
598
|
-
const
|
|
606
|
+
const {quoteArgs = false, ...execOpts} = opts ?? {};
|
|
607
|
+
const {privileged} = execOpts;
|
|
608
|
+
const args = Array.isArray(cmd) ? cmd : [cmd];
|
|
609
|
+
const cmdArr = quoteArgs ? args.map((arg) => util.quote(arg)) : args;
|
|
599
610
|
const fullCmd: string[] = ['shell'];
|
|
600
611
|
if (privileged) {
|
|
601
612
|
log.info(`'adb shell ${util.quote(redactSecrets(cmdArr))}' requires root access`);
|
|
@@ -608,7 +619,7 @@ export async function shell(this: ADB, cmd: string | string[], opts?: ShellExecO
|
|
|
608
619
|
} else {
|
|
609
620
|
fullCmd.push(...cmdArr);
|
|
610
621
|
}
|
|
611
|
-
return await this.adbExec(fullCmd, opts);
|
|
622
|
+
return await this.adbExec(fullCmd, opts === undefined ? undefined : execOpts);
|
|
612
623
|
}
|
|
613
624
|
|
|
614
625
|
/**
|
|
@@ -1368,16 +1379,25 @@ export async function isMitmCertificateInstalled(this: ADB, cert: Buffer | strin
|
|
|
1368
1379
|
*
|
|
1369
1380
|
* @param argTransformer - Function to transform each argument into command array
|
|
1370
1381
|
* @param args - Array of arguments to process
|
|
1382
|
+
* @param opts - Set quoteArgs to true for unquoted literal command arguments (default false)
|
|
1371
1383
|
* @throws {Error} If argument transformer returns invalid result or command execution fails
|
|
1372
1384
|
*/
|
|
1373
|
-
export async function shellChunks(
|
|
1385
|
+
export async function shellChunks(
|
|
1386
|
+
this: ADB,
|
|
1387
|
+
argTransformer: (x: string) => string[],
|
|
1388
|
+
args: string[],
|
|
1389
|
+
opts: Pick<ShellExecOptions, 'quoteArgs'> = {},
|
|
1390
|
+
): Promise<void> {
|
|
1374
1391
|
const commands: string[][] = [];
|
|
1375
1392
|
let cmdChunk: string[] = [];
|
|
1376
1393
|
for (const arg of args) {
|
|
1377
|
-
const
|
|
1378
|
-
if (!Array.isArray(
|
|
1394
|
+
const command = argTransformer(arg);
|
|
1395
|
+
if (!Array.isArray(command)) {
|
|
1379
1396
|
throw new Error('Argument transformer must result in an array');
|
|
1380
1397
|
}
|
|
1398
|
+
// Quote before measuring chunks and adding separators. A caller-supplied
|
|
1399
|
+
// semicolon is literal data when quoting is enabled.
|
|
1400
|
+
const nextCmd = opts.quoteArgs ? command.map((value) => util.quote(value)) : command;
|
|
1381
1401
|
if (nextCmd.at(-1) !== ';') {
|
|
1382
1402
|
nextCmd.push(';');
|
|
1383
1403
|
}
|
package/lib/tools/types.ts
CHANGED
|
@@ -637,6 +637,14 @@ export interface SpecialAdbExecOptions {
|
|
|
637
637
|
}
|
|
638
638
|
|
|
639
639
|
export interface ShellExecOptions {
|
|
640
|
+
/**
|
|
641
|
+
* Quote each command argument as literal data for the device shell.
|
|
642
|
+
* Pass unquoted arguments when enabled. A string command is treated as one
|
|
643
|
+
* literal argument; use an array for an executable and its arguments.
|
|
644
|
+
* Defaults to false for compatibility with existing shell expressions.
|
|
645
|
+
* TODO: Enable by default in the next breaking release.
|
|
646
|
+
*/
|
|
647
|
+
quoteArgs?: boolean;
|
|
640
648
|
/**
|
|
641
649
|
* The name of the corresponding Appium's timeout capability
|
|
642
650
|
* (used in the error messages).
|
package/lib/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type {AppiumLoggerLevel} from '@appium/types';
|
|
2
|
+
|
|
1
3
|
import type {Logcat} from './logcat.js';
|
|
2
4
|
import type {StringRecord} from './tools/types.js';
|
|
3
5
|
|
|
@@ -27,6 +29,7 @@ export interface ADBOptions {
|
|
|
27
29
|
remoteAdbPort?: number;
|
|
28
30
|
clearDeviceLogsOnStart?: boolean;
|
|
29
31
|
listenAllNetwork?: boolean;
|
|
32
|
+
logLevel?: AppiumLoggerLevel;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export interface ADBExecutable {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appium-adb",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.3.0",
|
|
4
4
|
"description": "Android Debug Bridge interface",
|
|
5
5
|
"homepage": "https://appium.io",
|
|
6
6
|
"bugs": {
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"@types/ini": "^4.1.0",
|
|
58
58
|
"@types/node": "^26.6.5",
|
|
59
59
|
"@types/semver": "^7.5.0",
|
|
60
|
-
"@types/sinon": "^22.0.
|
|
60
|
+
"@types/sinon": "^22.0.2",
|
|
61
61
|
"sinon": "^22.0.0"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
|
|
65
65
|
"npm": ">=10"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "0cfdd5ff3c91b690c3f07a9efc400415c7bd24bb"
|
|
68
68
|
}
|