Skip to content

fix(upnp): pin LC_CTYPE = C around ASCII tolower() in UPnPBase - #208

Merged
got3nks merged 1 commit into
amule-org:masterfrom
got3nks:fix/upnp-tolower-ascii-scope
Jun 20, 2026
Merged

fix(upnp): pin LC_CTYPE = C around ASCII tolower() in UPnPBase#208
got3nks merged 1 commit into
amule-org:masterfrom
got3nks:fix/upnp-tolower-ascii-scope

Conversation

@got3nks

@got3nks got3nks commented Jun 20, 2026

Copy link
Copy Markdown

Summary

Follow-up to #205. After that PR landed, amuled started picking LC_CTYPE up from the environment instead of staying on the C locale. That's the right behaviour for non-ASCII filenames, but it exposed a latent Turkish-i issue in UPnPBase.cpp: four tolower() call sites that rely on ASCII semantics and silently break under tr_TR.UTF-8 (where tolower('I') returns ı / U+0131 instead of i).

Net effect for Turkish-locale users on amuled with UPnP enabled: auto-portforward silently fails because CUPnPService::Execute rejects every argument's direction string. amule (GUI) was already exposed to the same bug via wxLocale::Init() on Turkish-language installs; this also fixes it there as a side effect.

Sites wrapped

All four use CCtypeAsciiScope (the existing RAII helper that pins LC_CTYPE = "C" for the duration of its scope and restores it on destruction — same pattern as CamuleFileConfig.h from #852 and MaxMindDBDatabase.cpp from the GeoIP flag fix):

  • stdStringIsEqualCI — case-insensitive std::string helper used throughout the file.
  • stdStringStartsWithCI — sibling helper, same shape.
  • CUPnPService::Execute — direct tolower(direction[0]) != 'i' validation against "in"/"out".
  • CUPnPControlPoint::Callback BYEBYE case — device-type URN lowercase before matching UPnP::Device::IGW.

Test plan

  • Configure + build on macOS with -DENABLE_UPNP=YES (amule, amuled, amulecmd, amuleweb all build clean; zero new errors, zero new warnings).
  • CI matrix: Ubuntu Debug+Release, macOS Debug+Release, mingw-w64 Debug+Release, I18n CI.

UPnPBase.cpp has four call sites that rely on tolower() producing
ASCII semantics:

  * stdStringIsEqualCI / stdStringStartsWithCI -- the two
    case-insensitive std::string helpers used throughout the file.
  * CUPnPService::Execute -- direct comparison against 'i' and 'n' to
    validate the UPnP argument direction string ("in" / "out").
  * CUPnPControlPoint::Callback BYEBYE case -- transforms the
    discovered device-type URN to lowercase before matching against
    UPnP::Device::IGW.

Under tr_TR.UTF-8 (and any other Turkish-locale variant), libc
tolower('I') returns U+0131 (dotless i) instead of 'i'. This
silently breaks UPnP direction validation, device-type matching,
namespace lowercase comparisons, and HTTP-header lowercasing --
amuled's UPnP auto-portforward stops working for Turkish-locale
users.

Before amule-project#205 (i18n: fix non-ASCII mangling under default C locale),
amuled stayed on the C locale throughout its lifetime and these
sites were silently safe. The fix in amule-project#205 made amuled pick LC_CTYPE
up from the environment so unicode2char() emits real UTF-8 -- which
exposed the latent Turkish-i issue documented above.

Wrap each tolower() scope in CCtypeAsciiScope -- the project's
existing RAII helper that pins LC_CTYPE = "C" for the duration of
its scope and restores it on destruction. Same pattern already used
in CamuleFileConfig.h (amule-project#852) and MaxMindDBDatabase.cpp.

amule (GUI) was already exposed to this bug via wxLocale's
setlocale(LC_ALL) on Turkish-language installs; the wrap fixes it
there too as a free side effect.
@got3nks
got3nks merged commit 70eb1ef into amule-org:master Jun 20, 2026
10 checks passed
@got3nks
got3nks deleted the fix/upnp-tolower-ascii-scope branch June 20, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant