microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/add-network-inspector-server-tests

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidContainerUtility.ts

254lines · 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 * as path from "path";
5import { OutputChannelLogger } from "../log/OutputChannelLogger";
6import { AdbHelper } from "./adb";
7
8const allowedAppNameRegex = /^[\w.-]+$/;
9const appNotApplicationRegex = /not an application/;
10const appNotDebuggableRegex = /debuggable/;
11const operationNotPermittedRegex = /not permitted/;
12const deviceTmpDir = "/data/local/tmp/";
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 async function pushFile(
81 adbHelper: AdbHelper,
82 deviceId: string,
83 app: string,
84 destFilepath: string,
85 sourceFilepath: string,
86 logger?: OutputChannelLogger,
87): Promise<void> {
88 const validApp = await validateAppName(app);
89 const validFilepath = await validateFilePath(destFilepath);
90 return _pushFile(adbHelper, deviceId, validApp, validFilepath, sourceFilepath, logger);
91}
92
93async function _pushFile(
94 adbHelper: AdbHelper,
95 deviceId: string,
96 app: string,
97 destFilepath: string,
98 sourceFilepath: string,
99 logger?: OutputChannelLogger,
100): Promise<void> {
101 const destFileName = path.basename(destFilepath);
102 const tmpFilePath = deviceTmpDir + destFileName;
103
104 try {
105 const pushRes = await adbHelper.executeQuery(deviceId, [
106 "push",
107 sourceFilepath,
108 tmpFilePath,
109 ]);
110 logger?.debug(pushRes);
111 const command = `cp "${tmpFilePath}" "${destFilepath}" && chmod 644 "${destFilepath}"`;
112 const appCommandRes = await executeCommandAsApp(adbHelper, deviceId, app, command);
113 logger?.debug(appCommandRes);
114 } finally {
115 await adbHelper.executeShellCommand(deviceId, `rm ${tmpFilePath}`);
116 }
117}
118
119/**
120 * @preserve
121 * Start region: the code is borrowed from https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/utils/androidContainerUtilityInternal.tsx
122 *
123 * Copyright (c) Facebook, Inc. and its affiliates.
124 *
125 * This source code is licensed under the MIT license found in the
126 * LICENSE file in the root directory of this source tree.
127 *
128 * @format
129 */
130
131function validateAppName(app: string): Promise<string> {
132 if (app.match(allowedAppNameRegex)) {
133 return Promise.resolve(app);
134 }
135 return Promise.reject(new Error(`Disallowed run-as user: ${app}`));
136}
137
138function validateFilePath(filePath: string): Promise<string> {
139 if (!/^[A-Za-z0-9._\/-]+$/.test(filePath)) {
140 return Promise.reject(new Error(`Disallowed filepath characters: ${filePath}`));
141 }
142 if (filePath.includes("..")) {
143 return Promise.reject(new Error(`Path traversal not allowed: ${filePath}`));
144 }
145 return Promise.resolve(filePath);
146}
147
148function validateFileContent(content: string): Promise<string> {
149 if (!content.match(/"/)) {
150 return Promise.resolve(content);
151 }
152 return Promise.reject(new Error(`Disallowed escaping file content: ${content}`));
153}
154
155function _push(
156 adbHelper: AdbHelper,
157 deviceId: string,
158 app: string,
159 filename: string,
160 contents: string,
161 logger?: OutputChannelLogger,
162): Promise<void> {
163 const command = `echo \\"${contents}\\" > "${filename}" && chmod 644 "${filename}"`;
164 return executeCommandAsApp(adbHelper, deviceId, app, command)
165 .then(res => {
166 logger?.debug(res);
167 })
168 .catch(error => {
169 if (error instanceof RunAsError) {
170 // Fall back to running the command directly. This will work if adb is running as root.
171 return executeCommandWithSu(adbHelper, deviceId, app, command)
172 .then(() => undefined)
173 .catch(e => {
174 logger?.debug(e.toString());
175 throw error;
176 });
177 }
178 throw error;
179 });
180}
181
182function _pull(
183 adbHelper: AdbHelper,
184 deviceId: string,
185 app: string,
186 path: string,
187 logger?: OutputChannelLogger,
188): Promise<string> {
189 const command = `cat "${path}"`;
190 return executeCommandAsApp(adbHelper, deviceId, app, command).catch(error => {
191 if (error instanceof RunAsError) {
192 // Fall back to running the command directly. This will work if adb is running as root.
193 return executeCommandWithSu(adbHelper, deviceId, app, command).catch(e => {
194 // Throw the original error.
195 logger?.debug(e.toString());
196 throw error;
197 });
198 }
199 throw error;
200 });
201}
202
203// Keep this method private since it relies on pre-validated arguments
204function executeCommandAsApp(
205 adbHelper: AdbHelper,
206 deviceId: string,
207 app: string,
208 command: string,
209): Promise<string> {
210 return _executeCommandWithRunner(adbHelper, deviceId, app, command, `run-as '${app}'`);
211}
212
213function executeCommandWithSu(
214 adbHelper: AdbHelper,
215 deviceId: string,
216 app: string,
217 command: string,
218): Promise<string> {
219 return _executeCommandWithRunner(adbHelper, deviceId, app, command, "su");
220}
221
222function _executeCommandWithRunner(
223 adbHelper: AdbHelper,
224 deviceId: string,
225 app: string,
226 command: string,
227 runner: string,
228): Promise<string> {
229 return adbHelper.executeShellCommand(deviceId, `echo '${command}' | ${runner}`).then(output => {
230 if (output.match(appNotApplicationRegex)) {
231 throw new RunAsError(
232 RunAsErrorCode.NotAnApp,
233 `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`,
234 );
235 }
236 if (output.match(appNotDebuggableRegex)) {
237 throw new RunAsError(
238 RunAsErrorCode.NotDebuggable,
239 `Android app ${app} is not debuggable. To use it with Flipper, add android:debuggable="true" to the application section of AndroidManifest.xml`,
240 );
241 }
242 if (output.toLowerCase().match(operationNotPermittedRegex)) {
243 throw new Error(
244 `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`,
245 );
246 }
247 return output;
248 });
249}
250
251/**
252 * @preserve
253 * End region: https://github.com/facebook/flipper/blob/v0.79.1/desktop/app/src/utils/androidContainerUtilityInternal.tsx
254 */
255