microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
b7451aef6bd5846d7333cd23872aaadd1d57136c

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/cdp-proxy/debuggerEndpointHelper.ts

147lines · 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 URL from "url";
5import * as ipModule from "ip";
6const dns = require("dns").promises;
7import * as http from "http";
8import * as https from "https";
9import { PromiseUtil } from "../common/node/promise";
10
11export class DebuggerEndpointHelper {
12 private localv4: Buffer;
13 private localv6: Buffer;
14 private promiseUtil: PromiseUtil;
15
16 constructor() {
17 this.localv4 = ipModule.toBuffer("127.0.0.1");
18 this.localv6 = ipModule.toBuffer("::1");
19 this.promiseUtil = new PromiseUtil();
20 }
21
22 /**
23 * Attempts to retrieve the debugger websocket URL for a process listening
24 * at the given address, retrying until available.
25 * @param browserURL -- Address like `http://localhost:1234`
26 */
27 public async retryGetWSEndpoint(browserURL: string, attemptNumber: number): Promise<string> {
28 try {
29 return await this.getWSEndpoint(browserURL);
30 } catch (e) {
31 if (attemptNumber < 1) {
32 throw new Error(`Could not connect to debug target at ${browserURL}: ${e.message}`);
33 }
34
35 await this.promiseUtil.delay(1000);
36 return await this.retryGetWSEndpoint(browserURL, --attemptNumber);
37 }
38 }
39
40 /**
41 * Returns the debugger websocket URL a process listening at the given address.
42 * @param browserURL -- Address like `http://localhost:1234`
43 */
44 public async getWSEndpoint(browserURL: string): Promise<string> {
45 const jsonVersion = await this.fetchJson<{ webSocketDebuggerUrl?: string }>(
46 URL.resolve(browserURL, "/json/version")
47 );
48 if (jsonVersion.webSocketDebuggerUrl) {
49 return jsonVersion.webSocketDebuggerUrl;
50 }
51
52 // Chrome its top-level debugg on /json/version, while Node does not.
53 // Request both and return whichever one got us a string.
54 const jsonList = await this.fetchJson<{ webSocketDebuggerUrl: string }[]>(
55 URL.resolve(browserURL, "/json/list")
56 );
57 if (jsonList.length) {
58 return jsonList[0].webSocketDebuggerUrl;
59 }
60
61 throw new Error("Could not find any debuggable target");
62 }
63
64 /**
65 * Fetches JSON content from the given URL.
66 */
67 private async fetchJson<T>(url: string): Promise<T> {
68 const data = await this.fetch(url);
69 return JSON.parse(data);
70 }
71
72 /**
73 * Fetches content from the given URL.
74 */
75 private async fetch(url: string): Promise<string> {
76 const isSecure = !url.startsWith("http://");
77 const driver = isSecure ? https : http;
78 const targetAddressIsLoopback = await this.isLoopback(url);
79
80 return new Promise<string>((fulfill, reject) => {
81 const requestOptions: https.RequestOptions = {};
82
83 if (isSecure && targetAddressIsLoopback) {
84 requestOptions.rejectUnauthorized = false;
85 }
86
87 const request = driver.get(url, requestOptions, response => {
88
89 let data = "";
90 response.setEncoding("utf8");
91 response.on("data", (chunk: string) => (data += chunk));
92 response.on("end", () => fulfill(data));
93 response.on("error", reject);
94 });
95
96 request.on("error", reject);
97 request.end();
98 });
99 }
100
101 /**
102 * Gets whether the IP is a loopback address.
103 */
104 private async isLoopback(address: string) {
105 let ipOrHostname: string;
106 try {
107 const url = new URL.URL(address);
108 // replace brackets in ipv6 addresses:
109 ipOrHostname = url.hostname.replace(/^\[|\]$/g, "");
110 } catch {
111 ipOrHostname = address;
112 }
113
114 if (this.isLoopbackIp(ipOrHostname)) {
115 return true;
116 }
117
118 try {
119 const resolved = await dns.lookup(ipOrHostname);
120 return this.isLoopbackIp(resolved.address);
121 } catch {
122 return false;
123 }
124 }
125
126 /**
127 * Checks if the given address, well-formed loopback IPs. We don't need exotic
128 * variations like `127.1` because `dns.lookup()` will resolve the proper
129 * version for us. The "right" way would be to parse the IP to an integer
130 * like Go does (https://golang.org/pkg/net/#IP.IsLoopback), but this
131 * is lightweight and works.
132 */
133 private isLoopbackIp(ipOrLocalhost: string) {
134 if (ipOrLocalhost.toLowerCase() === "localhost") {
135 return true;
136 }
137
138 let buf: Buffer;
139 try {
140 buf = ipModule.toBuffer(ipOrLocalhost);
141 } catch {
142 return false;
143 }
144
145 return buf.equals(this.localv4) || buf.equals(this.localv6);
146 }
147}
148