diff options
| author | Osmium Sorcerer <os@sof.beauty> | 2026-03-16 14:35:42 +0000 |
|---|---|---|
| committer | Osmium Sorcerer <os@sof.beauty> | 2026-04-18 16:52:22 +0000 |
| commit | 8538104fd5573ba5eeade30ee4a20893224960f9 (patch) | |
| tree | 61133e858ed428ce7963d5a979afe62c907e5216 | |
| parent | 2ef41402209b82279656ae4b1affe6484be1ed77 (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.
| -rw-r--r-- | webAO/client/handleCharacterInfo.ts | 16 | ||||
| -rw-r--r-- | webAO/client/saveChatLogHandle.ts | 2 | ||||
| -rw-r--r-- | webAO/dom/renderPlayerList.ts | 2 | ||||
| -rw-r--r-- | webAO/dom/updateBackgroundPreview.ts | 2 | ||||
| -rw-r--r-- | webAO/dom/updateEvidenceIcon.ts | 4 | ||||
| -rw-r--r-- | webAO/packets/handlers/handleLE.ts | 2 | ||||
| -rw-r--r-- | webAO/packets/handlers/handleMC.ts | 2 | ||||
| -rw-r--r-- | webAO/packets/handlers/handleMS.ts | 10 | ||||
| -rw-r--r-- | webAO/packets/handlers/handlePU.ts | 2 | ||||
| -rw-r--r-- | webAO/packets/handlers/handlePV.ts | 12 | ||||
| -rw-r--r-- | webAO/viewport/utils/handleICSpeaking.ts | 6 | ||||
| -rw-r--r-- | webAO/viewport/utils/preloadMessageAssets.ts | 8 | ||||
| -rw-r--r-- | webAO/viewport/utils/setSide.ts | 2 | ||||
| -rw-r--r-- | webAO/viewport/viewport.ts | 18 |
14 files changed, 44 insertions, 44 deletions
diff --git a/webAO/client/handleCharacterInfo.ts b/webAO/client/handleCharacterInfo.ts index 28992e9..6bec813 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 e083f2f..5e6b277 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 48354ae..9087d94 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 57073b4..bc2803b 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 dbda07c..34019f7 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 fe8691c..ae1bb02 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 187a9cd..aaa30a1 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 9b46bc0..a2f7f33 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 3b70ad3..d6febd0 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 0865fe8..17e9fc1 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}`)) { @@ -71,12 +71,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: "", @@ -95,7 +95,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 a612e77..b48a445 100644 --- a/webAO/viewport/utils/handleICSpeaking.ts +++ b/webAO/viewport/utils/handleICSpeaking.ts @@ -148,7 +148,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; @@ -272,7 +272,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`), ); @@ -288,7 +288,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 0a24e84..f3924cd 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 564d477..c34d162 100644 --- a/webAO/viewport/utils/setSide.ts +++ b/webAO/viewport/utils/setSide.ts @@ -10,7 +10,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 45cfc96..70ec5c5 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); } } |
