新增:`HttpServer`请求/应答报文进行`RSA`非对称加密传输

优化:允许自定义客户端与服务端时间容差,避免请求重放攻击 #227
pull/231/head
pppscn 2 years ago
parent 1b93aeb857
commit 581c0c2ef2

@ -6,6 +6,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RadioGroup
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.idormy.sms.forwarder.App
@ -38,9 +39,7 @@ import com.xuexiang.xutil.XUtil
@Suppress("PrivatePropertyName", "PropertyName")
@Page(name = "主动控制·客户端")
class ClientFragment : BaseFragment<FragmentClientBinding?>(),
View.OnClickListener,
RecyclerViewHolder.OnItemClickListener<PageInfo> {
class ClientFragment : BaseFragment<FragmentClientBinding?>(), View.OnClickListener, RecyclerViewHolder.OnItemClickListener<PageInfo> {
val TAG: String = ClientFragment::class.java.simpleName
private var appContext: App? = null
@ -113,6 +112,41 @@ class ClientFragment : BaseFragment<FragmentClientBinding?>(),
}
})
//安全措施
var safetyMeasuresId = R.id.rb_safety_measures_none
when (HttpServerUtils.clientSafetyMeasures) {
1 -> {
safetyMeasuresId = R.id.rb_safety_measures_sign
binding!!.tvSignKey.text = getString(R.string.sign_key)
}
2 -> {
safetyMeasuresId = R.id.rb_safety_measures_rsa
binding!!.tvSignKey.text = getString(R.string.public_key)
}
else -> {
binding!!.layoutSignKey.visibility = View.GONE
}
}
binding!!.rgSafetyMeasures.check(safetyMeasuresId)
binding!!.rgSafetyMeasures.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
var safetyMeasures = 0
binding!!.layoutSignKey.visibility = View.GONE
when (checkedId) {
R.id.rb_safety_measures_sign -> {
safetyMeasures = 1
binding!!.tvSignKey.text = getString(R.string.sign_key)
binding!!.layoutSignKey.visibility = View.VISIBLE
}
R.id.rb_safety_measures_rsa -> {
safetyMeasures = 2
binding!!.tvSignKey.text = getString(R.string.public_key)
binding!!.layoutSignKey.visibility = View.VISIBLE
}
else -> {}
}
HttpServerUtils.clientSafetyMeasures = safetyMeasures
}
binding!!.etSignKey.setText(HttpServerUtils.clientSignKey)
binding!!.etSignKey.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
@ -136,30 +170,46 @@ class ClientFragment : BaseFragment<FragmentClientBinding?>(),
}
Log.d(TAG, "serverHistory = $serverHistory")
MaterialDialog.Builder(requireContext())
.title(R.string.server_history)
.items(serverHistory.keys)
.itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
//XToastUtils.info("$which: $text")
val matches = Regex("【(.*)】(.*)", RegexOption.IGNORE_CASE).findAll(text).toList().flatMap(MatchResult::groupValues)
Log.i(TAG, "matches = $matches")
if (matches.isNotEmpty()) {
binding!!.etServerAddress.setText(matches[2])
MaterialDialog.Builder(requireContext()).title(R.string.server_history).items(serverHistory.keys).itemsCallbackSingleChoice(0) { _: MaterialDialog?, _: View?, _: Int, text: CharSequence ->
//XToastUtils.info("$which: $text")
val matches = Regex("【(.*)】(.*)", RegexOption.IGNORE_CASE).findAll(text).toList().flatMap(MatchResult::groupValues)
Log.i(TAG, "matches = $matches")
if (matches.isNotEmpty()) {
binding!!.etServerAddress.setText(matches[2])
} else {
binding!!.etServerAddress.setText(text)
}
val signKey = serverHistory[text].toString()
if (!TextUtils.isEmpty(signKey)) {
val keyMatches = Regex("(.*)##(.*)", RegexOption.IGNORE_CASE).findAll(signKey).toList().flatMap(MatchResult::groupValues)
Log.i(TAG, "keyMatches = $keyMatches")
if (keyMatches.isNotEmpty()) {
binding!!.etSignKey.setText(keyMatches[1])
var safetyMeasuresId = R.id.rb_safety_measures_none
when (keyMatches[2]) {
"1" -> {
safetyMeasuresId = R.id.rb_safety_measures_sign
binding!!.tvSignKey.text = getString(R.string.sign_key)
}
"2" -> {
safetyMeasuresId = R.id.rb_safety_measures_rsa
binding!!.tvSignKey.text = getString(R.string.public_key)
}
else -> {
binding!!.tvSignKey.visibility = View.GONE
binding!!.etSignKey.visibility = View.GONE
}
}
binding!!.rgSafetyMeasures.check(safetyMeasuresId)
} else {
binding!!.etServerAddress.setText(text)
binding!!.etSignKey.setText(serverHistory[text])
}
binding!!.etSignKey.setText(serverHistory[text])
true // allow selection
}
.positiveText(R.string.select)
.negativeText(R.string.cancel)
.neutralText(R.string.clear_history)
.neutralColor(ResUtils.getColors(R.color.red))
.onNeutral { _: MaterialDialog?, _: DialogAction? ->
serverHistory.clear()
HttpServerUtils.serverHistory = ""
}
.show()
true // allow selection
}.positiveText(R.string.select).negativeText(R.string.cancel).neutralText(R.string.clear_history).neutralColor(ResUtils.getColors(R.color.red)).onNeutral { _: MaterialDialog?, _: DialogAction? ->
serverHistory.clear()
HttpServerUtils.serverHistory = ""
}.show()
}
R.id.btn_server_test -> {
if (!CommonUtils.checkUrl(HttpServerUtils.serverAddress)) {
@ -183,22 +233,12 @@ class ClientFragment : BaseFragment<FragmentClientBinding?>(),
XToastUtils.error(getString(R.string.click_test_button_first))
return
}
if (serverConfig != null && (
(item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend)
|| (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery)
|| (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery)
|| (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery)
|| (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery)
|| (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol)
)
) {
if (serverConfig != null && ((item.name == ResUtils.getString(R.string.api_sms_send) && !serverConfig!!.enableApiSmsSend) || (item.name == ResUtils.getString(R.string.api_sms_query) && !serverConfig!!.enableApiSmsQuery) || (item.name == ResUtils.getString(R.string.api_call_query) && !serverConfig!!.enableApiCallQuery) || (item.name == ResUtils.getString(R.string.api_contact_query) && !serverConfig!!.enableApiContactQuery) || (item.name == ResUtils.getString(R.string.api_battery_query) && !serverConfig!!.enableApiBatteryQuery) || (item.name == ResUtils.getString(R.string.api_wol) && !serverConfig!!.enableApiWol))) {
XToastUtils.error(getString(R.string.disabled_on_the_server))
return
}
@Suppress("UNCHECKED_CAST")
PageOption.to(Class.forName(item.classPath) as Class<XPageFragment>) //跳转的fragment
.setNewActivity(true)
.open(this)
@Suppress("UNCHECKED_CAST") PageOption.to(Class.forName(item.classPath) as Class<XPageFragment>) //跳转的fragment
.setNewActivity(true).open(this)
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(e.message.toString())
@ -212,58 +252,87 @@ class ClientFragment : BaseFragment<FragmentClientBinding?>(),
val msgMap: MutableMap<String, Any> = mutableMapOf()
val timestamp = System.currentTimeMillis()
msgMap["timestamp"] = timestamp
val clientSignKey = HttpServerUtils.clientSignKey
if (!TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey.toString())
val clientSignKey = HttpServerUtils.clientSignKey.toString()
if ((HttpServerUtils.clientSafetyMeasures == 1 || HttpServerUtils.clientSafetyMeasures == 2) && TextUtils.isEmpty(clientSignKey)) {
if (needToast) XToastUtils.error("请输入签名密钥或RSA公钥")
return
}
if (HttpServerUtils.clientSafetyMeasures == 1 && !TextUtils.isEmpty(clientSignKey)) {
msgMap["sign"] = HttpServerUtils.calcSign(timestamp.toString(), clientSignKey)
}
val dataMap: MutableMap<String, Any> = mutableMapOf()
msgMap["data"] = dataMap
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
if (needToast) mCountDownHelper?.start()
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
//.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
//.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
//.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
.cacheMode(CacheMode.NO_CACHE).timeStamp(true)
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
if (needToast) mCountDownHelper?.finish()
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<ConfigData> = Gson().fromJson(response, object : TypeToken<BaseResponse<ConfigData>>() {}.type)
if (resp.code == 200) {
serverConfig = resp.data!!
if (needToast) XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
//删除3.0.8之前保存的记录
serverHistory.remove(HttpServerUtils.serverAddress.toString())
//添加到历史记录
val key = "${serverConfig?.extraDeviceMark}${HttpServerUtils.serverAddress.toString()}"
serverHistory[key] = HttpServerUtils.clientSignKey ?: ""
HttpServerUtils.serverHistory = Gson().toJson(serverHistory)
HttpServerUtils.serverConfig = Gson().toJson(serverConfig)
if (needToast) mCountDownHelper?.start()
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
if (needToast) mCountDownHelper?.finish()
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<ConfigData> = Gson().fromJson(json, object : TypeToken<BaseResponse<ConfigData>>() {}.type)
if (resp.code == 200) {
serverConfig = resp.data!!
if (needToast) XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
//删除3.0.8之前保存的记录
serverHistory.remove(HttpServerUtils.serverAddress.toString())
//添加到历史记录
val key = "${serverConfig?.extraDeviceMark}${HttpServerUtils.serverAddress.toString()}"
if (TextUtils.isEmpty(HttpServerUtils.clientSignKey)) {
serverHistory[key] = "SMSFORWARDER##" + HttpServerUtils.clientSafetyMeasures.toString()
} else {
if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
serverHistory[key] = HttpServerUtils.clientSignKey + "##" + HttpServerUtils.clientSafetyMeasures.toString()
}
} catch (e: Exception) {
e.printStackTrace()
if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
HttpServerUtils.serverHistory = Gson().toJson(serverHistory)
HttpServerUtils.serverConfig = Gson().toJson(serverConfig)
} else {
if (needToast) XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
if (needToast) mCountDownHelper?.finish()
} catch (e: Exception) {
e.printStackTrace()
if (needToast) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
mCountDownHelper?.finish()
}
}
})
}
})
}
override fun onDestroyView() {

@ -10,6 +10,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.RadioGroup
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
@ -18,20 +19,19 @@ import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentServerBinding
import com.idormy.sms.forwarder.service.HttpService
import com.idormy.sms.forwarder.utils.HTTP_SERVER_PORT
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.RandomUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xpage.annotation.Page
import com.xuexiang.xui.widget.actionbar.TitleBar
import com.xuexiang.xui.widget.button.SmoothCheckBox
import com.xuexiang.xui.widget.dialog.materialdialog.MaterialDialog
import com.xuexiang.xui.widget.picker.XSeekBar
import com.xuexiang.xutil.app.ServiceUtils
import com.xuexiang.xutil.net.NetworkUtils
import com.xuexiang.xutil.system.ClipboardUtils
import java.io.File
import java.net.InetAddress
import java.security.KeyPairGenerator
@Suppress("PrivatePropertyName")
@ -79,6 +79,65 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
//启动更新UI定时器
handler.post(runnable)
//安全措施
var safetyMeasuresId = R.id.rb_safety_measures_none
when (HttpServerUtils.safetyMeasures) {
1 -> {
safetyMeasuresId = R.id.rb_safety_measures_sign
binding!!.layoutSignKey.visibility = View.VISIBLE
binding!!.layoutTimeTolerance.visibility = View.VISIBLE
}
2 -> {
safetyMeasuresId = R.id.rb_safety_measures_rsa
binding!!.layoutPrivateKey.visibility = View.VISIBLE
binding!!.layoutPublicKey.visibility = View.VISIBLE
}
else -> {}
}
binding!!.rgSafetyMeasures.check(safetyMeasuresId)
binding!!.rgSafetyMeasures.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
var safetyMeasures = 0
binding!!.layoutSignKey.visibility = View.GONE
binding!!.layoutTimeTolerance.visibility = View.GONE
binding!!.layoutPrivateKey.visibility = View.GONE
binding!!.layoutPublicKey.visibility = View.GONE
when (checkedId) {
R.id.rb_safety_measures_sign -> {
safetyMeasures = 1
binding!!.layoutSignKey.visibility = View.VISIBLE
binding!!.layoutTimeTolerance.visibility = View.VISIBLE
}
R.id.rb_safety_measures_rsa -> {
safetyMeasures = 2
binding!!.layoutPrivateKey.visibility = View.VISIBLE
binding!!.layoutPublicKey.visibility = View.VISIBLE
}
else -> {}
}
HttpServerUtils.safetyMeasures = safetyMeasures
}
//RSA公私钥
binding!!.btnCopyPublicKey.setOnClickListener(this)
binding!!.btnGenerateKey.setOnClickListener(this)
binding!!.etPublicKey.setText(HttpServerUtils.serverPublicKey)
binding!!.etPublicKey.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
HttpServerUtils.serverPublicKey = binding!!.etPublicKey.text.toString().trim()
}
})
binding!!.etPrivateKey.setText(HttpServerUtils.serverPrivateKey)
binding!!.etPrivateKey.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
HttpServerUtils.serverPrivateKey = binding!!.etPrivateKey.text.toString().trim()
}
})
//签名密钥
binding!!.btnSignKey.setOnClickListener(this)
binding!!.btnPathPicker.setOnClickListener(this)
binding!!.etSignKey.setText(HttpServerUtils.serverSignKey)
@ -89,6 +148,13 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
HttpServerUtils.serverSignKey = binding!!.etSignKey.text.toString().trim()
}
})
//时间容差
binding!!.xsbTimeTolerance.setDefaultValue(HttpServerUtils.timeTolerance)
binding!!.xsbTimeTolerance.setOnSeekBarListener { _: XSeekBar?, newValue: Int ->
HttpServerUtils.timeTolerance = newValue
}
//web客户端
binding!!.etWebPath.setText(HttpServerUtils.serverWebPath)
binding!!.etWebPath.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
@ -155,6 +221,29 @@ class ServerFragment : BaseFragment<FragmentServerBinding?>(), View.OnClickListe
}
refreshButtonText()
}
R.id.btn_generate_key -> {
val generator = KeyPairGenerator.getInstance("RSA") //密钥生成器
generator.initialize(2048)
val keyPair = generator.genKeyPair()
val publicKey = keyPair.public
val privateKey = keyPair.private
val publicKeyEncoded = Base64.encode(publicKey.encoded)
val privateKeyEncoded = Base64.encode(privateKey.encoded)
println("publicKey=$publicKeyEncoded")
println("privateKey=$privateKeyEncoded")
binding!!.etPublicKey.setText(publicKeyEncoded)
binding!!.etPrivateKey.setText(privateKeyEncoded)
ClipboardUtils.copyText(publicKeyEncoded)
XToastUtils.info(getString(R.string.rsa_key_tips))
}
R.id.btn_copy_public_key -> {
ClipboardUtils.copyText(binding!!.etPublicKey.text)
XToastUtils.info(getString(R.string.rsa_key_tips2))
}
R.id.btn_sign_key -> {
val sign = RandomUtils.getRandomNumbersAndLetters(16)
ClipboardUtils.copyText(sign)

@ -10,9 +10,7 @@ import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientBatteryQueryBinding
import com.idormy.sms.forwarder.entity.BatteryInfo
import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack
@ -60,53 +58,71 @@ class BatteryQueryFragment : BaseFragment<FragmentClientBatteryQueryBinding?>()
val dataMap: MutableMap<String, Any> = mutableMapOf()
msgMap["data"] = dataMap
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
//.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
//.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
//.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<BatteryInfo> = Gson().fromJson(response, object : TypeToken<BaseResponse<BatteryInfo>>() {}.type)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
val batteryInfo = resp.data ?: return
val groupListView = binding!!.infoList
val section = XUIGroupListView.newSection(context)
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {}
if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {}
if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {}
if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {}
section.addTo(groupListView)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<BatteryInfo> = Gson().fromJson(json, object : TypeToken<BaseResponse<BatteryInfo>>() {}.type)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
val batteryInfo = resp.data ?: return
val groupListView = binding!!.infoList
val section = XUIGroupListView.newSection(context)
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_level), batteryInfo.level))) {}
if (batteryInfo.scale != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_scale), batteryInfo.scale))) {}
if (batteryInfo.voltage != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_voltage), batteryInfo.voltage))) {}
if (batteryInfo.temperature != "") section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_temperature), batteryInfo.temperature))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_status), batteryInfo.status))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_health), batteryInfo.health))) {}
section.addItemView(groupListView.createItemView(String.format(ResUtils.getString(R.string.battery_plugged), batteryInfo.plugged))) {}
section.addTo(groupListView)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
}
})
})
}
}

@ -207,46 +207,65 @@ class CallQueryFragment : BaseFragment<FragmentClientCallQueryBinding?>() {
if (refresh) pageNum = 1
msgMap["data"] = CallQueryData(callType, pageNum, pageSize, keyword)
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<List<CallInfo>?> = Gson().fromJson(response, object : TypeToken<BaseResponse<List<CallInfo>?>>() {}.type)
if (resp.code == 200) {
//XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
pageNum++
if (refresh) {
mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0)
} else {
mAdapter!!.loadMore(resp.data)
binding!!.refreshLayout.finishLoadMore()
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<List<CallInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<CallInfo>?>>() {}.type)
if (resp.code == 200) {
pageNum++
if (refresh) {
mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
mAdapter!!.loadMore(resp.data)
binding!!.refreshLayout.finishLoadMore()
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
})
}
})
}

@ -18,10 +18,8 @@ import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientCloneBinding
import com.idormy.sms.forwarder.entity.CloneInfo
import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.CommonUtils
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.*
import com.idormy.sms.forwarder.utils.Base64
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
@ -181,12 +179,7 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
XToastUtils.error(getString(R.string.export_failed))
}
} catch (e: Exception) {
XToastUtils.error(
String.format(
getString(R.string.export_failed_tips),
e.message
)
)
XToastUtils.error(String.format(getString(R.string.export_failed_tips), e.message))
}
}
//导入配置
@ -225,12 +218,7 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
XToastUtils.error(getString(R.string.import_failed))
}
} catch (e: Exception) {
XToastUtils.error(
String.format(
getString(R.string.import_failed_tips),
e.message
)
)
XToastUtils.error(String.format(getString(R.string.import_failed_tips), e.message))
}
}
}
@ -258,43 +246,59 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
}
msgMap["data"] = HttpServerUtils.exportSettings()
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
//.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
//.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
//.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<String> = Gson().fromJson(
response,
object : TypeToken<BaseResponse<String>>() {}.type
)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
pushCountDownHelper?.finish()
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
})
pushCountDownHelper?.finish()
}
})
}
@ -323,62 +327,80 @@ class CloneFragment : BaseFragment<FragmentClientCloneBinding?>(), View.OnClickL
dataMap["version_code"] = AppUtils.getAppVersionCode()
msgMap["data"] = dataMap
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
//.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
//.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
//.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
exportCountDownHelper?.finish()
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
//替换Date字段为当前时间
val builder = GsonBuilder()
builder.registerTypeAdapter(
Date::class.java,
JsonDeserializer<Any?> { _, _, _ -> Date() })
val gson = builder.create()
val resp: BaseResponse<CloneInfo> = gson.fromJson(
response,
object : TypeToken<BaseResponse<CloneInfo>>() {}.type
)
if (resp.code == 200) {
val cloneInfo = resp.data
Log.d(TAG, "cloneInfo = $cloneInfo")
if (cloneInfo == null) {
XToastUtils.error(ResUtils.getString(R.string.request_failed))
return
}
//判断版本是否一致
HttpServerUtils.compareVersion(cloneInfo)
if (HttpServerUtils.restoreSettings(cloneInfo)) {
XToastUtils.success(getString(R.string.import_succeeded))
}
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
//替换Date字段为当前时间
val builder = GsonBuilder()
builder.registerTypeAdapter(
Date::class.java,
JsonDeserializer<Any?> { _, _, _ -> Date() })
val gson = builder.create()
val resp: BaseResponse<CloneInfo> = gson.fromJson(json, object : TypeToken<BaseResponse<CloneInfo>>() {}.type)
if (resp.code == 200) {
val cloneInfo = resp.data
Log.d(TAG, "cloneInfo = $cloneInfo")
if (cloneInfo == null) {
XToastUtils.error(ResUtils.getString(R.string.request_failed))
return
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
//判断版本是否一致
HttpServerUtils.compareVersion(cloneInfo)
if (HttpServerUtils.restoreSettings(cloneInfo)) {
XToastUtils.success(getString(R.string.import_succeeded))
}
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
}
})
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
exportCountDownHelper?.finish()
}
})
}

@ -190,43 +190,60 @@ class ContactQueryFragment : BaseFragment<FragmentClientContactQueryBinding?>()
else
ContactQueryData(1, 20, null, keyword)
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
//.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
//.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
//.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<List<ContactInfo>?> = Gson().fromJson(response, object : TypeToken<BaseResponse<List<ContactInfo>?>>() {}.type)
if (resp.code == 200) {
//XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<List<ContactInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<ContactInfo>?>>() {}.type)
if (resp.code == 200) {
//XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
})
}
})
}

@ -197,46 +197,66 @@ class SmsQueryFragment : BaseFragment<FragmentClientSmsQueryBinding?>() {
if (refresh) pageNum = 1
msgMap["data"] = SmsQueryData(smsType, pageNum, pageSize, keyword)
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<List<SmsInfo>?> = Gson().fromJson(response, object : TypeToken<BaseResponse<List<SmsInfo>?>>() {}.type)
if (resp.code == 200) {
//XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
pageNum++
if (refresh) {
mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0)
} else {
mAdapter!!.loadMore(resp.data)
binding!!.refreshLayout.finishLoadMore()
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<List<SmsInfo>?> = Gson().fromJson(json, object : TypeToken<BaseResponse<List<SmsInfo>?>>() {}.type)
if (resp.code == 200) {
//XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
pageNum++
if (refresh) {
mAdapter!!.refresh(resp.data)
binding!!.refreshLayout.finishRefresh()
binding!!.recyclerView.scrollToPosition(0)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
mAdapter!!.loadMore(resp.data)
binding!!.refreshLayout.finishLoadMore()
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
})
}
})
}

@ -112,43 +112,60 @@ class SmsSendFragment : BaseFragment<FragmentClientSmsSendBinding?>(), View.OnCl
dataMap["msg_content"] = msgContent
msgMap["data"] = dataMap
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
mCountDownHelper?.start()
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
//.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数
//.retryDelay(SettingUtils.requestDelayTime) //超时重试的延迟时间
//.retryIncreaseDelay(SettingUtils.requestDelayTime) //超时重试叠加延时
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
mCountDownHelper?.finish()
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<String> = Gson().fromJson(response, object : TypeToken<BaseResponse<String>>() {}.type)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
mCountDownHelper?.start()
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
mCountDownHelper?.finish()
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
mCountDownHelper?.finish()
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
})
mCountDownHelper?.finish()
}
})
}
else -> {}
}

@ -10,9 +10,7 @@ import com.idormy.sms.forwarder.R
import com.idormy.sms.forwarder.core.BaseFragment
import com.idormy.sms.forwarder.databinding.FragmentClientWolSendBinding
import com.idormy.sms.forwarder.server.model.BaseResponse
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.SettingUtils
import com.idormy.sms.forwarder.utils.XToastUtils
import com.idormy.sms.forwarder.utils.*
import com.xuexiang.xaop.annotation.SingleClick
import com.xuexiang.xhttp2.XHttp
import com.xuexiang.xhttp2.cache.model.CacheMode
@ -144,46 +142,63 @@ class WolSendFragment : BaseFragment<FragmentClientWolSendBinding?>(), View.OnCl
dataMap["port"] = port
msgMap["data"] = dataMap
val requestMsg: String = Gson().toJson(msgMap)
var requestMsg: String = Gson().toJson(msgMap)
Log.i(TAG, "requestMsg:$requestMsg")
mCountDownHelper?.start()
XHttp.post(requestUrl)
.upJson(requestMsg)
val postRequest = XHttp.post(requestUrl)
.keepJson(true)
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
mCountDownHelper?.finish()
}
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
try {
requestMsg = Base64.encode(requestMsg.toByteArray())
requestMsg = RSACrypt.encryptByPublicKey(requestMsg, publicKey)
Log.i(TAG, "requestMsg: $requestMsg")
} catch (e: Exception) {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + e.message)
e.printStackTrace()
return
}
postRequest.upString(requestMsg)
} else {
postRequest.upJson(requestMsg)
}
mCountDownHelper?.start()
postRequest.execute(object : SimpleCallBack<String>() {
override fun onError(e: ApiException) {
XToastUtils.error(e.displayMessage)
mCountDownHelper?.finish()
}
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
val resp: BaseResponse<String> = Gson().fromJson(
response,
object : TypeToken<BaseResponse<String>>() {}.type
)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
//添加到历史记录
wolHistory[mac] = ip
HttpServerUtils.wolHistory = Gson().toJson(wolHistory)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
override fun onSuccess(response: String) {
Log.i(TAG, response)
try {
var json = response
if (HttpServerUtils.clientSafetyMeasures == 2) {
val publicKey = RSACrypt.getPublicKey(HttpServerUtils.clientSignKey.toString())
json = RSACrypt.decryptByPublicKey(json, publicKey)
json = String(Base64.decode(json))
}
val resp: BaseResponse<String> = Gson().fromJson(json, object : TypeToken<BaseResponse<String>>() {}.type)
if (resp.code == 200) {
XToastUtils.success(ResUtils.getString(R.string.request_succeeded))
//添加到历史记录
wolHistory[mac] = ip
HttpServerUtils.wolHistory = Gson().toJson(wolHistory)
} else {
XToastUtils.error(ResUtils.getString(R.string.request_failed) + resp.msg)
}
mCountDownHelper?.finish()
} catch (e: Exception) {
e.printStackTrace()
XToastUtils.error(ResUtils.getString(R.string.request_failed) + response)
}
})
mCountDownHelper?.finish()
}
})
}
else -> {}
}

@ -1,13 +1,17 @@
package com.idormy.sms.forwarder.server.component
import android.text.TextUtils
import android.util.Log
import com.google.gson.GsonBuilder
import com.idormy.sms.forwarder.server.model.BaseRequest
import com.idormy.sms.forwarder.utils.Base64
import com.idormy.sms.forwarder.utils.HttpServerUtils
import com.idormy.sms.forwarder.utils.RSACrypt
import com.xuexiang.xrouter.utils.TextUtils
import com.yanzhenjie.andserver.annotation.Converter
import com.yanzhenjie.andserver.error.HttpException
import com.yanzhenjie.andserver.framework.MessageConverter
import com.yanzhenjie.andserver.framework.body.JsonBody
import com.yanzhenjie.andserver.framework.body.StringBody
import com.yanzhenjie.andserver.http.ResponseBody
import com.yanzhenjie.andserver.util.IOUtils
import com.yanzhenjie.andserver.util.MediaType
@ -25,7 +29,17 @@ class AppMessageConverter : MessageConverter {
override fun convert(output: Any?, mediaType: MediaType?): ResponseBody {
//返回统一结构报文
return JsonBody(HttpServerUtils.response(output))
var response = HttpServerUtils.response(output)
Log.d(TAG, "response: $response")
if (HttpServerUtils.safetyMeasures != 2) {
return JsonBody(response)
}
val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey.toString())
response = Base64.encode(response.toByteArray())
response = RSACrypt.encryptByPrivateKey(response, privateKey)
return StringBody(response)
}
@Throws(IOException::class)
@ -33,9 +47,21 @@ class AppMessageConverter : MessageConverter {
val charset: Charset? = mediaType?.charset
Log.d(TAG, "Charset: $charset")
val json = if (charset == null) IOUtils.toString(stream) else IOUtils.toString(stream, charset)
var json = if (charset == null) IOUtils.toString(stream) else IOUtils.toString(stream, charset)
Log.d(TAG, "Json: $json")
if (HttpServerUtils.safetyMeasures == 2) {
if (TextUtils.isEmpty(HttpServerUtils.serverPrivateKey)) {
Log.e(TAG, "RSA解密失败: 私钥为空")
throw HttpException(500, "服务端未配置私钥")
}
val privateKey = RSACrypt.getPrivateKey(HttpServerUtils.serverPrivateKey.toString())
json = RSACrypt.decryptByPrivateKey(json, privateKey)
json = String(Base64.decode(json))
Log.d(TAG, "Json: $json")
}
//修改接口数据中的null、“”为默认值
val builder = GsonBuilder()
builder.registerTypeAdapter(Int::class.java, IntegerDefaultAdapter())
@ -45,7 +71,7 @@ class AppMessageConverter : MessageConverter {
Log.d(TAG, "Bean: $t")
//校验时间戳时间误差不能超过1小时&& 签名
if (!TextUtils.isEmpty(HttpServerUtils.serverSignKey)) {
if (HttpServerUtils.safetyMeasures == 1) {
HttpServerUtils.checkSign(t as BaseRequest<*>)
}

@ -0,0 +1,88 @@
package com.idormy.sms.forwarder.utils
import java.io.UnsupportedEncodingException
/**
* Base64编码解码
*/
object Base64 {
private val base64EncodeChars = charArrayOf('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/')
private val base64DecodeChars = byteArrayOf(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1)
fun encode(data: ByteArray): String {
val sb = StringBuffer()
val len = data.size
var i = 0
var b1: Int
var b2: Int
var b3: Int
while (i < len) {
b1 = ((data[i++]).toInt() and 0xff)
if (i == len) {
sb.append(base64EncodeChars[b1.ushr(2)])
sb.append(base64EncodeChars[b1 and 0x3 shl 4])
sb.append("==")
break
}
b2 = (data[i++]).toInt() and 0xff
if (i == len) {
sb.append(base64EncodeChars[b1.ushr(2)])
sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0).ushr(4)])
sb.append(base64EncodeChars[b2 and 0x0f shl 2])
sb.append("=")
break
}
b3 = (data[i++]).toInt() and 0xff
sb.append(base64EncodeChars[b1.ushr(2)])
sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0).ushr(4)])
sb.append(base64EncodeChars[b2 and 0x0f shl 2 or (b3 and 0xc0).ushr(6)])
sb.append(base64EncodeChars[b3 and 0x3f])
}
return sb.toString()
}
@Throws(UnsupportedEncodingException::class)
fun decode(str: String): ByteArray {
val sb = StringBuffer()
val data = str.toByteArray(charset("US-ASCII"))
val len = data.size
var i = 0
var b1: Int
var b2: Int
var b3: Int
var b4: Int
while (i < len) {
/* b1 */
do {
b1 = base64DecodeChars[(data[i++]).toInt()].toInt()
} while (i < len && b1 == -1)
if (b1 == -1) break
/* b2 */
do {
b2 = base64DecodeChars[(data[i++]).toInt()].toInt()
} while (i < len && b2 == -1)
if (b2 == -1) break
sb.append((b1 shl 2 or (b2 and 0x30).ushr(4)).toChar())
/* b3 */
do {
b3 = data[i++].toInt()
if (b3 == 61) return sb.toString().toByteArray(charset("ISO-8859-1"))
b3 = base64DecodeChars[b3].toInt()
} while (i < len && b3 == -1)
if (b3 == -1) break
sb.append((b2 and 0x0f shl 4 or (b3 and 0x3c).ushr(2)).toChar())
/* b4 */
do {
b4 = data[i++].toInt()
if (b4 == 61) return sb.toString().toByteArray(charset("ISO-8859-1"))
b4 = base64DecodeChars[b4].toInt()
} while (i < len && b4 == -1)
if (b4 == -1) break
sb.append((b3 and 0x03 shl 6 or b4).toChar())
}
return sb.toString().toByteArray(charset("ISO-8859-1"))
}
}

@ -322,7 +322,11 @@ const val HTTP_SUCCESS_CODE: Int = 200
const val HTTP_FAILURE_CODE: Int = 500
const val SP_ENABLE_SERVER = "enable_server"
const val SP_ENABLE_SERVER_AUTORUN = "enable_server_autorun"
const val SP_SERVER_SAFETY_MEASURES = "server_safety_measures"
const val SP_SERVER_SIGN_KEY = "server_sign_key"
const val SP_SERVER_TIME_TOLERANCE = "server_time_tolerance"
const val SP_SERVER_PUBLIC_KEY = "server_public_key"
const val SP_SERVER_PRIVATE_KEY = "server_private_key"
const val SP_SERVER_WEB_PATH = "server_web_path"
const val SP_ENABLE_API_CLONE = "enable_api_clone"
const val SP_ENABLE_API_SMS_SEND = "enable_api_sms_send"
@ -335,6 +339,7 @@ const val SP_WOL_HISTORY = "wol_history"
const val SP_SERVER_ADDRESS = "server_address"
const val SP_SERVER_HISTORY = "server_history"
const val SP_SERVER_CONFIG = "server_config"
const val SP_CLIENT_SAFETY_MEASURES = "client_safety_measures"
const val SP_CLIENT_SIGN_KEY = "client_sign_key"
var CLIENT_FRAGMENT_LIST = listOf(
PageInfo(

@ -32,6 +32,30 @@ class HttpServerUtils private constructor() {
MMKVUtils.put(SP_ENABLE_SERVER_AUTORUN, enableServerAutorun)
}
//服务端安全设置
@JvmStatic
var safetyMeasures: Int
get() = MMKVUtils.getInt(SP_SERVER_SAFETY_MEASURES, if (TextUtils.isEmpty(serverSignKey)) 0 else 1)
set(safetyMeasures) {
MMKVUtils.put(SP_SERVER_SAFETY_MEASURES, safetyMeasures)
}
//服务端RSA公钥
@JvmStatic
var serverPublicKey: String?
get() = MMKVUtils.getString(SP_SERVER_PUBLIC_KEY, "")
set(serverPublicKey) {
MMKVUtils.put(SP_SERVER_PUBLIC_KEY, serverPublicKey)
}
//服务端RSA私钥
@JvmStatic
var serverPrivateKey: String?
get() = MMKVUtils.getString(SP_SERVER_PRIVATE_KEY, "")
set(serverPrivateKey) {
MMKVUtils.put(SP_SERVER_PRIVATE_KEY, serverPrivateKey)
}
//服务端签名密钥
@JvmStatic
var serverSignKey: String?
@ -40,6 +64,14 @@ class HttpServerUtils private constructor() {
MMKVUtils.put(SP_SERVER_SIGN_KEY, serverSignKey)
}
//时间容差
@JvmStatic
var timeTolerance: Int
get() = MMKVUtils.getInt(SP_SERVER_TIME_TOLERANCE, 600)
set(timeTolerance) {
MMKVUtils.put(SP_SERVER_TIME_TOLERANCE, timeTolerance)
}
//自定义web客户端目录
@JvmStatic
var serverWebPath: String?
@ -72,7 +104,15 @@ class HttpServerUtils private constructor() {
MMKVUtils.put(SP_SERVER_CONFIG, serverConfig)
}
//客户端签名密钥
//服务端安全设置
@JvmStatic
var clientSafetyMeasures: Int
get() = MMKVUtils.getInt(SP_CLIENT_SAFETY_MEASURES, if (TextUtils.isEmpty(clientSignKey)) 0 else 1)
set(clientSafetyMeasures) {
MMKVUtils.put(SP_CLIENT_SAFETY_MEASURES, clientSafetyMeasures)
}
//客户端签名密钥/RSA公钥
@JvmStatic
var clientSignKey: String?
get() = MMKVUtils.getString(SP_CLIENT_SIGN_KEY, "")
@ -164,8 +204,9 @@ class HttpServerUtils private constructor() {
val timestamp = System.currentTimeMillis()
val diffTime = kotlin.math.abs(timestamp - req.timestamp)
if (diffTime > 3600000L) {
throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, diffTime))
val tolerance = timeTolerance * 1000L
if (diffTime > tolerance) {
throw HttpException(500, String.format(getString(R.string.timestamp_verify_failed), timestamp, timeTolerance, diffTime))
}
val sign = calcSign(req.timestamp.toString(), signSecret.toString())
@ -306,7 +347,7 @@ class HttpServerUtils private constructor() {
if (output != null) {
resp["data"] = output
}
if (!TextUtils.isEmpty(serverSignKey)) {
if (safetyMeasures == 1) {
resp["sign"] = calcSign(timestamp.toString(), serverSignKey.toString())
}
}

@ -0,0 +1,210 @@
package com.idormy.sms.forwarder.utils
import java.io.ByteArrayOutputStream
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.PublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
/**
* 非对称加密RSA加密和解密
*/
@Suppress("unused")
object RSACrypt {
private const val transformation = "RSA"
private const val ENCRYPT_MAX_SIZE = 245
private const val DECRYPT_MAX_SIZE = 256
/**
* 私钥加密
* @param input 原文
* @param privateKey 私钥
*/
fun encryptByPrivateKey(input: String, privateKey: PrivateKey): String {
//创建cipher对象
val cipher = Cipher.getInstance(transformation)
//初始化cipher
cipher.init(Cipher.ENCRYPT_MODE, privateKey)
//****非对称加密****
val byteArray = input.toByteArray()
//分段加密
var temp: ByteArray?
var offset = 0 //当前偏移的位置
val outputStream = ByteArrayOutputStream()
//拆分input
while (byteArray.size - offset > 0) {
//每次最大加密245个字节
if (byteArray.size - offset >= ENCRYPT_MAX_SIZE) {
//剩余部分大于245
//加密完整245
temp = cipher.doFinal(byteArray, offset, ENCRYPT_MAX_SIZE)
//重新计算偏移位置
offset += ENCRYPT_MAX_SIZE
} else {
//加密最后一块
temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
//重新计算偏移位置
offset = byteArray.size
}
//存储到临时的缓冲区
outputStream.write(temp)
}
outputStream.close()
return Base64.encode(outputStream.toByteArray())
}
/**
* 公钥加密
* @param input 原文
* @param publicKey 公钥
*/
fun encryptByPublicKey(input: String, publicKey: PublicKey): String {
//创建cipher对象
val cipher = Cipher.getInstance(transformation)
//初始化cipher
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
//****非对称加密****
val byteArray = input.toByteArray()
var temp: ByteArray?
var offset = 0 //当前偏移的位置
val outputStream = ByteArrayOutputStream()
//拆分input
while (byteArray.size - offset > 0) {
//每次最大加密117个字节
if (byteArray.size - offset >= ENCRYPT_MAX_SIZE) {
//剩余部分大于117
//加密完整117
temp = cipher.doFinal(byteArray, offset, ENCRYPT_MAX_SIZE)
//重新计算偏移位置
offset += ENCRYPT_MAX_SIZE
} else {
//加密最后一块
temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
//重新计算偏移位置
offset = byteArray.size
}
//存储到临时的缓冲区
outputStream.write(temp)
}
outputStream.close()
return Base64.encode(outputStream.toByteArray())
}
/**
* 私钥解密
* @param input 秘文
* @param privateKey 私钥
*/
fun decryptByPrivateKey(input: String, privateKey: PrivateKey): String {
//创建cipher对象
val cipher = Cipher.getInstance(transformation)
//初始化cipher
cipher.init(Cipher.DECRYPT_MODE, privateKey)
//****非对称加密****
val byteArray = Base64.decode(input)
//分段解密
var temp: ByteArray?
var offset = 0 //当前偏移的位置
val outputStream = ByteArrayOutputStream()
//拆分input
while (byteArray.size - offset > 0) {
//每次最大解密256个字节
if (byteArray.size - offset >= DECRYPT_MAX_SIZE) {
temp = cipher.doFinal(byteArray, offset, DECRYPT_MAX_SIZE)
//重新计算偏移位置
offset += DECRYPT_MAX_SIZE
} else {
//加密最后一块
temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
//重新计算偏移位置
offset = byteArray.size
}
//存储到临时的缓冲区
outputStream.write(temp)
}
outputStream.close()
return String(outputStream.toByteArray())
}
/**
* 公钥解密
* @param input 秘文
* @param publicKey 公钥
*/
fun decryptByPublicKey(input: String, publicKey: PublicKey): String {
//创建cipher对象
val cipher = Cipher.getInstance(transformation)
//初始化cipher
cipher.init(Cipher.DECRYPT_MODE, publicKey)
//****非对称加密****
val byteArray = Base64.decode(input)
//分段解密
var temp: ByteArray?
var offset = 0 //当前偏移的位置
val outputStream = ByteArrayOutputStream()
//拆分input
while (byteArray.size - offset > 0) {
//每次最大解密256个字节
if (byteArray.size - offset >= DECRYPT_MAX_SIZE) {
temp = cipher.doFinal(byteArray, offset, DECRYPT_MAX_SIZE)
//重新计算偏移位置
offset += DECRYPT_MAX_SIZE
} else {
//加密最后一块
temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
//重新计算偏移位置
offset = byteArray.size
}
//存储到临时的缓冲区
outputStream.write(temp)
}
outputStream.close()
return String(outputStream.toByteArray())
}
fun getPrivateKey(privateKeyStr: String): PrivateKey {
//字符串转成秘钥对对象
val generator = KeyFactory.getInstance("RSA")
return generator.generatePrivate(PKCS8EncodedKeySpec(Base64.decode(privateKeyStr)))
}
fun getPublicKey(publicKeyStr: String): PublicKey {
//字符串转成秘钥对对象
val kf = KeyFactory.getInstance("RSA")
return kf.generatePublic(X509EncodedKeySpec(Base64.decode(publicKeyStr)))
}
}

@ -61,20 +61,55 @@
android:layout_weight="1"
android:hint="@string/service_address_hint" />
<com.xuexiang.xui.widget.button.ButtonView
android:id="@+id/btn_server_history"
style="@style/ButtonView.Gray"
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:minWidth="70dp"
android:padding="5dp"
android:text="@string/server_history" />
android:text="@string/safety_measures"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_safety_measures"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_safety_measures_none"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/safety_measures_none"
android:textSize="11sp" />
<RadioButton
android:id="@+id/rb_safety_measures_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/safety_measures_sign"
android:textSize="11sp" />
<RadioButton
android:id="@+id/rb_safety_measures_rsa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/safety_measures_encrypt"
android:textSize="11sp" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_sign_key"
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -82,24 +117,52 @@
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/tv_sign_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sign_key"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.ClearEditText
<EditText
android:id="@+id/et_sign_key"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1" />
android:layout_weight="1"
android:gravity="top"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:maxLines="5"
android:minLines="1"
android:scrollbars="vertical"
android:textSize="10sp"
tools:ignore="LabelFor,SmallSp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="10dp"
android:gravity="center_horizontal">
<com.xuexiang.xui.widget.button.ButtonView
android:id="@+id/btn_server_history"
style="@style/ButtonView.Gray"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minWidth="70dp"
android:padding="5dp"
android:text="@string/server_history" />
<com.xuexiang.xui.widget.button.ButtonView
android:id="@+id/btn_server_test"
style="@style/ButtonView.Green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginStart="15dp"
android:gravity="center"
android:minWidth="70dp"
android:padding="5dp"

@ -87,19 +87,11 @@
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_server_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/http_server_stopped"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/iv_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:gravity="center"
android:padding="3dp"
android:text="@string/copy"
@ -111,6 +103,14 @@
app:sb_shape_type="rectangle"
tools:ignore="PrivateResource,SmallSp" />
<TextView
android:id="@+id/tv_server_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/http_server_stopped"
android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout>
</LinearLayout>
@ -134,10 +134,57 @@
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/safety_measures"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_safety_measures"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="3dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_safety_measures_none"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/safety_measures_none"
android:textSize="11sp" />
<RadioButton
android:id="@+id/rb_safety_measures_sign"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/safety_measures_sign"
android:textSize="11sp" />
<RadioButton
android:id="@+id/rb_safety_measures_rsa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/safety_measures_encrypt"
android:textSize="11sp" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_sign_key"
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="15dp"
android:visibility="gone"
tools:ignore="RtlSymmetry">
<TextView
@ -172,33 +219,142 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layout_time_tolerance"
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="15dp"
android:visibility="gone"
tools:ignore="RtlSymmetry">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_tolerance"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_tolerance_tips"
android:textSize="9sp"
tools:ignore="SmallSp" />
</LinearLayout>
<com.xuexiang.xui.widget.picker.XSeekBar
android:id="@+id/xsb_time_tolerance"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
app:xsb_max="600"
app:xsb_min="1" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/web_client"
android:layout_marginStart="5dp"
android:text="@string/seconds"
android:textSize="12sp"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.ClearEditText
android:id="@+id/et_web_path"
</LinearLayout>
<LinearLayout
android:id="@+id/layout_private_key"
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="15dp"
android:visibility="gone"
tools:ignore="RtlSymmetry">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/private_key"
android:textStyle="bold" />
<EditText
android:id="@+id/et_private_key"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1" />
android:layout_weight="1"
android:gravity="top"
android:hint="@string/private_key_tips"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:maxLines="5"
android:minLines="2"
android:scrollbars="vertical"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_path_picker"
android:id="@+id/btn_generate_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/select_directory"
android:text="@string/generate_key"
android:textColor="@color/white"
android:textSize="10sp"
app:sb_color_unpressed="@color/colorPrimary"
app:sb_ripple_color="@color/white"
app:sb_ripple_duration="500"
app:sb_shape_type="rectangle"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_public_key"
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="15dp"
android:visibility="gone"
tools:ignore="RtlSymmetry">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/public_key"
android:textStyle="bold" />
<EditText
android:id="@+id/et_public_key"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:gravity="top"
android:hint="@string/public_key_tips"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:maxLines="5"
android:minLines="2"
android:scrollbars="vertical"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_copy_public_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/copy_public_key"
android:textColor="@color/white"
android:textSize="10sp"
app:sb_color_unpressed="@color/colorPrimary"
@ -233,6 +389,47 @@
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="15dp"
tools:ignore="RtlSymmetry">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/web_client"
android:textStyle="bold" />
<com.xuexiang.xui.widget.edittext.ClearEditText
android:id="@+id/et_web_path"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:hint="@string/web_path_tips"
android:textSize="10sp"
tools:ignore="SmallSp" />
<com.xuexiang.xui.widget.button.shadowbutton.RippleShadowShadowButton
android:id="@+id/btn_path_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:gravity="center"
android:padding="5dp"
android:text="@string/select_directory"
android:textColor="@color/white"
android:textSize="10sp"
app:sb_color_unpressed="@color/colorPrimary"
app:sb_ripple_color="@color/white"
app:sb_ripple_duration="500"
app:sb_shape_type="rectangle"
tools:ignore="SmallSp" />
</LinearLayout>
<LinearLayout
style="@style/settingBarStyle"
android:layout_width="match_parent"
@ -459,7 +656,7 @@
android:layout_height="wrap_content"
android:text="@string/api_wol"
android:textStyle="bold"
tools:ignore="RelativeOverlap" />
tools:ignore="RelativeOverlap,TooManyViews" />
<TextView
android:layout_width="match_parent"

@ -778,6 +778,8 @@
<string name="server_settings">Server Settings</string>
<string name="server_settings_tips">It is recommended to enable signature settings, click "Random" to generate and copy to clipboard</string>
<string name="sign_key">Sign Key</string>
<string name="rsa_key_tips">Key pair generated and copied to clipboard</string>
<string name="rsa_key_tips2">Copied to clipboard</string>
<string name="sign_key_tips">Key generated and copied to clipboard</string>
<string name="copy" tools:ignore="PrivateResource">Copy</string>
<string name="random">Random</string>
@ -867,7 +869,7 @@
<string name="sign_verify_failed">Sign verify failed</string>
<string name="version_code_required">version_code required</string>
<string name="inconsistent_version">The app versions of the client and server are inconsistent</string>
<string name="timestamp_verify_failed" formatted="false">The timestamp verification failed, and the difference with the server time (%s) cannot exceed 1 hour (diffTime=%s)</string>
<string name="timestamp_verify_failed" formatted="false">The timestamp verification failed, and the difference with the server time (%s) cannot exceed %s sec. (diffTime=%s)</string>
<string name="main_title">Main title</string>
<string name="subtitle">Subtitle</string>
@ -921,5 +923,18 @@
<string name="user_id">User ID</string>
<string name="auto_clean_logs">Auto delete logs N days ago</string>
<string name="auto_clean_logs_tips">0=disabled, scan when battery change</string>
<string name="day"></string>
<string name="day">Day</string>
<string name="safety_measures">Safety Measures</string>
<string name="safety_measures_none">None</string>
<string name="safety_measures_sign">Sign</string>
<string name="safety_measures_encrypt">Encrypt</string>
<string name="web_path_tips">See Github Wiki, download to Download directory</string>
<string name="time_tolerance">Time Tolerance</string>
<string name="time_tolerance_tips">Minimize time tolerance to avoid request replay attacks</string>
<string name="private_key">Private Key</string>
<string name="private_key_tips">Private key is used on the server: the private key of the server response message is encrypted, and the client public key is decrypted</string>
<string name="generate_key">Generate</string>
<string name="public_key">Public Key</string>
<string name="public_key_tips">Public key is used on the client: client request message public key encryption, server private key decryption</string>
<string name="copy_public_key">Copy</string>
</resources>

@ -781,6 +781,8 @@
<string name="copy" tools:ignore="PrivateResource">复制</string>
<string name="random">随机生成</string>
<string name="sign_key">签名密钥</string>
<string name="rsa_key_tips">已生成公私钥对,并复制公钥到剪贴板</string>
<string name="rsa_key_tips2">已复制公钥到剪贴板</string>
<string name="sign_key_tips">已生成密钥,并复制到剪贴板</string>
<string name="enable_function">启用功能</string>
<string name="enable_function_tips">按需选择您要启用远程控制的功能</string>
@ -832,9 +834,9 @@
<string name="battery_plugged">充电器:%s</string>
<string name="server_history">历史记录</string>
<string name="server_test">测试接口</string>
<string name="server_test">登录服务</string>
<string name="invalid_service_address">无效的服务地址!\n格式http://127.0.0.1:5000 或 https://smsf.demo.com</string>
<string name="click_test_button_first">请先点击【测试接口】按钮,获取服务端已启用的功能列表</string>
<string name="click_test_button_first">请先点击【登录服务】按钮,获取服务端已启用的功能列表</string>
<string name="disabled_on_the_server">服务端禁用此功能</string>
<string name="frpc_failed_to_run">Frpc运行失败</string>
<string name="successfully_deleted">删除成功</string>
@ -868,7 +870,7 @@
<string name="sign_verify_failed">签名校验失败</string>
<string name="version_code_required">version_code节点必传</string>
<string name="inconsistent_version">客户端与服务端的App版本不一致</string>
<string name="timestamp_verify_failed" formatted="false">timestamp校验失败与服务器时间(%s)误差不能超过1小时(diffTime=%s)</string>
<string name="timestamp_verify_failed" formatted="false">timestamp校验失败与服务器时间(%s)误差不能超过%s秒(diffTime=%s)</string>
<string name="main_title">主标题</string>
<string name="subtitle">副标题</string>
@ -923,4 +925,17 @@
<string name="auto_clean_logs">自动删除N天前的转发记录</string>
<string name="auto_clean_logs_tips">0=禁用,触发机制:每次电量变化时扫描</string>
<string name="day"></string>
<string name="safety_measures">安全措施</string>
<string name="safety_measures_none">不需要</string>
<string name="safety_measures_sign">校验签名</string>
<string name="safety_measures_encrypt">加密传输</string>
<string name="web_path_tips">参见 Github Wiki下载到 Download 目录</string>
<string name="time_tolerance">客户端与服务端时间容差</string>
<string name="time_tolerance_tips">尽量缩短时间容差,避免请求重放攻击</string>
<string name="private_key">RSA私钥</string>
<string name="private_key_tips">RSA私钥用在服务端服务端应答报文私钥加密客户端公钥解密</string>
<string name="generate_key">生成密钥</string>
<string name="public_key">RSA公钥</string>
<string name="public_key_tips">RSA公钥用在客户端客户端请求报文公钥加密服务端私钥解密</string>
<string name="copy_public_key">复制公钥</string>
</resources>

Loading…
Cancel
Save