aboutsummaryrefslogtreecommitdiff
path: root/webAO
diff options
context:
space:
mode:
authorOsmium Sorcerer <os@sof.beauty>2026-03-16 14:35:42 +0000
committerOsmium Sorcerer <os@sof.beauty>2026-06-06 03:06:43 +0000
commit2c08ae7470f7d057b69e9047c34caab1abbe205d (patch)
treef346a668f89f201926e8e4d90317e216c31c7485 /webAO
parentbaa39f317e978d02b7b1c0a5fd8c7c3b3585de69 (diff)
Remove toLowerCase mangling
For whatever reason, WebAO decides to normalize almost every string component in URLs, packets, and INI files to lower case. First, the glaring issue. In the URLs, this handling of paths is utterly broken and corrupts data. By mangling characters, you change the resource identity and break valid URLs. According to section 6.2.2.1 of RFC 3986 (Case Normalization): > When a URI uses components of the generic syntax, the component syntax > equivalence rules always apply; namely, that the scheme and host are > case-insensitive and therefore should be normalized to lowercase. For > example, the URI <HTTP://www.EXAMPLE.com/> is equivalent to > <http://www.example.com/>. The other generic syntax components are > assumed to be case-sensitive unless specifically defined otherwise by > the scheme (see Section 6.2.3) Scheme and host _are_ case-insensitive. Path is _not_, so isn't everything else. Section 6.2.3 doesn't define any normalization for the path component in HTTP schemes. Thus, example.com/item and example.com/Item are two different resources. I can only think of idiotic conventions of a particular poorly designed file system when it comes to this absurdity. There's no reason to drag them around in our developments. For these systems, case doesn't matter anyway, normalization is their job, not server hosts' who end up having to either rewrite every URL request for every asset, or mangle their asset directory and then rewrite almost every INI config (and spam "showname=Name" everywhere because now your character directory has to be "name"). So, instead of using absurd ad-hoc solutions to a broken implementation such as forcing everything to lower case on the server side, this commit attempts to fix the root issue and make URL handling conformant to relevant standards. Similar situation with strings within packets, although not as severe in practice. Case must be preserved, otherwise it's corrupting data for no reason. If a normalization is needed, it should be done at the call site of whatever requires it (like a filtering function), not by the parser. As for the INI, it's opinionated. While the values absolutely must not be normalized, a case can be made for keys and section names: why not allow "Options", "options", or even "oPtiOnS"? It's more convenient, and corresponds to the platform quirk of Windows (which Qt unfortunately inherits in AO2 Client). I don't think there's a good reason to allow such leniency in parsing, and removing superfluous normalization is a better move: less data transformations, less ambiguity, more strictness. In practice, INIs tend to be well-formed, and it's good discipline to write them this way. In several places, the case-folding does make sense: callwords, OOC commands, CSS class names for areas, and character list filters. These will behave weirdly and inconveniently without it. In most places, however, it only causes unnecessary breakage.
Diffstat (limited to 'webAO')
-rw-r--r--webAO/client/handleCharacterInfo.ts16
-rw-r--r--webAO/client/saveChatLogHandle.ts2
-rw-r--r--webAO/dom/renderPlayerList.ts2
-rw-r--r--webAO/dom/updateBackgroundPreview.ts2
-rw-r--r--webAO/dom/updateEvidenceIcon.ts4
-rw-r--r--webAO/packets/handlers/handleLE.ts2
-rw-r--r--webAO/packets/handlers/handleMC.ts2
-rw-r--r--webAO/packets/handlers/handleMS.ts10
-rw-r--r--webAO/packets/handlers/handlePU.ts2
-rw-r--r--webAO/packets/handlers/handlePV.ts12
-rw-r--r--webAO/viewport/utils/handleICSpeaking.ts6
-rw-r--r--webAO/viewport/utils/preloadMessageAssets.ts8
-rw-r--r--webAO/viewport/utils/setSide.ts2
-rw-r--r--webAO/viewport/viewport.ts18
14 files changed, 44 insertions, 44 deletions
diff --git a/webAO/client/handleCharacterInfo.ts b/webAO/client/handleCharacterInfo.ts
index 28992e95..6bec813e 100644
--- a/webAO/client/handleCharacterInfo.ts
+++ b/webAO/client/handleCharacterInfo.ts
@@ -16,7 +16,7 @@ export const setupCharacterBasic = (chargs: string[], charid: number) => {
img.title = chargs[0];
const iconExt = client.charicon_extensions[0] || ".png";
img.src = `${AO_HOST}characters/${encodeURI(
- chargs[0].toLowerCase(),
+ chargs[0],
)}/char_icon${iconExt}`;
const mute_select = <HTMLSelectElement>(
@@ -61,7 +61,7 @@ export const ensureCharIni = async (charid: number): Promise<any> => {
try {
const cinidata = await request(
- `${AO_HOST}characters/${encodeURI(char.name.toLowerCase())}/char.ini`,
+ `${AO_HOST}characters/${encodeURI(char.name)}/char.ini`,
);
cini = iniParse(cinidata);
} catch (err) {
@@ -87,13 +87,13 @@ export const ensureCharIni = async (charid: number): Promise<any> => {
// Replace defaults with actual ini values
char.showname = safeTags(cini.options.showname);
- char.blips = safeTags(cini.options.blips).toLowerCase();
- char.gender = safeTags(cini.options.gender).toLowerCase();
- char.side = safeTags(cini.options.side).toLowerCase();
+ char.blips = safeTags(cini.options.blips);
+ char.gender = safeTags(cini.options.gender);
+ char.side = safeTags(cini.options.side);
char.chat =
cini.options.chat === ""
- ? safeTags(cini.options.category).toLowerCase()
- : safeTags(cini.options.chat).toLowerCase();
+ ? safeTags(cini.options.category)
+ : safeTags(cini.options.chat);
char.icon = img ? img.src : "";
char.inifile = cini;
@@ -119,7 +119,7 @@ export const handleCharacterInfo = async (chargs: string[], charid: number) => {
img.title = chargs[0];
const iconExt = client.charicon_extensions[0] || ".png";
img.src = `${AO_HOST}characters/${encodeURI(
- chargs[0].toLowerCase(),
+ chargs[0],
)}/char_icon${iconExt}`;
// Reset inifile so ensureCharIni will re-fetch
diff --git a/webAO/client/saveChatLogHandle.ts b/webAO/client/saveChatLogHandle.ts
index e083f2f9..5e6b277b 100644
--- a/webAO/client/saveChatLogHandle.ts
+++ b/webAO/client/saveChatLogHandle.ts
@@ -18,7 +18,7 @@ export const saveChatlogHandle = async () => {
const mo = new Intl.DateTimeFormat("en", { month: "short" }).format(d);
const da = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(d);
- const filename = `chatlog-${da}-${mo}-${ye}`.toLowerCase();
+ const filename = `chatlog-${da}-${mo}-${ye}`;
downloadFile(messages.join("\n"), filename);
// Reset Chatbox to Empty
diff --git a/webAO/dom/renderPlayerList.ts b/webAO/dom/renderPlayerList.ts
index 48354ae2..9087d949 100644
--- a/webAO/dom/renderPlayerList.ts
+++ b/webAO/dom/renderPlayerList.ts
@@ -25,7 +25,7 @@ export function renderPlayerList() {
img.style.maxHeight = "60px";
if (player.charName) {
const iconExt = client.charicon_extensions[0] || ".png";
- img.src = `${AO_HOST}characters/${encodeURI(player.charName.toLowerCase())}/char_icon${iconExt}`;
+ img.src = `${AO_HOST}characters/${encodeURI(player.charName)}/char_icon${iconExt}`;
img.alt = player.charName;
img.title = player.charName;
}
diff --git a/webAO/dom/updateBackgroundPreview.ts b/webAO/dom/updateBackgroundPreview.ts
index 57073b49..bc2803b0 100644
--- a/webAO/dom/updateBackgroundPreview.ts
+++ b/webAO/dom/updateBackgroundPreview.ts
@@ -37,7 +37,7 @@ export function updateBackgroundPreview() {
}
tryBackgroundUrls(
`${AO_HOST}background/${encodeURI(
- background_select.value.toLowerCase(),
+ background_select.value,
)}/defenseempty`,
).then((resp) => {
background_preview.src = resp;
diff --git a/webAO/dom/updateEvidenceIcon.ts b/webAO/dom/updateEvidenceIcon.ts
index dbda07cc..34019f74 100644
--- a/webAO/dom/updateEvidenceIcon.ts
+++ b/webAO/dom/updateEvidenceIcon.ts
@@ -16,12 +16,12 @@ export function updateEvidenceIcon() {
if (evidence_select.selectedIndex === 0) {
evidence_filename.style.display = "initial";
evidence_iconbox.src = `${AO_HOST}evidence/${encodeURI(
- evidence_filename.value.toLowerCase(),
+ evidence_filename.value,
)}`;
} else {
evidence_filename.style.display = "none";
evidence_iconbox.src = `${AO_HOST}evidence/${encodeURI(
- evidence_select.value.toLowerCase(),
+ evidence_select.value,
)}`;
}
}
diff --git a/webAO/packets/handlers/handleLE.ts b/webAO/packets/handlers/handleLE.ts
index fe8691c8..ae1bb02c 100644
--- a/webAO/packets/handlers/handleLE.ts
+++ b/webAO/packets/handlers/handleLE.ts
@@ -17,7 +17,7 @@ export const handleLE = (args: string[]) => {
name: prepChat(arg[0]),
desc: prepChat(arg[1]),
filename: arg[2],
- icon: `${AO_HOST}evidence/${encodeURI(arg[2].toLowerCase())}`,
+ icon: `${AO_HOST}evidence/${encodeURI(arg[2])}`,
};
}
diff --git a/webAO/packets/handlers/handleMC.ts b/webAO/packets/handlers/handleMC.ts
index 187a9cd3..aaa30a1b 100644
--- a/webAO/packets/handlers/handleMC.ts
+++ b/webAO/packets/handlers/handleMC.ts
@@ -21,7 +21,7 @@ export const handleMC = (args: string[]) => {
if (track.startsWith("http")) {
music.src = track;
} else {
- music.src = `${AO_HOST}sounds/music/${encodeURI(track.toLowerCase())}`;
+ music.src = `${AO_HOST}sounds/music/${encodeURI(track)}`;
}
music.loop = looping;
music.play().catch(() => {});
diff --git a/webAO/packets/handlers/handleMS.ts b/webAO/packets/handlers/handleMS.ts
index 9b46bc04..a2f7f33f 100644
--- a/webAO/packets/handlers/handleMS.ts
+++ b/webAO/packets/handlers/handleMS.ts
@@ -58,15 +58,15 @@ export const handleMS = (args: string[]) => {
if (char_muted === false) {
let chatmsg = {
- deskmod: Number(safeTags(args[1]).toLowerCase()),
- preanim: safeTags(args[2]).toLowerCase(), // get preanim
+ deskmod: Number(safeTags(args[1])),
+ preanim: safeTags(args[2]), // get preanim
nameplate: msg_nameplate,
chatbox: char_chatbox,
name: char_name,
- sprite: safeTags(args[4]).toLowerCase(),
+ sprite: safeTags(args[4]),
content: prepChat(args[5]), // Escape HTML tags
- side: args[6].toLowerCase(),
- sound: safeTags(args[7]).toLowerCase(),
+ side: args[6],
+ sound: safeTags(args[7]),
blips: safeTags(msg_blips),
type: Number(args[8]),
charid: char_id,
diff --git a/webAO/packets/handlers/handlePU.ts b/webAO/packets/handlers/handlePU.ts
index 3b70ad3a..d6febd03 100644
--- a/webAO/packets/handlers/handlePU.ts
+++ b/webAO/packets/handlers/handlePU.ts
@@ -21,7 +21,7 @@ export const handlePU = (args: string[]) => {
case 1:
player.charName = data;
const charId = client.chars.findIndex(
- (c: any) => c && c.name.toLowerCase() === data.toLowerCase()
+ (c: any) => c && c.name === data
);
if (charId >= 0) {
player.charId = charId;
diff --git a/webAO/packets/handlers/handlePV.ts b/webAO/packets/handlers/handlePV.ts
index 2c1cf750..ba48f36f 100644
--- a/webAO/packets/handlers/handlePV.ts
+++ b/webAO/packets/handlers/handlePV.ts
@@ -45,7 +45,7 @@ export const handlePV = async (args: string[]) => {
class="emote_button">No emotes available</span>`;
} else {
// Probe extensions once using button1_off, then reuse for all emotes
- const charPath = `${AO_HOST}characters/${encodeURI(me.name.toLowerCase())}/emotions/`;
+ const charPath = `${AO_HOST}characters/${encodeURI(me.name)}/emotions/`;
let emoteExtension = client.emotions_extensions[0];
for (const extension of client.emotions_extensions) {
if (await fileExists(`${charPath}button1_off${extension}`)) {
@@ -70,12 +70,12 @@ export const handlePV = async (args: string[]) => {
const url = `${charPath}button${i}_off${emoteExtension}`;
emotes[i] = {
- desc: emoteinfo[0].toLowerCase(),
- preanim: emoteinfo[1].toLowerCase(),
- emote: emoteinfo[2].toLowerCase(),
+ desc: emoteinfo[0],
+ preanim: emoteinfo[1],
+ emote: emoteinfo[2],
zoom: Number(emoteinfo[3]) || 0,
deskmod: Number(emoteinfo[4]) || 1,
- sfx: esfx.toLowerCase(),
+ sfx: esfx,
sfxdelay: esfxd,
frame_screenshake: "",
frame_realization: "",
@@ -94,7 +94,7 @@ export const handlePV = async (args: string[]) => {
if (
await fileExists(
- `${AO_HOST}characters/${encodeURI(me.name.toLowerCase())}/custom.gif`,
+ `${AO_HOST}characters/${encodeURI(me.name)}/custom.gif`,
)
) {
document.getElementById("button_4")!.style.display = "";
diff --git a/webAO/viewport/utils/handleICSpeaking.ts b/webAO/viewport/utils/handleICSpeaking.ts
index f9b9973c..84946d2d 100644
--- a/webAO/viewport/utils/handleICSpeaking.ts
+++ b/webAO/viewport/utils/handleICSpeaking.ts
@@ -166,7 +166,7 @@ export const handle_ic_speaking = async (playerChatMsg: ChatMsg) => {
chatContainerBox.style.opacity = "0";
if (client.viewport.getChatmsg().objection === 4) {
shoutSprite.src = `${AO_HOST}characters/${encodeURI(
- client.viewport.getChatmsg().name!.toLowerCase(),
+ client.viewport.getChatmsg().name!,
)}/custom.gif`;
} else {
shoutSprite.src = client.resources[shout].src;
@@ -290,7 +290,7 @@ export const handle_ic_speaking = async (playerChatMsg: ChatMsg) => {
client.viewport.blipChannels.forEach(
(channel: HTMLAudioElement) =>
(channel.src = `${AO_HOST}sounds/blips/${encodeURI(
- client.viewport.getChatmsg().blips.toLowerCase(),
+ client.viewport.getChatmsg().blips,
)}.opus`),
);
@@ -306,7 +306,7 @@ export const handle_ic_speaking = async (playerChatMsg: ChatMsg) => {
// apply effects
fg.style.animation = "";
- const effectName = client.viewport.getChatmsg().effects[0].toLowerCase();
+ const effectName = client.viewport.getChatmsg().effects[0];
const badEffects = ["", "-", "none"];
if (effectName.startsWith("rain")) {
(<HTMLLinkElement>document.getElementById("effect_css")).href =
diff --git a/webAO/viewport/utils/preloadMessageAssets.ts b/webAO/viewport/utils/preloadMessageAssets.ts
index 0a24e84a..f3924cdb 100644
--- a/webAO/viewport/utils/preloadMessageAssets.ts
+++ b/webAO/viewport/utils/preloadMessageAssets.ts
@@ -61,8 +61,8 @@ export default async function preloadMessageAssets(
AO_HOST: string,
emoteExtensions: string[],
): Promise<PreloadedAssets> {
- const charName = chatmsg.name!.toLowerCase();
- const charEmote = chatmsg.sprite!.toLowerCase();
+ const charName = chatmsg.name!;
+ const charEmote = chatmsg.sprite!;
const doPreload = async (): Promise<PreloadedAssets> => {
// Build candidate URL lists for each emote
@@ -76,12 +76,12 @@ export default async function preloadMessageAssets(
chatmsg.preanim !== "";
const preanimUrls = hasPreanim
- ? buildEmoteUrls(AO_HOST, emoteExtensions, charName, chatmsg.preanim!.toLowerCase(), "")
+ ? buildEmoteUrls(AO_HOST, emoteExtensions, charName, chatmsg.preanim!, "")
: null;
const hasPair = !!chatmsg.other_name;
const pairIdleUrls = hasPair
- ? buildEmoteUrls(AO_HOST, emoteExtensions, chatmsg.other_name!.toLowerCase(), chatmsg.other_emote!.toLowerCase(), "(a)")
+ ? buildEmoteUrls(AO_HOST, emoteExtensions, chatmsg.other_name!, chatmsg.other_emote!, "(a)")
: null;
// Shout SFX per-character path
diff --git a/webAO/viewport/utils/setSide.ts b/webAO/viewport/utils/setSide.ts
index a9dbfdb3..97d76f0f 100644
--- a/webAO/viewport/utils/setSide.ts
+++ b/webAO/viewport/utils/setSide.ts
@@ -9,7 +9,7 @@ export async function setBackgroundImage(elementid: string, bgname: string, bgpa
let url;
let success = false;
for (const extension of client.background_extensions) {
- url = `${AO_HOST}background/${encodeURI(bgname.toLowerCase())}/${bgpart}${extension}`;
+ url = `${AO_HOST}background/${encodeURI(bgname)}/${bgpart}${extension}`;
const exists = await fileExists(url);
if (exists) {
diff --git a/webAO/viewport/viewport.ts b/webAO/viewport/viewport.ts
index 45cfc964..70ec5c55 100644
--- a/webAO/viewport/viewport.ts
+++ b/webAO/viewport/viewport.ts
@@ -52,7 +52,7 @@ const viewport = (): Viewport => {
backgroundName = value;
};
const getBackgroundFolder = () =>
- `${AO_HOST}background/${encodeURI(backgroundName.toLowerCase())}/`;
+ `${AO_HOST}background/${encodeURI(backgroundName)}/`;
const getTextNow = () => {
return textnow;
};
@@ -147,8 +147,8 @@ const viewport = (): Viewport => {
const chatBox = document.getElementById("client_chat");
const waitingBox = document.getElementById("client_chatwaiting");
const chatBoxInner = document.getElementById("client_inner_chat");
- const charName = chatmsg.name.toLowerCase();
- const charEmote = chatmsg.sprite.toLowerCase();
+ const charName = chatmsg.name;
+ const charEmote = chatmsg.sprite;
if (chatmsg.content.charAt(textnow.length) !== " ") {
blipChannels[currentBlipChannel].play().catch(() => {});
@@ -319,11 +319,11 @@ const viewport = (): Viewport => {
);
}
- const charName = chatmsg.name.toLowerCase();
- const charEmote = chatmsg.sprite.toLowerCase();
+ const charName = chatmsg.name;
+ const charEmote = chatmsg.sprite;
- const pairName = chatmsg.other_name.toLowerCase();
- const pairEmote = chatmsg.other_emote.toLowerCase();
+ const pairName = chatmsg.other_name;
+ const pairEmote = chatmsg.other_emote;
// TODO: preanims sometimes play when they're not supposed to
const isShoutOver = tickTimer >= shoutTimer;
@@ -353,7 +353,7 @@ const viewport = (): Viewport => {
if (chatmsg.preloadedAssets) {
setEmoteFromUrl(chatmsg.preloadedAssets.preanimUrl, false, chatmsg.side);
} else {
- const preanim = chatmsg.preanim.toLowerCase();
+ const preanim = chatmsg.preanim;
setEmote(AO_HOST, client, charName, preanim, "", false, chatmsg.side);
}
}
@@ -489,7 +489,7 @@ const viewport = (): Viewport => {
(chatmsg.type == 1 || chatmsg.type == 2 || chatmsg.type == 6)
) {
const sfxUrl = chatmsg.preloadedAssets?.emoteSfxUrl
- ?? `${AO_HOST}sounds/general/${encodeURI(chatmsg.sound.toLowerCase())}.opus`;
+ ?? `${AO_HOST}sounds/general/${encodeURI(chatmsg.sound)}.opus`;
playSFX(sfxUrl, chatmsg.looping_sfx);
}
}