feat: Add support for IP address:port access

Update regex to support IP address format
Improve URL handling logic for IP addresses
Update error message to reflect supported formats
fix: Allow cleartext HTTP traffic

Add android:usesCleartextTraffic="true" to AndroidManifest.xml
Fix err_cleartext_not_permitted error
perf: Optimize regex performance

Precompile regex patterns as constants
Use precompiled URL_PATTERN and IP_PATTERN for matching
Reduce CPU overhead on each button click
This commit is contained in:
Yawata 2024-12-30 09:13:26 +08:00
parent 789d2eea0d
commit 3d49ae565d
6 changed files with 48 additions and 13 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" /> <bytecodeTargetLevel target="21" />
</component> </component>
</project> </project>

View file

@ -4,6 +4,7 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View file

@ -9,7 +9,8 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.WebViewApp"> android:theme="@style/Theme.WebViewApp"
android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View file

@ -17,6 +17,8 @@ class MainActivity : AppCompatActivity() {
companion object { companion object {
const val SHARED_PREFS_NAME = "lobchat_prefs" const val SHARED_PREFS_NAME = "lobchat_prefs"
const val KEY_SAVED_URL = "saved_url" const val KEY_SAVED_URL = "saved_url"
private val URL_PATTERN = Regex("^(?:(http|https):\\/\\/)?((?:[\\w-]+\\.)+[a-z0-9]+|(?:\\d{1,3}\\.){3}\\d{1,3})(:\\d+)?((?:\\/[^/?#]*)+)?(\\?[^#]+)?(#.+)?$")
private val IP_PATTERN = Regex("^(\\d{1,3}\\.){3}\\d{1,3}(:\\d+)?$")
} }
@ -40,18 +42,16 @@ class MainActivity : AppCompatActivity() {
// 点击按钮后加载用户输入的 URL // 点击按钮后加载用户输入的 URL
loadUrlButton.setOnClickListener { loadUrlButton.setOnClickListener {
val url = urlInput.text.toString().trim() val url = urlInput.text.toString().trim()
val urlPattern = Regex("^(?:(http|https):\\/\\/)?((?:[\\w-]+\\.)+[a-z0-9]+)((?:\\/[^/?#]*)+)?(\\?[^#]+)?(#.+)?$")
if (url.isNotEmpty()) { if (url.isNotEmpty()) {
// 确保 URL 包含 http 或 https // 确保 URL 包含 http 或 https
val formattedUrl = if (!urlPattern.matches(url)) { val formattedUrl = if (!URL_PATTERN.matches(url)) {
Toast.makeText(this, "请输入正确的网站地址", Toast.LENGTH_SHORT).show() Toast.makeText(this, "请输入正确的网站地址或IP", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
} else { } else {
if (!url.startsWith("http://") && !url.startsWith("https://")) { when {
"https://$url" IP_PATTERN.matches(url) -> "http://$url"
} else { !url.startsWith("http://") && !url.startsWith("https://") -> "https://$url"
url else -> url
} }
} }
@ -71,3 +71,4 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }

View file

@ -1,6 +1,7 @@
package com.example.lobchat package com.example.lobchat
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -23,6 +24,12 @@ class WebViewActivity : AppCompatActivity() {
private var isLobeChatVerified = false private var isLobeChatVerified = false
private lateinit var refreshButton: FloatingActionButton private lateinit var refreshButton: FloatingActionButton
companion object {
const val SHARED_PREFS_NAME = "lobchat_prefs"
const val KEY_SAVED_URLS = "saved_urls"
const val MAX_URL_HISTORY = 5
}
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -77,7 +84,9 @@ class WebViewActivity : AppCompatActivity() {
override fun onPageFinished(view: WebView?, url: String?) { override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url) super.onPageFinished(view, url)
validateLobeChat(view) if (url != null) {
validateLobeChat(view, url)
}
} }
} }
@ -94,7 +103,7 @@ class WebViewActivity : AppCompatActivity() {
} }
} }
private fun validateLobeChat(view: WebView?) { private fun validateLobeChat(view: WebView?, url: String) {
if (!isLobeChatVerified) { if (!isLobeChatVerified) {
view?.evaluateJavascript( view?.evaluateJavascript(
""" """
@ -107,6 +116,7 @@ class WebViewActivity : AppCompatActivity() {
if (result == "true") { if (result == "true") {
isLobeChatVerified = true isLobeChatVerified = true
Toast.makeText(this@WebViewActivity, "LobeChat validation passed", Toast.LENGTH_SHORT).show() Toast.makeText(this@WebViewActivity, "LobeChat validation passed", Toast.LENGTH_SHORT).show()
saveUrlToHistory(url)
} else { } else {
handleLobeChatValidationFailure() handleLobeChatValidationFailure()
} }
@ -114,6 +124,28 @@ class WebViewActivity : AppCompatActivity() {
} }
} }
private fun saveUrlToHistory(url: String) {
val sharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
val urls = sharedPreferences.getStringSet(KEY_SAVED_URLS, mutableSetOf())?.toMutableList() ?: mutableListOf()
// Remove the URL if it already exists
urls.remove(url)
// Add the URL to the beginning of the list
urls.add(0, url)
// Ensure the list does not exceed the maximum size
if (urls.size > MAX_URL_HISTORY) {
urls.removeAt(urls.size - 1)
}
// Save the updated list back to SharedPreferences
with(sharedPreferences.edit()) {
putStringSet(KEY_SAVED_URLS, urls.toSet())
apply()
}
}
private fun handleLobeChatValidationFailure() { private fun handleLobeChatValidationFailure() {
Toast.makeText(this@WebViewActivity, "The webpage does not contain the required LobeChat text in the header.", Toast.LENGTH_LONG).show() Toast.makeText(this@WebViewActivity, "The webpage does not contain the required LobeChat text in the header.", Toast.LENGTH_LONG).show()
val intent = Intent(this@WebViewActivity, MainActivity::class.java) val intent = Intent(this@WebViewActivity, MainActivity::class.java)