Skip to content

Commit

Permalink
MISC: Validate hostname and port of RFA (#1721)
Browse files Browse the repository at this point in the history
  • Loading branch information
catloversg authored Oct 24, 2024
1 parent f49d3b3 commit 810cfc8
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 17 deletions.
50 changes: 33 additions & 17 deletions src/GameOptions/ui/RemoteAPIPage.tsx
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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"}
/>
Expand All @@ -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>
),
Expand Down
49 changes: 49 additions & 0 deletions src/Settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}
},
};

0 comments on commit 810cfc8

Please sign in to comment.