Skip to content

Commit f2fe4c1

Browse files
committed
feat(reviewers): use obscure fixed port with setting
1. Put the fixed port under an opt-in developer option due to uncertainty about the security until someone takes a deeper look in the matter 2. Randomize the initial value of the port so the port isn't publicly known
1 parent 6ff8220 commit f2fe4c1

File tree

6 files changed

+44
-38
lines changed

6 files changed

+44
-38
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ import com.ichi2.anki.settings.Prefs
127127
import com.ichi2.anki.snackbar.BaseSnackbarBuilderProvider
128128
import com.ichi2.anki.snackbar.SnackbarBuilder
129129
import com.ichi2.anki.snackbar.showSnackbar
130-
import com.ichi2.anki.ui.windows.reviewer.ReviewerViewModel
130+
import com.ichi2.anki.ui.windows.reviewer.ReviewerFragment
131131
import com.ichi2.anki.utils.OnlyOnce.Method.ANSWER_CARD
132132
import com.ichi2.anki.utils.OnlyOnce.preventSimultaneousExecutions
133133
import com.ichi2.anki.utils.ext.showDialogFragment
@@ -163,8 +163,6 @@ import kotlinx.coroutines.runBlocking
163163
import timber.log.Timber
164164
import java.io.File
165165
import java.io.UnsupportedEncodingException
166-
import java.net.BindException
167-
import java.net.ServerSocket
168166
import java.net.URLDecoder
169167
import java.util.concurrent.locks.Lock
170168
import java.util.concurrent.locks.ReadWriteLock
@@ -272,7 +270,7 @@ abstract class AbstractFlashcardViewer :
272270

273271
// needs to be lateinit due to a reliance on Context
274272

275-
val server: AnkiServer
273+
lateinit var server: AnkiServer
276274

277275
@get:VisibleForTesting
278276
var cardContent: String? = null
@@ -363,16 +361,6 @@ abstract class AbstractFlashcardViewer :
363361
}
364362

365363
init {
366-
val port =
367-
try {
368-
ServerSocket(ReviewerViewModel.DEFAULT_PORT).use {
369-
it.reuseAddress = true
370-
it.localPort
371-
}
372-
} catch (_: BindException) {
373-
0
374-
}
375-
server = AnkiServer(this, port).also { it.start() }
376364
ChangeManager.subscribe(this)
377365
}
378366

@@ -561,6 +549,8 @@ abstract class AbstractFlashcardViewer :
561549

562550
setContentView(getContentViewAttr(fullscreenMode))
563551

552+
val port = ReviewerFragment.getServerPort()
553+
server = AnkiServer(this, port).also { it.start() }
564554
// Make ACTION_PROCESS_TEXT for in-app searching possible on > Android 4.0
565555
delegate.isHandleNativeActionModesEnabled = true
566556
val mainView = findViewById<View>(android.R.id.content)
@@ -655,7 +645,9 @@ abstract class AbstractFlashcardViewer :
655645

656646
override fun onDestroy() {
657647
super.onDestroy()
658-
server.stop()
648+
if (this::server.isInitialized) {
649+
server.stop()
650+
}
659651
tts.releaseTts(this)
660652
// WebView.destroy() should be called after the end of use
661653
// http://developer.android.com/reference/android/webkit/WebView.html#destroy()

AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,8 @@ object Prefs {
194194
var isDevOptionsEnabled: Boolean
195195
get() = getBoolean(R.string.dev_options_enabled_by_user_key, false) || BuildConfig.DEBUG
196196
set(value) = putBoolean(R.string.dev_options_enabled_by_user_key, value)
197+
198+
var useFixedPortInReviewer by booleanPref(R.string.use_fixed_port_pref_key, false)
199+
200+
var reviewerPort by intPref(R.string.reviewer_port_pref_key, defaultValue = 0)
197201
}

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,18 @@ import com.ichi2.anki.utils.ext.removeSubMenu
8282
import com.ichi2.anki.utils.ext.sharedPrefs
8383
import com.ichi2.anki.utils.ext.window
8484
import com.ichi2.libanki.sched.Counts
85+
import timber.log.Timber
86+
import java.net.BindException
87+
import java.net.ServerSocket
8588

8689
class ReviewerFragment :
8790
CardViewerFragment(R.layout.reviewer2),
8891
BaseSnackbarBuilderProvider,
8992
ActionMenuView.OnMenuItemClickListener,
9093
DispatchKeyEventListener {
9194
override val viewModel: ReviewerViewModel by viewModels {
92-
ReviewerViewModel.factory(CardMediaPlayer(), BindingMap(sharedPrefs(), ViewerAction.entries))
95+
val bindingMap = BindingMap(sharedPrefs(), ViewerAction.entries)
96+
ReviewerViewModel.factory(CardMediaPlayer(), bindingMap, getServerPort())
9397
}
9498

9599
override val webView: WebView
@@ -451,5 +455,23 @@ class ReviewerFragment :
451455

452456
companion object {
453457
fun getIntent(context: Context): Intent = CardViewerActivity.getIntent(context, ReviewerFragment::class)
458+
459+
fun getServerPort(): Int {
460+
if (!Prefs.useFixedPortInReviewer) return 0
461+
return try {
462+
ServerSocket(Prefs.reviewerPort)
463+
.use {
464+
it.reuseAddress = true
465+
it.localPort
466+
}.also {
467+
if (Prefs.reviewerPort == 0) {
468+
Prefs.reviewerPort = it
469+
}
470+
}
471+
} catch (_: BindException) {
472+
Timber.w("Fixed port %d under use. Using dynamic port", Prefs.reviewerPort)
473+
0
474+
}
475+
}
454476
}
455477
}

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,11 @@ import kotlinx.coroutines.delay
6969
import kotlinx.coroutines.flow.MutableSharedFlow
7070
import kotlinx.coroutines.flow.MutableStateFlow
7171
import timber.log.Timber
72-
import java.net.BindException
73-
import java.net.ServerSocket
7472

7573
class ReviewerViewModel(
7674
cardMediaPlayer: CardMediaPlayer,
7775
private val bindingMap: BindingMap<ReviewerBinding, ViewerAction>,
76+
serverPort: Int = 0,
7877
) : CardViewerViewModel(cardMediaPlayer),
7978
ChangeManager.Subscriber,
8079
BindingProcessor<ReviewerBinding, ViewerAction> {
@@ -99,7 +98,7 @@ class ReviewerViewModel(
9998
val typeAnswerFlow = MutableStateFlow<TypeAnswer?>(null)
10099
val destinationFlow = MutableSharedFlow<Destination>()
101100

102-
override val server: AnkiServer
101+
override val server: AnkiServer = AnkiServer(this, serverPort).also { it.start() }
103102
private val stateMutationKey = TimeManager.time.intTimeMS().toString()
104103
val statesMutationEval = MutableSharedFlow<String>()
105104

@@ -127,17 +126,6 @@ class ReviewerViewModel(
127126
}
128127

129128
init {
130-
val port =
131-
try {
132-
ServerSocket(DEFAULT_PORT).use {
133-
it.reuseAddress = true
134-
it.localPort
135-
}
136-
} catch (_: BindException) {
137-
0
138-
}
139-
server = AnkiServer(this, port).also { it.start() }
140-
141129
bindingMap.setProcessor(this)
142130
ChangeManager.subscribe(this)
143131
launchCatchingIO {
@@ -644,20 +632,14 @@ class ReviewerViewModel(
644632
}
645633

646634
companion object {
647-
/**
648-
* Default port of the Reviewer's [AnkiServer].
649-
* Using a static port makes the URL constant, and that
650-
* makes possible to use JavaScript's `localStorage`.
651-
*/
652-
const val DEFAULT_PORT = 40001
653-
654635
fun factory(
655636
soundPlayer: CardMediaPlayer,
656637
bindingMap: BindingMap<ReviewerBinding, ViewerAction>,
638+
serverPort: Int,
657639
): ViewModelProvider.Factory =
658640
viewModelFactory {
659641
initializer {
660-
ReviewerViewModel(soundPlayer, bindingMap)
642+
ReviewerViewModel(soundPlayer, bindingMap, serverPort)
661643
}
662644
}
663645

AnkiDroid/src/main/res/values/preferences.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@
205205
<string name="pref_new_notifications">newNotifications</string>
206206
<string name="dev_options_enabled_by_user_key">devOptionsEnabledByUser</string>
207207
<string name="pref_cat_wip_key">workInProgressDevOptions</string>
208+
<string name="reviewer_port_pref_key">reviewerPort</string>
209+
<string name="use_fixed_port_pref_key">useFixedPort</string>
208210
<!-- Developer options > Create fake notes -->
209211
<string name="pref_fill_default_deck_number_key">FillDefaultNumberDeck</string>
210212
<string name="pref_fill_default_deck_key">FillDefaultDeck</string>

AnkiDroid/src/main/res/xml/preferences_dev_options.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
android:title="Set Database to pre-Scoped Storage default"
4949
android:summary="Touch here to set the database path to /storage/emulated/0/AnkiDroid"
5050
android:key="@string/pref_set_database_path_debug_key"/>
51+
<SwitchPreferenceCompat
52+
android:title="Use fixed port in the Reviewer"
53+
android:key="@string/use_fixed_port_pref_key"
54+
android:defaultValue="false"/>
5155
<SwitchPreferenceCompat
5256
android:title="New congrats screen"
5357
android:key="@string/new_congrats_screen_pref_key"

0 commit comments

Comments
 (0)