MISC: Validate hostname and port of RFA (#1721)

This commit is contained in:
catloversg 2024-10-24 08:12:04 +07:00 committed by GitHub
parent f49d3b32a5
commit 810cfc8b21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 82 additions and 17 deletions

@ -1,28 +1,46 @@
import React, { useState } from "react";
import { Button, Link, TextField, Tooltip, Typography } from "@mui/material";
import { GameOptionsPage } from "./GameOptionsPage";
import { Settings } from "../../Settings/Settings";
import { isValidConnectionHostname, isValidConnectionPort, Settings } from "../../Settings/Settings";
import { ConnectionBauble } from "./ConnectionBauble";
import { isRemoteFileApiConnectionLive, newRemoteFileApiConnection } from "../../RemoteFileAPI/RemoteFileAPI";
export const RemoteAPIPage = (): React.ReactElement => {
const [remoteFileApiHostname, setRemoteFileApiHostname] = useState(Settings.RemoteFileApiAddress);
const [remoteFileApiPort, setRemoteFileApiPort] = useState(Settings.RemoteFileApiPort);
const [remoteFileApiAddress, setRemoteFileApiAddress] = useState(Settings.RemoteFileApiAddress);
function handleRemoteFileApiPortChange(event: React.ChangeEvent<HTMLInputElement>): void {
setRemoteFileApiPort(Number(event.target.value));
Settings.RemoteFileApiPort = Number(event.target.value);
function handleRemoteFileApiHostnameChange(event: React.ChangeEvent<HTMLInputElement>): void {
let newValue = event.target.value.trim();
// Empty string will be automatically changed to "localhost".
if (newValue === "") {
newValue = "localhost";
}
if (!isValidConnectionHostname(newValue)) {
return;
}
setRemoteFileApiHostname(newValue);
Settings.RemoteFileApiAddress = newValue;
}
function handleRemoteFileApiAddressChange(event: React.ChangeEvent<HTMLInputElement>): void {
setRemoteFileApiAddress(String(event.target.value));
Settings.RemoteFileApiAddress = String(event.target.value);
function handleRemoteFileApiPortChange(event: React.ChangeEvent<HTMLInputElement>): void {
let newValue = event.target.value.trim();
// Empty string will be automatically changed to "0".
if (newValue === "") {
newValue = "0";
}
const port = Number.parseInt(newValue);
// Disallow invalid ports but still allow the player to set port to 0. Setting it to 0 means that RFA is disabled.
if (port !== 0 && !isValidConnectionPort(port)) {
return;
}
setRemoteFileApiPort(port);
Settings.RemoteFileApiPort = port;
}
return (
<GameOptionsPage title="Remote API">
<Typography>
These settings control the Remote API for bitburner. This is typically used to write scripts using an external
These settings control the Remote API for Bitburner. This is typically used to write scripts using an external
text editor and then upload files to the home server.
</Typography>
<Typography>
@ -37,18 +55,17 @@ export const RemoteAPIPage = (): React.ReactElement => {
<Tooltip
title={
<Typography>
This address is used to connect to a Remote API, please ensure that it matches with your Remote API address.
Default localhost.
This hostname is used to connect to a Remote API, please ensure that it matches with your Remote API
hostname. Default: localhost.
</Typography>
}
>
<TextField
key={"remoteAPIAddress"}
InputProps={{
startAdornment: <Typography>Address:&nbsp;</Typography>,
startAdornment: <Typography>Hostname:&nbsp;</Typography>,
}}
value={remoteFileApiAddress}
onChange={handleRemoteFileApiAddressChange}
value={remoteFileApiHostname}
onChange={handleRemoteFileApiHostnameChange}
placeholder="localhost"
size={"medium"}
/>
@ -63,10 +80,9 @@ export const RemoteAPIPage = (): React.ReactElement => {
}
>
<TextField
key={"remoteAPIPort"}
InputProps={{
startAdornment: (
<Typography color={remoteFileApiPort > 0 && remoteFileApiPort <= 65535 ? "success" : "error"}>
<Typography color={isValidConnectionPort(remoteFileApiPort) ? "success" : "error"}>
Port:&nbsp;
</Typography>
),

@ -4,6 +4,45 @@ import { defaultStyles } from "../Themes/Styles";
import { CursorStyle, CursorBlinking, WordWrapOptions } from "../ScriptEditor/ui/Options";
import { defaultMonacoTheme } from "../ScriptEditor/ui/themes";
/**
* This function won't be able to catch **all** invalid hostnames, and it's still fine. In order to validate a hostname
* properly, we need to import a good validation library or write one by ourselves. I think that it's unnecessary.
*
* Some invalid hostnames that we don't catch:
* - Invalid/missing TLD: "abc".
* - Use space character: "a a.com"
* - Use non-http schemes in the hostname: "ftp://a.com"
* - etc.
*/
export function isValidConnectionHostname(hostname: string): boolean {
/**
* We expect a hostname, but the player may mistakenly put other unexpected things. We will try to catch common mistakes:
* - Specify a scheme: http or https.
* - Specify a port.
* - Specify a pathname or search params.
*/
try {
// Check scheme.
if (hostname.startsWith("http://") || hostname.startsWith("https://")) {
return false;
}
// Parse to a URL with a default scheme.
const url = new URL(`http://${hostname}`);
// Check port, pathname, and search params.
if (url.port !== "" || url.pathname !== "/" || url.search !== "") {
return false;
}
} catch (e) {
console.error(`Invalid hostname: ${hostname}`, e);
return false;
}
return true;
}
export function isValidConnectionPort(port: number): boolean {
return Number.isFinite(port) && port > 0 && port <= 65535;
}
/** The current options the player has customized to their play style. */
export const Settings = {
/** How many servers per page */
@ -125,5 +164,15 @@ export const Settings = {
save.EditorTheme && Object.assign(Settings.EditorTheme, save.EditorTheme);
delete save.theme, save.styles, save.overview, save.EditorTheme;
Object.assign(Settings, save);
/**
* The hostname and port of RFA have not been validated properly, so the save data may contain invalid data. In that
* case, we set them to the default value.
*/
if (!isValidConnectionHostname(Settings.RemoteFileApiAddress)) {
Settings.RemoteFileApiAddress = "localhost";
}
if (!isValidConnectionPort(Settings.RemoteFileApiPort)) {
Settings.RemoteFileApiPort = 0;
}
},
};