microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.1.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/cdp-proxy/debuggerEndpointHelper.ts

164lines · modeblame

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