microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.5.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidContainerUtility.ts

250lines · 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 { 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 {
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
80export function pushFile(
81adbHelper: AdbHelper,
82deviceId: string,
83app: string,
84destFilepath: string,
85sourceFilepath: string,
86logger?: OutputChannelLogger,
87): Promise<void> {
88return validateAppName(app).then(validApp =>
89validateFilePath(destFilepath).then(validFilepath =>
90_pushFile(adbHelper, deviceId, validApp, validFilepath, sourceFilepath, logger),
91),
92);
93}
94
95function _pushFile(
96adbHelper: AdbHelper,
97deviceId: string,
98app: string,
99destFilepath: string,
100sourceFilepath: string,
101logger?: OutputChannelLogger,
102): Promise<void> {
103const destFileName = path.basename(destFilepath);
104const tmpFilePath = deviceTmpDir + destFileName;
105return adbHelper
106.executeQuery(deviceId, `push ${sourceFilepath} ${tmpFilePath}`)
107.then(res => {
108logger?.debug(res);
109const command = `cp "${tmpFilePath}" "${destFilepath}" && chmod 644 "${destFilepath}"`;
110return executeCommandAsApp(adbHelper, deviceId, app, command);
111})
112.then(res => {
113logger?.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> {
131if (app.match(allowedAppNameRegex)) {
132return Promise.resolve(app);
133}
134return Promise.reject(new Error(`Disallowed run-as user: ${app}`));
135}
136
137function validateFilePath(filePath: string): Promise<string> {
138if (!filePath.match(/[']/)) {
139return Promise.resolve(filePath);
140}
141return Promise.reject(new Error(`Disallowed escaping filepath: ${filePath}`));
142}
143
144function validateFileContent(content: string): Promise<string> {
145if (!content.match(/["]/)) {
146return Promise.resolve(content);
147}
148return Promise.reject(new Error(`Disallowed escaping file content: ${content}`));
149}
150
151function _push(
152adbHelper: AdbHelper,
153deviceId: string,
154app: string,
155filename: string,
156contents: string,
157logger?: OutputChannelLogger,
158): Promise<void> {
159const command = `echo \\"${contents}\\" > "${filename}" && chmod 644 "${filename}"`;
160return executeCommandAsApp(adbHelper, deviceId, app, command)
161.then(res => {
162logger?.debug(res);
163})
164.catch(error => {
165if (error instanceof RunAsError) {
166// Fall back to running the command directly. This will work if adb is running as root.
167return executeCommandWithSu(adbHelper, deviceId, app, command)
168.then(() => undefined)
169.catch(e => {
170logger?.debug(e.toString());
171throw error;
172});
173}
174throw error;
175});
176}
177
178function _pull(
179adbHelper: AdbHelper,
180deviceId: string,
181app: string,
182path: string,
183logger?: OutputChannelLogger,
184): Promise<string> {
185const command = `cat "${path}"`;
186return executeCommandAsApp(adbHelper, deviceId, app, command).catch(error => {
187if (error instanceof RunAsError) {
188// Fall back to running the command directly. This will work if adb is running as root.
189return executeCommandWithSu(adbHelper, deviceId, app, command).catch(e => {
190// Throw the original error.
191logger?.debug(e.toString());
192throw error;
193});
194}
195throw error;
196});
197}
198
199// Keep this method private since it relies on pre-validated arguments
200function executeCommandAsApp(
201adbHelper: AdbHelper,
202deviceId: string,
203app: string,
204command: string,
205): Promise<string> {
206return _executeCommandWithRunner(adbHelper, deviceId, app, command, `run-as '${app}'`);
207}
208
209function executeCommandWithSu(
210adbHelper: AdbHelper,
211deviceId: string,
212app: string,
213command: string,
214): Promise<string> {
215return _executeCommandWithRunner(adbHelper, deviceId, app, command, "su");
216}
217
218function _executeCommandWithRunner(
219adbHelper: AdbHelper,
220deviceId: string,
221app: string,
222command: string,
223runner: string,
224): Promise<string> {
225return adbHelper.executeShellCommand(deviceId, `echo '${command}' | ${runner}`).then(output => {
226if (output.match(appNotApplicationRegex)) {
227throw new RunAsError(
228RunAsErrorCode.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}
232if (output.match(appNotDebuggableRegex)) {
233throw new RunAsError(
234RunAsErrorCode.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}
238if (output.toLowerCase().match(operationNotPermittedRegex)) {
239throw 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}
243return 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*/