fix: Refactor AnkiServer for future HTTPS support (Issue #15991)#20885
fix: Refactor AnkiServer for future HTTPS support (Issue #15991)#20885mixelas wants to merge 10 commits intoankidroid:mainfrom
Conversation
david-allison
left a comment
There was a problem hiding this comment.
Are you planning on adding an implementation? Otherwise this PR doesn't feel mergeable on its own
BrayanDSO
left a comment
There was a problem hiding this comment.
Have you used AI on this PR?
| @@ -0,0 +1,78 @@ | |||
| /* | |||
| * Copyright (c) 2024 AnkiDroid Contributors | |||
There was a problem hiding this comment.
I just copied this from another file in the codebase. I wanted it to look like the other files in there. I also didn't add my name because i didn't think that this would get merged.
| private const val KEYSTORE_FILENAME = "anki_keystore.bks" | ||
| private const val KEYSTORE_PASSWORD = "ankidroid" | ||
| private const val KEY_PASSWORD = "ankidroid" |
There was a problem hiding this comment.
I guess that hardcoding these values isn't safe
There was a problem hiding this comment.
Their purpose was strictly as a placeholder. I didn't think that you would entrust me to do the whole thing so i made this instead.
|
|
||
| // TODO: Generate self-signed X.509 certificate | ||
| // Currently creates empty keystore. Future implementations should use: | ||
| // - Android KeyStore API (preferred), or |
There was a problem hiding this comment.
we probably should be doing that now
There was a problem hiding this comment.
I acknowledge this is incomplete. I didn't know which architecture you preferred.
| val statesMutationEvalFlow = MutableSharedFlow<String>() | ||
|
|
||
| override val server: AnkiServer = AnkiServer(this, repository.getServerPort()).also { it.start() } | ||
| override val server: AnkiServer = AnkiServer(this, null, repository.getServerPort()).also { it.start() } |
There was a problem hiding this comment.
this means the issue still isn't fixed on the new study screen
There was a problem hiding this comment.
I see the concern, if I passed null as the context, HTTPS wouldn't be enabled on the new study screen, which defeats the purpose of this PR.
| * Ignores any matching test defined with `@Flaky` in CI | ||
| * @see Flaky | ||
| */ | ||
| class IgnoreFlakyTestsInCIRule : TestRule { |
There was a problem hiding this comment.
idk why are you copying this file
There was a problem hiding this comment.
These files aren't in my commits.
| * @param runnable a function that is expected to throw an exception when executed | ||
| * @return the exception thrown by [runnable] | ||
| */ | ||
| inline fun <reified T : Throwable> assertThrows( |
There was a problem hiding this comment.
idk why you are copying this file
There was a problem hiding this comment.
These files aren't in my commits. they appear to be pre-existing in the repository or from the base branch
|
This was initially a placeholder as i didn't think that I had to make the complete HTTPS support. That is why it looks bad and AI generated, as I tried to do a simple fix. I will get on with pushing a PR with complete HTTPS support and give it my best. |
|
@david-allison can you please review again. |
|
The emulator tests are broken (and please remove the merge commit and rebase/force push onto main) |
ba113ab to
ffb28ba
Compare
| @@ -0,0 +1,36 @@ | |||
| /* | |||
| * Copyright (c) 2024 David Allison <davidallisongithub@gmail.com> | |||
There was a problem hiding this comment.
Please remove these files, they shouldn't have been committed. testutils and testlib have been replaced with test fixtures
349aa3e to
7c75875
Compare
| import timber.log.Timber | ||
|
|
||
| abstract class CardViewerViewModel( | ||
| app: Application, |
There was a problem hiding this comment.
Following the code, the Application dependency is only necessary to pass it to AnkiServer -> SslUtil so it can get a File from the cache directory.
Instead of all that, get the file on the fragment and pass it down. That way we avoid the Application dependency
7c75875 to
797d3c6
Compare
9ddccc0 to
5731752
Compare
| init { | ||
| // Enable HTTPS if context was provided (Issue #15991) | ||
| // This allows WebView to load cards without cleartext restrictions on modern Android | ||
| if (isHttps && sslContext != null) { |
There was a problem hiding this comment.
logically, it feels like isHttps doesn't need to exist. If it's true, then cacheDir is not null, which means sslContext is non-null
There was a problem hiding this comment.
Ah, i see, I removed the separate isHttps flag and now derive the scheme from the SSL context itself, so the server is HTTPS only when SSL initialization succeeds
| ) : NanoHTTPD(LOCALHOST, port) { | ||
| fun baseUrl(): String = "http://$LOCALHOST:$listeningPort/" | ||
| private val isHttps = cacheDir != null | ||
| private val sslContext = cacheDir?.let { SslUtil.getSSLContext(it) } |
There was a problem hiding this comment.
I think i fixed this one as well. I changed SSL initialization to fail safely: if keystore/context creation throws, the server logs the failure and falls back to HTTP instead of crashing on startup.
| savedInstanceState: Bundle?, | ||
| ) { | ||
| server = AnkiServer(this).also { it.start() } | ||
| server = AnkiServer(this, requireContext().cacheDir).also { it.start() } |
There was a problem hiding this comment.
Why cacheDir?
- It differs per-profile, is that intended
- The OS can wipe is, is that intended?
There was a problem hiding this comment.
I used cacheDir because the keystore is a local, disposable artifact for loopback HTTPS. It is profile-scoped, and can be regenerated if the OS clears it. If you’d prefer a more persistent location, I can switch it, but for this use case cacheDir keeps the storage ephemeral.
|
I addressed the points you raised. I inlined the ViewModel creation into the fragments and kept the cache-dir threading as small as possible. The goal was just to pass the local directory through to the server setup without adding extra architectural layers. Let me know if there is anything else that i need to do. |

Purpose / Description
This PR prepares the codebase for HTTPS support by refactoring AnkiServer to support SSL/TLS configuration, while maintaining backward compatibility.
Fixes
Approach
Added Context parameter to AnkiServer constructor to enable SSL setup when needed
How Has This Been Tested?
AnkiServer baseUrl() returning HTTP when context not provided
AnkiServer baseUrl() containing localhost address
SslUtil placeholder initialization
Tested on:
Learning (optional, can help others)
While i was working with Android SDK, i noticed that it doesn't include sun.security internal APIs in modern versions. The proper approach for self-signed certificate generation on Android involves either:
Checklist
Please, go through these checks before submitting the PR.