mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-05-13 02:50:04 +02:00
DARKNET: Prevent generating malformed darknet server hostname (#2744)
This commit is contained in:
@@ -18,6 +18,7 @@ import { hasFullDarknetAccess } from "../effects/effects";
|
||||
import { getFriendlyType, TypeAssertionError } from "../../utils/TypeAssertion";
|
||||
import { isIPAddress } from "../../Types/strings";
|
||||
import { roundToTwo } from "../../utils/helpers/roundToTwo";
|
||||
import { safelyReverseString } from "../../utils/StringHelperFunctions";
|
||||
|
||||
export type PasswordResponse = {
|
||||
code: DarknetResponseCode;
|
||||
@@ -158,11 +159,11 @@ const decorateName = (name: string): string => {
|
||||
const connector = connectors[Math.floor(Math.random() * connectors.length)];
|
||||
|
||||
if (Math.random() < 0.3) {
|
||||
updatedName = l33tifyName(name);
|
||||
updatedName = l33tifyName(updatedName);
|
||||
}
|
||||
|
||||
if (Math.random() < 0.05) {
|
||||
updatedName = updatedName.split("").reverse().join("");
|
||||
updatedName = safelyReverseString(updatedName);
|
||||
}
|
||||
|
||||
if (Math.random() < 0.1) {
|
||||
@@ -180,7 +181,11 @@ const decorateName = (name: string): string => {
|
||||
}
|
||||
} while (GetServer(updatedName) !== null);
|
||||
|
||||
return updatedName;
|
||||
// Defensive coding. All operations above preserve well-formed UTF-16, so this is currently redundant. It's a
|
||||
// safeguard to ensure the function never returns ill-formed UTF-16 if future changes introduce code unit–level
|
||||
// manipulation.
|
||||
// This normalization is lossy (lone surrogates -> U+FFFD).
|
||||
return updatedName.toWellFormed();
|
||||
};
|
||||
|
||||
const l33tifyName = (name: string): string => {
|
||||
@@ -191,7 +196,11 @@ const l33tifyName = (name: string): string => {
|
||||
const replacement: string = l33t[char] ?? "";
|
||||
updatedName = updatedName.replaceAll(char, replacement);
|
||||
}
|
||||
return updatedName;
|
||||
// Defensive coding. All operations above preserve well-formed UTF-16, so this is currently redundant. It's a
|
||||
// safeguard to ensure the function never returns ill-formed UTF-16 if future changes introduce code unit–level
|
||||
// manipulation.
|
||||
// This normalization is lossy (lone surrogates -> U+FFFD).
|
||||
return updatedName.toWellFormed();
|
||||
};
|
||||
|
||||
const getMaxRam = (difficulty: number): number => {
|
||||
|
||||
@@ -212,6 +212,12 @@ export class PlayerObject extends Person implements IPlayer {
|
||||
delete player.jobs[loadedCompanyName as CompanyName];
|
||||
}
|
||||
}
|
||||
// A bug created ill-formed UTF-16 darknet hostnames that caused the in-game editor to crash. Player.currentServer
|
||||
// may point to one of these invalid hostnames. This code migrates the invalid hostnames and protects against
|
||||
// similar issues in the future.
|
||||
if (!player.currentServer.isWellFormed()) {
|
||||
player.currentServer = player.currentServer.toWellFormed();
|
||||
}
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,25 @@ export function loadAllServers(saveString: string): void {
|
||||
if (!(server instanceof Server) && !(server instanceof HacknetServer) && !(server instanceof DarknetServer)) {
|
||||
throw new Error(`Server ${serverName} is not an instance of Server or HacknetServer or DarknetServer.`);
|
||||
}
|
||||
// Sanitize hostname
|
||||
// A bug created ill-formed UTF-16 darknet hostnames that caused the in-game editor to crash. This code migrates
|
||||
// those invalid hostnames and protects against similar issues in the future.
|
||||
if (!server.hostname.isWellFormed()) {
|
||||
server.hostname = server.hostname.toWellFormed();
|
||||
for (const script of server.scripts.values()) {
|
||||
script.server = server.hostname;
|
||||
}
|
||||
if (server.savedScripts) {
|
||||
for (const script of server.savedScripts) {
|
||||
script.server = server.hostname;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sanitize hostnames in server.serversOnNetwork
|
||||
for (const [index, value] of server.serversOnNetwork.entries()) {
|
||||
server.serversOnNetwork[index] = value.toWellFormed();
|
||||
}
|
||||
|
||||
AllServers.set(server.hostname, server);
|
||||
AllServers.set(server.ip, server);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,9 @@ Copy your save here if possible
|
||||
\`\`\`
|
||||
`.trim();
|
||||
|
||||
const issueUrl = `${newIssueUrl}?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`;
|
||||
const issueUrl = `${newIssueUrl}?title=${encodeURIComponent(title.toWellFormed())}&body=${encodeURIComponent(
|
||||
body.toWellFormed(),
|
||||
)}`;
|
||||
|
||||
return {
|
||||
metadata,
|
||||
|
||||
@@ -105,3 +105,18 @@ export function getKeyFromReactElements(a: string | React.JSX.Element, b: string
|
||||
const keyOfb = typeof b === "string" ? b : b.key ?? "";
|
||||
return keyOfA + keyOfb;
|
||||
}
|
||||
|
||||
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
||||
|
||||
/**
|
||||
* input.split("") operates on UTF-16 code units and can break surrogate pairs.
|
||||
* For example, 'a🅱️b' is 'a\uD83C\uDD71\uFE0Fb'. A naive reverse yields 'b\uFE0F\uDD71\uD83Ca', which is ill-formed
|
||||
* UTF-16 and not 'b🅱️a' as expected.
|
||||
* Passing such a string to encodeURIComponent may throw a URIError (e.g. in Monaco editor code when processing model
|
||||
* ids).
|
||||
*/
|
||||
export function safelyReverseString(input: string): string {
|
||||
return Array.from(graphemeSegmenter.segment(input), (s) => s.segment)
|
||||
.reverse()
|
||||
.join("");
|
||||
}
|
||||
|
||||
@@ -780,6 +780,7 @@ describe("mutateDarknet and webstorm", () => {
|
||||
function validatePath(hostname: string): void {
|
||||
expectWithMessage(isDirectoryPath(`${hostname}/`), true, `Invalid hostname: ${hostname}`);
|
||||
expectWithMessage(isFilePath(`${hostname}/data.txt`), true, `Invalid hostname: ${hostname}`);
|
||||
expectWithMessage(hostname.isWellFormed(), true, `Malformed hostname: ${hostname}`);
|
||||
}
|
||||
|
||||
describe("Darknet server name generator", () => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as db from "../../../src/db";
|
||||
import * as FileUtils from "../../../src/utils/FileUtils";
|
||||
import type { SaveData } from "../../../src/types";
|
||||
import { calculateExp } from "../../../src/PersonObjects/formulas/skill";
|
||||
import { GetAllServers, GetServer } from "../../../src/Server/AllServers";
|
||||
|
||||
async function loadGameFromSaveData(saveData: SaveData) {
|
||||
// Simulate loading the data in IndexedDB
|
||||
@@ -132,4 +133,25 @@ describe("v3", () => {
|
||||
expect(mockedDownload).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
test("Malformed hostname", async () => {
|
||||
const saveData = new Uint8Array(fs.readFileSync("test/jest/Migration/save-files/malformed-hostname.gz"));
|
||||
await loadGameFromSaveData(saveData);
|
||||
for (const server of GetAllServers(true)) {
|
||||
expect(server.hostname.isWellFormed()).toBe(true);
|
||||
for (const script of server.scripts.values()) {
|
||||
expect(script.server).toStrictEqual(server.hostname);
|
||||
}
|
||||
if (server.savedScripts) {
|
||||
for (const script of server.savedScripts) {
|
||||
expect(script.server).toStrictEqual(server.hostname);
|
||||
}
|
||||
}
|
||||
for (const hostname of server.serversOnNetwork) {
|
||||
expect(hostname.isWellFormed()).toBe(true);
|
||||
expect(GetServer(hostname)).not.toBeNull();
|
||||
}
|
||||
}
|
||||
expect(() => Player.getCurrentServer()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user