microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
caa0dc5eb84731caee69a4d06915c6a3856ec289

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/android/androidContainerUtility.ts

250lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import { AdbHelper } from "./adb";
5import * as path from "path";
6import { OutputChannelLogger } from "../log/OutputChannelLogger";
7
8const allowedAppNameRegex = /^[\w.-]+$/;
9const appNotApplicationRegex = /not an application/;
10const appNotDebuggableRegex = /debuggable/;
11const operationNotPermittedRegex = /not permitted/;
12const deviceTmpDir = "/sdcard/";
13
14// The code is borrowed from https://github.com/facebook/flipper/blob/master/desktop/app/src/utils/androidContainerUtility.tsx,
15// https://github.com/facebook/flipper/blob/master/desktop/app/src/utils/androidContainerUtilityInternal.tsx
16
17enum RunAsErrorCode {
18 NotAnApp = 1,
19 NotDebuggable = 2,
20}
21
22class RunAsError extends Error {
23 code: RunAsErrorCode;
24
25 constructor(code: RunAsErrorCode, message?: string) {
26 super(message);
27 this.code = code;
28 Object.setPrototypeOf(this, new.target.prototype);
29 }
30}
31
32/**
33 * @preserve
34 * Start region: the code is borrowed from https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/utils/androidContainerUtility.tsx#L19-L46
35 *
36 * Copyright (c) Facebook, Inc. and its affiliates.
37 *
38 * This source code is licensed under the MIT license found in the
39 * LICENSE file in the root directory of this source tree.
40 *
41 * @format
42 */
43
44export function push(
45 adbHelper: AdbHelper,
46 deviceId: string,
47 app: string,
48 filepath: string,
49 contents: string,
50 logger?: OutputChannelLogger,
51): Promise<void> {
52 return validateAppName(app).then(validApp =>
53 validateFilePath(filepath).then(validFilepath =>
54 validateFileContent(contents).then(validContent =>
55 _push(adbHelper, deviceId, validApp, validFilepath, validContent, logger),
56 ),
57 ),
58 );
59}
60
61export function pull(
62 adbHelper: AdbHelper,
63 deviceId: string,
64 app: string,
65 path: string,
66 logger?: OutputChannelLogger,
67): Promise<string> {
68 return validateAppName(app).then(validApp =>
69 validateFilePath(path).then(validPath =>
70 _pull(adbHelper, deviceId, validApp, validPath, logger),
71 ),
72 );
73}
74
75/**
76 * @preserve
77 * End region: https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/utils/androidContainerUtility.tsx#L19-L46
78 */
79
80export function pushFile(
81 adbHelper: AdbHelper,
82 deviceId: string,
83 app: string,
84 destFilepath: string,
85 sourceFilepath: string,
86 logger?: OutputChannelLogger,
87): Promise<void> {
88 return validateAppName(app).then(validApp =>
89 validateFilePath(destFilepath).then(validFilepath =>
90 _pushFile(adbHelper, deviceId, validApp, validFilepath, sourceFilepath, logger),
91 ),
92 );
93}
94
95function _pushFile(
96 adbHelper: AdbHelper,
97 deviceId: string,
98 app: string,
99 destFilepath: string,
100 sourceFilepath: string,
101 logger?: OutputChannelLogger,
102): Promise<void> {
103 const destFileName = path.basename(destFilepath);
104 const tmpFilePath = deviceTmpDir + destFileName;
105 return adbHelper
106 .executeQuery(deviceId, `push ${sourceFilepath} ${tmpFilePath}`)
107 .then(res => {
108 logger?.debug(res);
109 const command = `cp "${tmpFilePath}" "${destFilepath}" && chmod 644 "${destFilepath}"`;
110 return executeCommandAsApp(adbHelper, deviceId, app, command);
111 })
112 .then(res => {
113 logger?.debug(res);
114 })
115 .finally(() => adbHelper.executeShellCommand(deviceId, `rm ${tmpFilePath}`));
116}
117
118/**
119 * @preserve
120 * Start region: the code is borrowed from https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/utils/androidContainerUtilityInternal.tsx
121 *
122 * Copyright (c) Facebook, Inc. and its affiliates.
123 *
124 * This source code is licensed under the MIT license found in the
125 * LICENSE file in the root directory of this source tree.
126 *
127 * @format
128 */
129
130function validateAppName(app: string): Promise<string> {
131 if (app.match(allowedAppNameRegex)) {
132 return Promise.resolve(app);
133 }
134 return Promise.reject(new Error(`Disallowed run-as user: ${app}`));
135}
136
137function validateFilePath(filePath: string): Promise<string> {
138 if (!filePath.match(/[']/)) {
139 return Promise.resolve(filePath);
140 }
141 return Promise.reject(new Error(`Disallowed escaping filepath: ${filePath}`));
142}
143
144function validateFileContent(content: string): Promise<string> {
145 if (!content.match(/["]/)) {
146 return Promise.resolve(content);
147 }
148 return Promise.reject(new Error(`Disallowed escaping file content: ${content}`));
149}
150
151function _push(
152 adbHelper: AdbHelper,
153 deviceId: string,
154 app: string,
155 filename: string,
156 contents: string,
157 logger?: OutputChannelLogger,
158): Promise<void> {
159 const command = `echo \\"${contents}\\" > "${filename}" && chmod 644 "${filename}"`;
160 return executeCommandAsApp(adbHelper, deviceId, app, command)
161 .then(res => {
162 logger?.debug(res);
163 })
164 .catch(error => {
165 if (error instanceof RunAsError) {
166 // Fall back to running the command directly. This will work if adb is running as root.
167 return executeCommandWithSu(adbHelper, deviceId, app, command)
168 .then(() => undefined)
169 .catch(e => {
170 logger?.debug(e.toString());
171 throw error;
172 });
173 }
174 throw error;
175 });
176}
177
178function _pull(
179 adbHelper: AdbHelper,
180 deviceId: string,
181 app: string,
182 path: string,
183 logger?: OutputChannelLogger,
184): Promise<string> {
185 const command = `cat "${path}"`;
186 return executeCommandAsApp(adbHelper, deviceId, app, command).catch(error => {
187 if (error instanceof RunAsError) {
188 // Fall back to running the command directly. This will work if adb is running as root.
189 return executeCommandWithSu(adbHelper, deviceId, app, command).catch(e => {
190 // Throw the original error.
191 logger?.debug(e.toString());
192 throw error;
193 });
194 }
195 throw error;
196 });
197}
198
199// Keep this method private since it relies on pre-validated arguments
200function executeCommandAsApp(
201 adbHelper: AdbHelper,
202 deviceId: string,
203 app: string,
204 command: string,
205): Promise<string> {
206 return _executeCommandWithRunner(adbHelper, deviceId, app, command, `run-as '${app}'`);
207}
208
209function executeCommandWithSu(
210 adbHelper: AdbHelper,
211 deviceId: string,
212 app: string,
213 command: string,
214): Promise<string> {
215 return _executeCommandWithRunner(adbHelper, deviceId, app, command, "su");
216}
217
218function _executeCommandWithRunner(
219 adbHelper: AdbHelper,
220 deviceId: string,
221 app: string,
222 command: string,
223 runner: string,
224): Promise<string> {
225 return adbHelper.executeShellCommand(deviceId, `echo '${command}' | ${runner}`).then(output => {
226 if (output.match(appNotApplicationRegex)) {
227 throw new RunAsError(
228 RunAsErrorCode.NotAnApp,
229 `Android package ${app} is not an application. To use it with Flipper, either run adb as root or add an <application> tag to AndroidManifest.xml`,
230 );
231 }
232 if (output.match(appNotDebuggableRegex)) {
233 throw new RunAsError(
234 RunAsErrorCode.NotDebuggable,
235 `Android app ${app} is not debuggable. To use it with Flipper, add android:debuggable="true" to the application section of AndroidManifest.xml`,
236 );
237 }
238 if (output.toLowerCase().match(operationNotPermittedRegex)) {
239 throw new Error(
240 `Your android device (${deviceId}) does not support the adb shell run-as command. We're tracking this at https://github.com/facebook/flipper/issues/92`,
241 );
242 }
243 return output;
244 });
245}
246
247/**
248 * @preserve
249 * End region: https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/utils/androidContainerUtilityInternal.tsx
250 */
251