microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
readme-sync-master-command-behaviors

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidContainerUtility.ts

254lines · modeblame

4bb0956eRedMickey5 years ago1// 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";
09f6024fHeniker4 years ago6import { AdbHelper } from "./adb";
4bb0956eRedMickey5 years ago7
8const allowedAppNameRegex = /^[\w.-]+$/;
9const appNotApplicationRegex = /not an application/;
10const appNotDebuggableRegex = /debuggable/;
11const operationNotPermittedRegex = /not permitted/;
d7456650RedMickey4 years ago12const deviceTmpDir = "/data/local/tmp/";
4bb0956eRedMickey5 years ago13
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 {
18NotAnApp = 1,
19NotDebuggable = 2,
20}
21
22class RunAsError extends Error {
23code: RunAsErrorCode;
24
25constructor(code: RunAsErrorCode, message?: string) {
26super(message);
27this.code = code;
28Object.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(
45adbHelper: AdbHelper,
46deviceId: string,
47app: string,
48filepath: string,
49contents: string,
50logger?: OutputChannelLogger,
51): Promise<void> {
52return validateAppName(app).then(validApp =>
53validateFilePath(filepath).then(validFilepath =>
54validateFileContent(contents).then(validContent =>
55_push(adbHelper, deviceId, validApp, validFilepath, validContent, logger),
56),
57),
58);
59}
60
61export function pull(
62adbHelper: AdbHelper,
63deviceId: string,
64app: string,
65path: string,
66logger?: OutputChannelLogger,
67): Promise<string> {
68return validateAppName(app).then(validApp =>
69validateFilePath(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
0d77292aJiglioNero4 years ago80export async function pushFile(
4bb0956eRedMickey5 years ago81adbHelper: AdbHelper,
82deviceId: string,
83app: string,
84destFilepath: string,
85sourceFilepath: string,
86logger?: OutputChannelLogger,
87): Promise<void> {
0d77292aJiglioNero4 years ago88const validApp = await validateAppName(app);
89const validFilepath = await validateFilePath(destFilepath);
90return _pushFile(adbHelper, deviceId, validApp, validFilepath, sourceFilepath, logger);
4bb0956eRedMickey5 years ago91}
92
0d77292aJiglioNero4 years ago93async function _pushFile(
4bb0956eRedMickey5 years ago94adbHelper: AdbHelper,
95deviceId: string,
96app: string,
97destFilepath: string,
98sourceFilepath: string,
99logger?: OutputChannelLogger,
100): Promise<void> {
101const destFileName = path.basename(destFilepath);
102const tmpFilePath = deviceTmpDir + destFileName;
0d77292aJiglioNero4 years ago103
104try {
be2158bdlucygramley4 weeks ago105const pushRes = await adbHelper.executeQuery(deviceId, [
106"push",
107sourceFilepath,
108tmpFilePath,
109]);
0d77292aJiglioNero4 years ago110logger?.debug(pushRes);
111const command = `cp "${tmpFilePath}" "${destFilepath}" && chmod 644 "${destFilepath}"`;
112const appCommandRes = await executeCommandAsApp(adbHelper, deviceId, app, command);
113logger?.debug(appCommandRes);
114} finally {
115await adbHelper.executeShellCommand(deviceId, `rm ${tmpFilePath}`);
116}
4bb0956eRedMickey5 years ago117}
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> {
132if (app.match(allowedAppNameRegex)) {
133return Promise.resolve(app);
134}
135return Promise.reject(new Error(`Disallowed run-as user: ${app}`));
136}
137
138function validateFilePath(filePath: string): Promise<string> {
7d3e1c06Lucy Gramley1 months ago139if (!/^[A-Za-z0-9._\/-]+$/.test(filePath)) {
140return Promise.reject(new Error(`Disallowed filepath characters: ${filePath}`));
4bb0956eRedMickey5 years ago141}
7d3e1c06Lucy Gramley1 months ago142if (filePath.includes("..")) {
143return Promise.reject(new Error(`Path traversal not allowed: ${filePath}`));
144}
145return Promise.resolve(filePath);
4bb0956eRedMickey5 years ago146}
147
148function validateFileContent(content: string): Promise<string> {
09f6024fHeniker4 years ago149if (!content.match(/"/)) {
4bb0956eRedMickey5 years ago150return Promise.resolve(content);
151}
152return Promise.reject(new Error(`Disallowed escaping file content: ${content}`));
153}
154
155function _push(
156adbHelper: AdbHelper,
157deviceId: string,
158app: string,
159filename: string,
160contents: string,
161logger?: OutputChannelLogger,
162): Promise<void> {
163const command = `echo \\"${contents}\\" > "${filename}" && chmod 644 "${filename}"`;
164return executeCommandAsApp(adbHelper, deviceId, app, command)
165.then(res => {
166logger?.debug(res);
167})
168.catch(error => {
169if (error instanceof RunAsError) {
170// Fall back to running the command directly. This will work if adb is running as root.
171return executeCommandWithSu(adbHelper, deviceId, app, command)
172.then(() => undefined)
173.catch(e => {
174logger?.debug(e.toString());
175throw error;
176});
177}
178throw error;
179});
180}
181
182function _pull(
183adbHelper: AdbHelper,
184deviceId: string,
185app: string,
186path: string,
187logger?: OutputChannelLogger,
188): Promise<string> {
189const command = `cat "${path}"`;
190return executeCommandAsApp(adbHelper, deviceId, app, command).catch(error => {
191if (error instanceof RunAsError) {
192// Fall back to running the command directly. This will work if adb is running as root.
193return executeCommandWithSu(adbHelper, deviceId, app, command).catch(e => {
194// Throw the original error.
195logger?.debug(e.toString());
196throw error;
197});
198}
199throw error;
200});
201}
202
203// Keep this method private since it relies on pre-validated arguments
204function executeCommandAsApp(
205adbHelper: AdbHelper,
206deviceId: string,
207app: string,
208command: string,
209): Promise<string> {
210return _executeCommandWithRunner(adbHelper, deviceId, app, command, `run-as '${app}'`);
211}
212
213function executeCommandWithSu(
214adbHelper: AdbHelper,
215deviceId: string,
216app: string,
217command: string,
218): Promise<string> {
219return _executeCommandWithRunner(adbHelper, deviceId, app, command, "su");
220}
221
222function _executeCommandWithRunner(
223adbHelper: AdbHelper,
224deviceId: string,
225app: string,
226command: string,
227runner: string,
228): Promise<string> {
229return adbHelper.executeShellCommand(deviceId, `echo '${command}' | ${runner}`).then(output => {
230if (output.match(appNotApplicationRegex)) {
231throw new RunAsError(
232RunAsErrorCode.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}
236if (output.match(appNotDebuggableRegex)) {
237throw new RunAsError(
238RunAsErrorCode.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}
242if (output.toLowerCase().match(operationNotPermittedRegex)) {
243throw 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}
247return 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*/