http: rewrite CHTTPDownloadThread on top of wxWebRequest - #462
Merged
Conversation
Fixes the intermittent startup SIGSEGV tracked in upstream PR amule-project#455. The crash happens on Linux when a startup HTTP download redirects to https://, which wxHTTP cannot speak: wxHTTP::Destroy() schedules the current handler for deferred deletion, the redirect handler throws, and before the deferred delete runs an epoll event fires on the still-registered fd — invoking a method on a socket mid-teardown. Reproducible on stock origin/master (~1-in-27 runs of amuled in a tight startup loop); the crash-hunt.sh script from PR amule-project#455 passes >115 iterations clean on Linux/ARM64 with this rewrite. The fault is inside libwx_baseu_net so it cannot be fixed inside aMule's code paths — the only reliable remedy is to avoid the wxSocket-based HTTP path entirely. The fix is to stop doing HTTP on a worker thread with wxSocket under the hood. CHTTPDownloadThread now: * Inherits wxEvtHandler instead of CMuleThread. No worker thread, no wxSocket, no wxEpollDispatcher on the download path. * Owns a wxWebRequest from wxWebSession::GetDefault(), kicks it off from the constructor on the main thread, binds wxEVT_WEBREQUEST_STATE on the same handler, and lets state transitions drive everything: Active → progress event to the optional dialog, Completed → rename wx's storage temp file into the caller-supplied path, Failed / Cancelled → clean up, Unauthorized → treat as failure. On every terminal state the object posts wxEVT_CORE_FINISHED_HTTP_DOWNLOAD to the existing dispatcher (CamuleApp::OnFinishedHTTPDownload, unchanged) and CallAfter()s its own destruction, so wxWebRequest's internal socket is already torn down by the time the handler dies. * Keeps Create() / Run() / Stop() / OnExit() / Entry() as stubs, so the six call sites (new + Create + Run) compile unchanged. When wx 3.3's wxWebRequestSync becomes a reasonable floor on distro defaults those stubs can be made real and the event-driven plumbing collapsed into a synchronous call on a worker thread with no wxSocket anywhere. Reentrancy: no nested event loops anywhere on the fix path. No ShowModal (the existing progress dialog has always been modeless). No blocking wait on the request. Cancellation via the dialog's Cancel button or the app-wide StopAll() is fire-and-forget — it calls Cancel() on the request, returns immediately, and State_Cancelled arrives later on the main loop. Avoids the class of bug that would come from a "sync wrapper = nested wxEventLoop" trick on wx 3.2. Redirects (including HTTP→HTTPS) are handled by wxWebRequest transparently, which removes the ~110-line recursive GetInputStream redirect handler — the actual seat of the upstream race. Proxy: a static ApplyProxyToDefaultSession() helper maps the existing CProxyData prefs onto wxWebSession::SetProxy, applied from the ctor. wxWebProxy / SetProxy are wx 3.3+ only, so the body is guarded with wxCHECK_VERSION(3,3,0); on wx 3.2 the libcurl backend still honours http_proxy / https_proxy / all_proxy env vars, so env-driven proxy keeps working. The legacy wxHTTP path never honoured aMule's Proxy prefs either (it only called SetProxyMode(bool) with no URL ever registered via wxURL::SetProxy / SetDefaultProxy anywhere in the codebase), so this is not a regression. SOCKS is intentionally skipped even on 3.3+ — wxWebProxy is HTTP-only. A libcurl-direct backend would be needed if SOCKS support matters, out of scope here. macOS App Transport Security opt-out ------------------------------------ wxWebRequest on macOS is backed by NSURLSession, which enforces ATS and blocks plaintext http:// by default. The legacy wxHTTP/wxSocket path bypassed ATS because it used raw BSD sockets; the rewrite inherits NSURLSession's policy instead. Without the opt-out every startup download to an http:// URL fails at the OS level — reproducible on the first server.met update: HTTPDownload.cpp: HTTP download failed for http://upd.emule-security.org/server.met: The resource could not be loaded because the App Transport Security policy requires the use of a secure connection. Inject NSAppTransportSecurity:NSAllowsArbitraryLoads=true via plutil as a POST_BUILD step on the `amule` target in src/CMakeLists.txt. Matches the legacy path's behaviour, not a new policy choice. macOS exit-time crash work-around --------------------------------- wx 3.3.2 has a bug in wxWebSessionURLSession::~wxWebSessionURLSession: it releases the NSURLSession and the delegate separately without first calling -invalidateAndCancel. NSURLSession retains its delegate strongly, so the session's dealloc already drops the delegate ref — wx's subsequent release hits a freed object and the process aborts with "pointer being freed was not allocated". This fires in wx module cleanup / atexit / __cxa_finalize on any Mac build after any HTTP download (version check, server.met, …). By the end of CamuleApp::OnExit we have saved state, joined threads, and flushed logs — nothing aMule-owned remains to clean up. std::_Exit bypasses atexit and static destructors, so the buggy wx dtor never runs and the process terminates cleanly. Linux / Windows continue through wx's normal cleanup; this macOS-only block can be dropped once the upstream wx fix lands in a release we depend on. Windows portable install: CA bundle for libcurl HTTPS ----------------------------------------------------- wxWebRequest on MSYS2 (MINGW64 / CLANGARM64) is backed by libcurl built with OpenSSL. MSYS2 libcurl is compiled with an absolute --with-ca-bundle= path (e.g. /clangarm64/etc/ssl/certs/ca-bundle.crt) that only exists on the developer's machine. On an end-user box the CA bundle isn't there and every HTTPS request (including the HTTP→ HTTPS redirect that hits the default startup URLs) fails with "libcurl error 77: Problem with the SSL CA cert". Two-part fix so portable zips work everywhere: * src/CMakeLists.txt (WIN32 AND MINGW install block): ship the MSYS2 CA bundle alongside the bundled DLLs as bin/ca-bundle.crt. * src/amule.cpp (CamuleApp::OnInit, __WINDOWS__-guarded): if CURL_CA_BUNDLE is not already set in the environment, point it at the bundled file resolved from wxStandardPaths::GetExecutablePath. Users running from a portable directory now get working HTTPS without any shell env setup; users who want to point at their own bundle still can by setting CURL_CA_BUNDLE before launch. Dialog / thread lifetime fix ---------------------------- CHTTPDownloadDialog holds a raw pointer (m_owner) to the owning CHTTPDownloadThread. On successful download the ordered flow is: FinishAndDestroy posts wxEVT_HTTP_SHUTDOWN to the dialog, posts wxEVT_CORE_FINISHED_HTTP_DOWNLOAD to the app dispatcher, then CallAfter()s `delete this`. Main loop: dialog OnShutdown → Destroy() (queues the dialog in wxPendingDelete), then app dispatcher runs, then CallAfter runs and deletes the thread. Later, idle → DeletePendingObjects runs the dialog dtor — which used to dereference the now-freed m_owner via StopDownload → ~vtable lookup → crash. Null the dialog's m_owner inside OnShutdown, so its dtor (which runs later from wxPendingDelete) no longer touches the already-deleted thread. Equivalent cleanup on the reverse direction (dialog closed before download completes) was already handled via DetachCompanion + Stop in StopDownload. Progress bar safety (wxGTK) --------------------------- wxWebRequest::GetBytesExpectedToReceive() returns wxInvalidOffset (-1) before Content-Length is known (or indefinitely if the server never sends it). Forwarding -1 into CHTTPDownloadDialog::UpdateGauge ended up calling wxGauge::SetRange(-1), which trips an assertion on the next repaint on wxGTK (./src/gtk/gauge.cpp:90, reproducible on IP filter updates on Ubuntu). Clamp the expected size at the event source, and make UpdateGauge only touch the gauge when the total is known and current <= total, so out-of-range values can never reach the widget. Scope ----- wxWebRequest is in wxBase_net since wx 3.2.0 (our floor since PR amule-project#459), so no new build dependency beyond install-time packaging of an MSYS2 CA bundle on Windows. * src/HTTPDownload.{h,cpp} — class becomes wxEvtHandler, legacy wxThread-style method stubs with a wx 3.3 / wxWebRequestSync TODO, DetachCompanion() on the owning side + m_owner nulling on the dialog side to make the handshake safe from both directions, UpdateGauge clamped. * src/amule.cpp — macOS-only std::_Exit at end of OnExit; Windows-only CURL_CA_BUNDLE fallback to the bundled ca-bundle.crt. * src/CMakeLists.txt — macOS-only POST_BUILD plutil to inject ATS opt-out into the CMake-generated Info.plist; Windows-only install of the MSYS2 CA bundle alongside the bundled DLLs. * Six call sites (version check, server.met, auto-update, IP filter, nodes.dat, GeoIP) compile unchanged.
This was referenced Apr 23, 2026
got3nks
added a commit
to got3nks/amule
that referenced
this pull request
Apr 29, 2026
aMule's CMakeLists.txt now hard-requires wxWebRequest (via the HTTP download path that landed in PR amule-project#462). On Linux, the backend is libcurl; without --with-libcurl at wx configure time, wxUSE_WEBREQUEST ends up 0 and the aMule cmake step fails: wxWidgets was found but wxUSE_WEBREQUEST is 0 in this build. Add libcurl4-openssl-dev to the apt install + --with-libcurl to the wx configure flags.
got3nks
added a commit
to got3nks/amule
that referenced
this pull request
May 3, 2026
… Releases API `amule.cpp:636` was hitting `http://amule.sourceforge.net/lastversion`, a plain-text `MAJOR.MINOR.UPDATE` file unmaintained since the project moved to GitHub years ago. Repoint the request at `https://api.github.com/repos/amule-project/amule/releases/latest`, which returns JSON describing the most recent non-prerelease, non-draft Release. Pairs with the release.yml flow added in amule-project#520: once a stable tag is published on GitHub, every aMule installation with version-check enabled picks it up automatically on next startup, with no maintainer step beyond un-drafting the Release. `/releases/latest` excludes pre-releases by design, so users on 2.3.3 stable won't be prompted to upgrade when we tag `3.0.0-beta` / `3.0.0-rc1` — only when 3.0.0 stable is published. Parser changes in `CheckNewVersion()`: - Concatenate all lines of the downloaded file before regex-matching (the JSON body is pretty-printed across many lines). - Extract `tag_name` via `wxRegEx` — simpler than dragging in a full JSON parser for one well-known field. - Strip optional `v` prefix and any pre-release / build-metadata suffix (`-beta`, `-rc1`, `+build42`) before the integer comparison. - Treat tags with fewer than three components (e.g. `3.1`) as missing-field-= 0 rather than erroring out. - Clean up the temp file in early-error paths too (the original only removed it on the success path). HTTPS works without transport changes — wxWebRequest support landed in amule-project#462. The `s_NewVersionCheck` pref still controls whether the request fires at all.
mrjimenez
pushed a commit
that referenced
this pull request
May 3, 2026
… Releases API `amule.cpp:636` was hitting `http://amule.sourceforge.net/lastversion`, a plain-text `MAJOR.MINOR.UPDATE` file unmaintained since the project moved to GitHub years ago. Repoint the request at `https://api.github.com/repos/amule-project/amule/releases/latest`, which returns JSON describing the most recent non-prerelease, non-draft Release. Pairs with the release.yml flow added in #520: once a stable tag is published on GitHub, every aMule installation with version-check enabled picks it up automatically on next startup, with no maintainer step beyond un-drafting the Release. `/releases/latest` excludes pre-releases by design, so users on 2.3.3 stable won't be prompted to upgrade when we tag `3.0.0-beta` / `3.0.0-rc1` — only when 3.0.0 stable is published. Parser changes in `CheckNewVersion()`: - Concatenate all lines of the downloaded file before regex-matching (the JSON body is pretty-printed across many lines). - Extract `tag_name` via `wxRegEx` — simpler than dragging in a full JSON parser for one well-known field. - Strip optional `v` prefix and any pre-release / build-metadata suffix (`-beta`, `-rc1`, `+build42`) before the integer comparison. - Treat tags with fewer than three components (e.g. `3.1`) as missing-field-= 0 rather than erroring out. - Clean up the temp file in early-error paths too (the original only removed it on the success path). HTTPS works without transport changes — wxWebRequest support landed in #462. The `s_NewVersionCheck` pref still controls whether the request fires at all.
ngosang
pushed a commit
to ngosang/amule
that referenced
this pull request
Jul 13, 2026
… parity (amule-project#434) (amule-project#462) aMule already publishes notes to Kad (STORENOTES) and answers other clients' note lookups, but nothing triggered a retrieval and the results were never surfaced. This wires the read side end to end and fixes two rating-encoding divergences from eMule 0.70b. Scope is the download side; shared-file parity is a planned follow-up. Core: - CKnownFile::RequestKadNoteSearch() fires an on-demand NOTES lookup, guarded to files in the shared list / download queue. A per-file running flag is set on start and cleared in ~CSearch; both edges, and each note arrival, call MarkECChanged() so the partfile is re-emitted in the next incremental EC update - this is how amulegui / amuleapi learn the lookup started, stream in results live, and see it finish (GET_UPDATE otherwise skips an unchanged partfile). When the lookup can't start, the specific reason (Kad down, a search already using this hash, file not shared/queued, ...) is logged at notice level so it is visible in release builds. - Kad search-result rating: decode raw 0-5 for Kad keyword hits instead of the ed2k server-packed (x&0xF)/3 formula. (Kad keyword results carry no rating tag in practice - ratings live in NOTES - but the decode is now correct if one is present. ed2k results are unchanged.) - ed2k client-write: send the rating packed (x51) to a peer client, raw to a server, matching eMule (remote clients previously decoded aMule's as 0). GUI (monolithic + amulegui): - "Get from Kad" button in the download comments dialog; retrieved notes merge into the existing comments list. A timer auto-refreshes while the lookup runs and stops when the daemon reports it finished (60s safety cap). "Show all comments" is enabled when Kad is connected (not only when source comments already exist), so the button is reachable for a file with no comments yet. - amulegui triggers over EC (EC_OP_SHARED_FILE_SEARCH_KAD_NOTES); notes ride the existing partfile-comments channel; the running flag is surfaced via a new EC_TAG_PARTFILE_KAD_COMMENT_SEARCHING so the remote dialog is stateful. REST (amuleapi): - POST /downloads/{hash}/comments triggers the lookup (202 Accepted). - kad_search_running exposed on the download object (list, detail, GET .../comments) and on the download_added/_updated SSE event, so clients can watch the start -> finish edge. - New comments_updated SSE event carries a download's full comment list (retrieved Kad notes + ed2k source comments) whenever it changes, so web clients get live comments without polling. - curl tests (04, 22) + REFERENCE.md / EVENTS.md updated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes upstream PR #455's intermittent startup
SIGSEGVon Linux by removing the entirewxHTTP/wxSocket-based HTTP download path and replacing it with an event-drivenwxWebRequest-based implementation on the main thread. No worker thread, nowxEpollDispatcheron the download path, no recursive redirect handler, HTTPS transparent, no nested event loops — therefore no reentrancy surface.Stock
origin/masterreproduces PR #455's crash at ~1-in-27 iterations ofamuledin a tight startup loop. This branch cleared 115+ iterations clean on Linux/ARM64 without a single SIGSEGV.Platform-specific follow-ups included in the same PR because they surfaced the moment the rewrite ran on each platform:
Info.plistopt-out of App Transport Security, so plaintexthttp://downloads still work (wxWebRequest's macOS backend isNSURLSession, which enforces ATS).std::_Exit(0)at the end ofCamuleApp::OnExitto side-step a wx 3.3.2 upstream bug in~wxWebSessionURLSessionthat otherwise crashes the process at teardown..exe;CamuleApp::OnInitpointsCURL_CA_BUNDLEat it on startup. Without this, libcurl on MSYS2 builds fails every HTTPS request with error 77 on end-user machines.wxWebRequest::GetBytesExpectedToReceive()at event source and in the dialog, so a-1(unknown Content-Length) never tripswxGauge::SetRange(-1)and the ensuing GTK assertion.wx 3.2.0is the floor (merged via #459), sowxWebRequestis always available inwxBase_net. No new build dependency.Why the upstream crash happens
CHTTPDownloadThread::GetInputStreamhandles 301/302 redirects by recursively reopening with a freshwxHTTP, tearing down the previous one withDestroy(). Since SourceForge and several other defaults now 301 tohttps://, andwxHTTPdoes not speak HTTPS, the recursion throws. Meanwhile the socketfdof the previouswxHTTPis still registered withwxEpollDispatcher, itswxSocketImplstill onwxPendingDelete. An epoll event fires on the fd betweenDestroy()and the pending-delete run → handler invoked on a socket mid-teardown → segfault.The fault is inside
libwx_baseu_net— not fixable from aMule code paths alone. Avoiding the code path is the only reliable remedy.Why
wxWebRequestrather than libcurl-direct (PR #455's approach)PR #455's libcurl backend is a good fix. Three reasons we reached for
wxWebRequestinstead:wxWebRequestships insidewxBase_netsince wx 3.2.0 (distro minimum in cmake: bump MIN_WX_VERSION to 3.2.0 and retire legacy wx.cmake #459). Adds zero lines of CMake detection.NSURLSessionon macOS. Each uses the OS-native HTTP stack rather than a common userland library.wxWebRequestCURLwraps libcurl, so Linux users get exactly the same HTTP engine PR http: fix intermittent startup SIGSEGV (and broken HTTPS) by using libcurl #455 reached for — just through a portable wx wrapper.Why not a synchronous wrapper (nested
wxEventLoop)wxWebRequestin wx 3.2 is async-only;wxWebRequestSynclands in 3.3. A "sync wrapper" done by running a nestedwxEventLoopwhile waiting forwxEVT_WEBREQUEST_STATEwould reintroduce a classic reentrancy surface (modal dialogs running during the wait, menu dispatch during the wait, paint events mid-wait). We explicitly avoid this.Instead, the class is event-driven on the main thread, and the 6 call sites of
CHTTPDownloadThreadalready used an async-completion contract viawxEVT_CORE_FINISHED_HTTP_DOWNLOAD+CamuleApp::OnFinishedHTTPDownload. We preserve that dispatcher verbatim, so nothing changes for callers.Once wx 3.3 /
wxWebRequestSyncis a reasonable distro floor, the entire class can collapse into a synchronous call on a worker thread with nowxSocketanywhere, and the current event-driven plumbing becomes a historical footnote.Create() / Run() / Stop() / OnExit() / Entry()are kept as stubs so the 6 call sites already pre-declare the eventual shape.Reentrancy proof
ProcessEvents, nowxYield.ShowModal— the progress dialog isShow(true)(modeless) as it always has been.Start()returns immediately; state transitions arrive asynchronously.StopAll) callsrequest.Cancel()fire-and-forget → returns →State_Cancelledevent arrives later on the main loop.OnFinishedHTTPDownloaddispatcher unchanged —ServerList::AutoDownloadFinished→AutoUpdateretry chain acrossaddresses.datURLs continues to work one-iteration-per-main-loop-turn.this->Destroy()(viaCallAfter) runs only afterState_Completed / Failed / Cancelled, so wxWebRequest's internal socket is already torn down by the time our handler is deleted.What changed
src/HTTPDownload.{h,cpp}wxEvtHandler; ~110-line recursiveGetInputStreamredirect handler deleted;Entry()/ worker-thread loop deleted;OnStateEventhandles Active / Completed / Failed / Cancelled / Unauthorized;FinishAndDestroycentralises terminal-state cleanup (post shutdown to dialog, post finished to theApp, erase registry,CallAfter(delete this)); dialogUpdateGaugemade defensive against-1totals.src/CMakeLists.txt(APPLE branch)POST_BUILDplutil -replace NSAppTransportSecurityto injectNSAllowsArbitraryLoads=trueinto the CMake-generatedInfo.plist.src/CMakeLists.txt(WIN32+MINGW branch)ca-bundle.crtnext to the bundled DLLs so libcurl HTTPS works from a portable install — builds on top of the existingfile(GET_RUNTIME_DEPENDENCIES)DLL-bundling block.src/amule.cpp(APPLE guard)std::_Exit(0)at the end ofCamuleApp::OnExitto bypass atexit (wx 3.3.2 macOS dtor bug — see below).src/amule.cpp(Windows guard)CamuleApp::OnInitpointsCURL_CA_BUNDLEat the bundledca-bundle.crtresolved fromwxStandardPaths::GetExecutablePathwhen the user has not set one explicitly.Net: ~50 LOC added, ~260 LOC removed in
HTTPDownload.cpp, one 8-lineInfo.plistinjection, ~15 lines macOS exit guard + Windows CA-bundle hook, ~20 lines CMake install for the CA bundle. No CMake build-flag changes beyond the post-build plist injection. No new dep.Proxy
HTTP proxy support via
wxWebProxy::FromURL(...)on wx 3.3+, guarded bywxCHECK_VERSION(3,3,0)sincewxWebProxy/SetProxydo not exist in 3.2. On wx 3.2 the libcurl backend still honours the standardhttp_proxy/https_proxy/all_proxyenv vars automatically.Not a regression vs. master: verified by reading wx 3.2.4 source —
wxHTTP::SetProxyMode(bool)only flips a boolean and has no URL to route through unlesswxURL::SetProxy/SetDefaultProxyis called first, which aMule never calls (git grepof entire upstream tree returns zero hits). The legacy Proxy-pref → HTTP plumbing was non-functional. SOCKS is out of scope on both paths; wx 3.3'swxWebProxyis HTTP-only and the legacy path didn't handle it either.macOS: ATS opt-out
NSURLSessionenforces App Transport Security, blocking plaintexthttp://by default on 10.11+. The legacywxSocketpath sidestepped ATS because it was raw BSD sockets. First download after the rewrite reproduced:plutil -replace NSAppTransportSecurity -json '{"NSAllowsArbitraryLoads": true}' Info.plistas aPOST_BUILDon theamuletarget restores the legacy behaviour. Not a new policy choice — simply keeping parity with master.macOS: exit-time crash workaround
After the rewrite, Cmd+Q (or any shutdown after an HTTP download) aborts:
Root cause is an upstream wxWidgets 3.3.2 bug. Reading
webrequest_urlsession.mm:508-515:wxWebSessionURLSession::~wxWebSessionURLSession() { [m_session release]; [m_delegate release]; ... }Per Apple's own docs,
NSURLSessionholds a strong reference to its delegate;-releaseon the session without first calling-invalidateAndCanceltriggers the session's dealloc to drop the delegate ref — and the next line releases an already-freed delegate. Double-free. Fires in wx's module cleanup, inatexit's__cxa_finalize, and anywhere else the destructor runs.Since aMule's orderly shutdown (
CamuleApp::OnExit) has already saved prefs, credits, known files, shared files, server list, partfile state, joined upload/download/disk I/O/asio threads, and logged "aMule shutdown completed." before we reach the end of the function, nothing aMule-owned remains to clean up.std::_Exit(0)bypassesatexit+ static destructors + wx's module cleanup, so the buggy dtor never fires and the process terminates cleanly.Scope of the skip on macOS:
AMULE_APP_BASE::OnExit()— defaultwxApp::OnExit, essentially a no-op.~CamuleApp/~CamuleAppCommon— only meaningful action isdelete m_singleInstance, which drops themuleLockfcntladvisory lock. The kernel releases the lock on process exit anyway, so the next launch is unaffected.WebRequestModule::OnExitis where the buggy dtor would fire.atexit()handlers —grep atexit src/returns zero hits; aMule registers none.Linux and Windows continue through wx's normal cleanup; the
_Exitblock is#if defined(__APPLE__)only and can be removed the day wx ships a fix in a version we depend on. The upstream fix is straightforward (call-invalidateAndCancelbefore release).Windows: portable install with bundled CA certs
On MSYS2 builds (MINGW64, CLANGARM64),
wxWebRequestis backed by libcurl built against OpenSSL. MSYS2's libcurl is compiled with an absolute--with-ca-bundle=path — e.g./clangarm64/etc/ssl/certs/ca-bundle.crt— that exists on the developer's machine but not on any end-user box. Result: every HTTPS request fails withThis affects the default version-check URL (
http://amule.sourceforge.net/lastversion→ 301 HTTPS), and any server that redirects HTTP→HTTPS.Two-part fix so
cmake --installproduces a self-contained portable directory that works on a fresh Windows machine:CMake install-time bundling (
src/CMakeLists.txt, in the existingWIN32 AND MINGWblock that already usesfile(GET_RUNTIME_DEPENDENCIES)to ship MSYS2 DLLs):find_filethe MSYS2 CA bundle (/clangarm64/etc/ssl/cert.pemor/mingw64/etc/ssl/certs/ca-bundle.crt, as shipped bymingw-w64-*-ca-certificates),install(FILES ... RENAME ca-bundle.crt)intobin/alongside the executables.Runtime lookup (
src/amule.cpp,__WINDOWS__-guarded, at the top ofCamuleApp::OnInit): ifCURL_CA_BUNDLEis not set, look forca-bundle.crtnext to the running executable viawxStandardPaths::GetExecutablePath+wxFileName::SetFullName, and set the env var viawxSetEnv. Users who want to point at their own bundle can still set the env var before launch.Result:
cmake --install build --prefix /path/to/portableproduces a fully self-contained tree (GUI + daemon + CLI + 34 DLLs + skins +ca-bundle.crt) that runs on any Windows ARM64 or x86_64 machine without MSYS2, with HTTPS working out of the box.Dialog / thread lifetime
CHTTPDownloadDialogholds a raw pointer to its owningCHTTPDownloadThread. On successful completion,FinishAndDestroypostswxEVT_HTTP_SHUTDOWNto the dialog, postswxEVT_CORE_FINISHED_HTTP_DOWNLOADto the app, thenCallAfters its owndelete. Processing on the main loop:OnShutdownruns →Destroy()queues the dialog onwxPendingDelete.CallAfterlambda runs → thread deleted.DeletePendingObjects→~CHTTPDownloadDialog→StopDownload()→ dereferencesm_owner— use-after-free.Fix:
OnShutdownnullsm_ownerimmediately, so the subsequent dtor seesm_owner == NULLand skips the wholeStopDownloaddance. The reverse direction (dialog closes first, cancels download) was already safe viaDetachCompanion()on the thread side.Progress bar safety (wxGTK)
wxWebRequest::GetBytesExpectedToReceive()returnswxInvalidOffset(= −1) when the server omitsContent-Length, and can also return 0 early in the state machine before headers have been parsed. Forwarding either value unchanged toCHTTPDownloadDialog::UpdateGaugeended up inwxGauge::SetRange(-1), which putsm_rangeMaxinto an invalid state and trips an assertion on the next repaint on wxGTK:Reproducible on the Ubuntu IP-filter update dialog. Fixed in two places:
OnStateEventclampsGetBytesExpectedToReceive()to 0 before posting the progress event, so "unknown total" never reaches the dialog as a negative value.UpdateGaugeonly touches the gauge whentotal > 0, and clampscurrentto[0, total]beforeSetValue, so out-of-range values can never reach the widget regardless of what the caller passes.Follow-up / future work
Create() / Run() / Stop() / OnExit() / Entry()can be turned into a real synchronous implementation viawxWebRequestSync, simplifying the class.~wxWebSessionURLSessiondelegate double-release upstream to wx; drop the_Exitworkaround when it lands.http_proxyenv vars on wx 3.2 viawxSetEnv, so GUI-configured proxy works on 3.2 builds too (out of scope here — legacy path never honoured the pref either).