Skip to content

Commit 3a5f785

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 11333a8 commit 3a5f785

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
@@ -198,4 +198,8 @@ object Prefs {
198198
var isDevOptionsEnabled: Boolean
199199
get() = getBoolean(R.string.dev_options_enabled_by_user_key, false) || BuildConfig.DEBUG
200200
set(value) = putBoolean(R.string.dev_options_enabled_by_user_key, value)
201+
202+
var useFixedPortInReviewer by booleanPref(R.string.use_fixed_port_pref_key, false)
203+
204+
var reviewerPort by intPref(R.string.reviewer_port_pref_key, defaultValue = 0)
201205
}

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
@@ -93,6 +93,9 @@ import com.ichi2.anki.utils.ext.window
9393
import com.ichi2.anki.utils.isWindowCompact
9494
import com.ichi2.libanki.sched.Counts
9595
import com.ichi2.utils.dp
96+
import timber.log.Timber
97+
import java.net.BindException
98+
import java.net.ServerSocket
9699

97100
class ReviewerFragment :
98101
CardViewerFragment(R.layout.reviewer2),
@@ -101,7 +104,8 @@ class ReviewerFragment :
101104
DispatchKeyEventListener,
102105
TagsDialogListener {
103106
override val viewModel: ReviewerViewModel by viewModels {
104-
ReviewerViewModel.factory(CardMediaPlayer(), BindingMap(sharedPrefs(), ViewerAction.entries))
107+
val bindingMap = BindingMap(sharedPrefs(), ViewerAction.entries)
108+
ReviewerViewModel.factory(CardMediaPlayer(), bindingMap, getServerPort())
105109
}
106110

107111
override val webView: WebView
@@ -539,5 +543,23 @@ class ReviewerFragment :
539543

540544
companion object {
541545
fun getIntent(context: Context): Intent = CardViewerActivity.getIntent(context, ReviewerFragment::class)
546+
547+
fun getServerPort(): Int {
548+
if (!Prefs.useFixedPortInReviewer) return 0
549+
return try {
550+
ServerSocket(Prefs.reviewerPort)
551+
.use {
552+
it.reuseAddress = true
553+
it.localPort
554+
}.also {
555+
if (Prefs.reviewerPort == 0) {
556+
Prefs.reviewerPort = it
557+
}
558+
}
559+
} catch (_: BindException) {
560+
Timber.w("Fixed port %d under use. Using dynamic port", Prefs.reviewerPort)
561+
0
562+
}
563+
}
542564
}
543565
}

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
@@ -71,12 +71,11 @@ import kotlinx.coroutines.delay
7171
import kotlinx.coroutines.flow.MutableSharedFlow
7272
import kotlinx.coroutines.flow.MutableStateFlow
7373
import timber.log.Timber
74-
import java.net.BindException
75-
import java.net.ServerSocket
7674

7775
class ReviewerViewModel(
7876
cardMediaPlayer: CardMediaPlayer,
7977
private val bindingMap: BindingMap<ReviewerBinding, ViewerAction>,
78+
serverPort: Int = 0,
8079
) : CardViewerViewModel(cardMediaPlayer),
8180
ChangeManager.Subscriber,
8281
BindingProcessor<ReviewerBinding, ViewerAction> {
@@ -103,7 +102,7 @@ class ReviewerViewModel(
103102
val editNoteTagsFlow = MutableSharedFlow<NoteId>()
104103
val setDueDateFlow = MutableSharedFlow<CardId>()
105104

106-
override val server: AnkiServer
105+
override val server: AnkiServer = AnkiServer(this, serverPort).also { it.start() }
107106
private val stateMutationKey = TimeManager.time.intTimeMS().toString()
108107
val statesMutationEval = MutableSharedFlow<String>()
109108

@@ -131,17 +130,6 @@ class ReviewerViewModel(
131130
}
132131

133132
init {
134-
val port =
135-
try {
136-
ServerSocket(DEFAULT_PORT).use {
137-
it.reuseAddress = true
138-
it.localPort
139-
}
140-
} catch (_: BindException) {
141-
0
142-
}
143-
server = AnkiServer(this, port).also { it.start() }
144-
145133
bindingMap.setProcessor(this)
146134
ChangeManager.subscribe(this)
147135
launchCatchingIO {
@@ -662,20 +650,14 @@ class ReviewerViewModel(
662650
}
663651

664652
companion object {
665-
/**
666-
* Default port of the Reviewer's [AnkiServer].
667-
* Using a static port makes the URL constant, and that
668-
* makes possible to use JavaScript's `localStorage`.
669-
*/
670-
const val DEFAULT_PORT = 40001
671-
672653
fun factory(
673654
soundPlayer: CardMediaPlayer,
674655
bindingMap: BindingMap<ReviewerBinding, ViewerAction>,
656+
serverPort: Int,
675657
): ViewModelProvider.Factory =
676658
viewModelFactory {
677659
initializer {
678-
ReviewerViewModel(soundPlayer, bindingMap)
660+
ReviewerViewModel(soundPlayer, bindingMap, serverPort)
679661
}
680662
}
681663

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@
204204
<string name="pref_new_notifications">newNotifications</string>
205205
<string name="dev_options_enabled_by_user_key">devOptionsEnabledByUser</string>
206206
<string name="pref_cat_wip_key">workInProgressDevOptions</string>
207+
<string name="reviewer_port_pref_key">reviewerPort</string>
208+
<string name="use_fixed_port_pref_key">useFixedPort</string>
207209
<!-- Developer options > Create fake notes -->
208210
<string name="pref_fill_default_deck_number_key">FillDefaultNumberDeck</string>
209211
<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)