Skip to content

Commit

Permalink
Show warning when request contains invalid JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
Waboodoo committed May 19, 2024
1 parent 7edf720 commit fb0dc77
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ sealed class ExecuteDialogState<T : Any> {
val title: Localizable? = null,
) : ExecuteDialogState<Unit>()

@Stable
data class Warning(
val message: Localizable,
val title: Localizable? = null,
val onHidden: (Boolean) -> Unit,
) : ExecuteDialogState<Unit>()

@Stable
data class GenericConfirm(
val message: Localizable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import ch.rmy.android.http_shortcuts.R
import ch.rmy.android.http_shortcuts.components.ColorPickerDialog
import ch.rmy.android.http_shortcuts.components.ConfirmDialog
import ch.rmy.android.http_shortcuts.components.FontSize
import ch.rmy.android.http_shortcuts.components.HideableDialog
import ch.rmy.android.http_shortcuts.components.HtmlRichText
import ch.rmy.android.http_shortcuts.components.MessageDialog
import ch.rmy.android.http_shortcuts.components.MultiSelectDialog
Expand Down Expand Up @@ -97,6 +98,14 @@ private fun ExecuteDialog(
onDismissRequest = onDismissed,
)
}
is ExecuteDialogState.Warning -> {
HideableDialog(
title = dialogState.title?.localize(),
message = dialogState.message.localize(),
onHidden = dialogState.onHidden,
onDismissed = onDismissed,
)
}
is ExecuteDialogState.GenericConfirm -> {
ConfirmDialog(
title = dialogState.title?.localize(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import ch.rmy.android.http_shortcuts.activities.execute.usecases.OpenInBrowserUs
import ch.rmy.android.http_shortcuts.activities.execute.usecases.RequestBiometricConfirmationUseCase
import ch.rmy.android.http_shortcuts.activities.execute.usecases.RequestSimpleConfirmationUseCase
import ch.rmy.android.http_shortcuts.activities.execute.usecases.ShowResultDialogUseCase
import ch.rmy.android.http_shortcuts.activities.execute.usecases.ValidateRequestDataUseCase
import ch.rmy.android.http_shortcuts.activities.response.DisplayResponseActivity
import ch.rmy.android.http_shortcuts.activities.response.models.ResponseData
import ch.rmy.android.http_shortcuts.data.domains.app.AppRepository
Expand Down Expand Up @@ -128,6 +129,7 @@ class Execution(
private val executionSchedulerStarter: ExecutionSchedulerWorker.Starter = entryPoint.executionSchedulerStarter()
private val sessionMonitor: SessionMonitor = entryPoint.sessionMonitor()
private val navigationArgStore: NavigationArgStore = entryPoint.navigationArgStore()
private val validateRequestData: ValidateRequestDataUseCase = entryPoint.validateRequestData()

private lateinit var globalCode: String
private lateinit var category: Category
Expand Down Expand Up @@ -340,6 +342,9 @@ class Execution(
fileUploadResult = fileUploadResult,
useCookieJar = shortcut.acceptCookies,
certificatePins = certificatePins,
validateRequestData = { requestData ->
validateRequestData(dialogHandle, shortcut, requestData)
},
)
} catch (e: UnknownHostException) {
if (shouldReschedule(e)) {
Expand Down Expand Up @@ -749,6 +754,7 @@ class Execution(
fun executionSchedulerStarter(): ExecutionSchedulerWorker.Starter
fun sessionMonitor(): SessionMonitor
fun navigationArgStore(): NavigationArgStore
fun validateRequestData(): ValidateRequestDataUseCase
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ch.rmy.android.http_shortcuts.activities.execute.usecases

import ch.rmy.android.framework.utils.localization.StringResLocalizable
import ch.rmy.android.http_shortcuts.R
import ch.rmy.android.http_shortcuts.activities.execute.DialogHandle
import ch.rmy.android.http_shortcuts.activities.execute.ExecuteDialogState
import ch.rmy.android.http_shortcuts.data.models.Shortcut
import ch.rmy.android.http_shortcuts.exceptions.DialogCancellationException
import ch.rmy.android.http_shortcuts.http.RequestData
import ch.rmy.android.http_shortcuts.utils.GsonUtil
import ch.rmy.android.http_shortcuts.utils.Settings
import com.google.gson.JsonParseException
import javax.inject.Inject

class ValidateRequestDataUseCase
@Inject
constructor(
private val settings: Settings,
) {
suspend operator fun invoke(dialogHandle: DialogHandle, shortcut: Shortcut, requestData: RequestData) {
if (!shortcut.usesCustomBody()) {
return
}
if (requestData.contentType?.startsWith("application/json", ignoreCase = true) != true) {
return
}
if (settings.isMalformedJsonWarningPermanentlyHidden) {
return
}
try {
GsonUtil.prettyPrintOrThrow(requestData.body)
} catch (e: JsonParseException) {
GsonUtil.extractErrorMessage(e)
?.let { errorMessage ->
try {
dialogHandle.showDialog(
ExecuteDialogState.Warning(
title = StringResLocalizable(R.string.warning_dialog_title),
message = StringResLocalizable(R.string.warning_message_malformed_json, errorMessage),
onHidden = {
settings.isMalformedJsonWarningPermanentlyHidden = it
},
)
)
} catch (e: DialogCancellationException) {
// Continue as normal
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
package ch.rmy.android.http_shortcuts.activities.main

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.window.DialogProperties
import ch.rmy.android.http_shortcuts.R
import ch.rmy.android.http_shortcuts.activities.main.models.RecoveryInfo
import ch.rmy.android.http_shortcuts.components.ChangeLogDialog
import ch.rmy.android.http_shortcuts.components.ChangeTitleDialog
import ch.rmy.android.http_shortcuts.components.ConfirmDialog
import ch.rmy.android.http_shortcuts.components.FontSize
import ch.rmy.android.http_shortcuts.components.HideableDialog
import ch.rmy.android.http_shortcuts.components.ProgressDialog
import ch.rmy.android.http_shortcuts.components.Spacing
import ch.rmy.android.http_shortcuts.components.TextInputDialog

@Composable
Expand Down Expand Up @@ -109,45 +94,10 @@ private fun NetworkRestrictionsWarningDialog(
onNetworkRestrictionsWarningHidden: (Boolean) -> Unit,
onDismissed: () -> Unit,
) {
var permanentlyHidden by remember {
mutableStateOf(false)
}
AlertDialog(
onDismissRequest = onDismissed,
text = {
Column(
verticalArrangement = Arrangement.spacedBy(Spacing.SMALL),
) {
Text(stringResource(R.string.warning_data_saver_battery_saver_enabled))
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
permanentlyHidden = !permanentlyHidden
onNetworkRestrictionsWarningHidden(permanentlyHidden)
}
.padding(Spacing.TINY),
horizontalArrangement = Arrangement.spacedBy(Spacing.SMALL, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = permanentlyHidden,
onCheckedChange = null,
)
Text(
stringResource(R.string.dialog_checkbox_do_not_show_again),
fontSize = FontSize.MEDIUM,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
},
confirmButton = {
TextButton(onClick = onDismissed) {
Text(stringResource(R.string.dialog_ok))
}
},
HideableDialog(
message = stringResource(R.string.warning_data_saver_battery_saver_enabled),
onHidden = onNetworkRestrictionsWarningHidden,
onDismissed = onDismissed,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ch.rmy.android.http_shortcuts.components

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import ch.rmy.android.http_shortcuts.R

@Composable
fun HideableDialog(
title: String? = null,
message: String,
onHidden: (Boolean) -> Unit,
onDismissed: () -> Unit,
) {
var permanentlyHidden by remember {
mutableStateOf(false)
}
AlertDialog(
onDismissRequest = onDismissed,
title = title?.let {
{
Text(it)
}
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(Spacing.MEDIUM),
) {
Text(message)
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
permanentlyHidden = !permanentlyHidden
onHidden(permanentlyHidden)
}
.padding(Spacing.TINY),
horizontalArrangement = Arrangement.spacedBy(Spacing.SMALL, Alignment.Start),
verticalAlignment = Alignment.CenterVertically,
) {
androidx.compose.material3.Checkbox(
checked = permanentlyHidden,
onCheckedChange = null,
)
Text(
stringResource(R.string.dialog_checkbox_do_not_show_again),
fontSize = FontSize.MEDIUM,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
},
confirmButton = {
TextButton(onClick = onDismissed) {
Text(stringResource(R.string.dialog_ok))
}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ constructor(
fileUploadResult: FileUploadManager.Result? = null,
useCookieJar: Boolean = false,
certificatePins: List<CertificatePinModel>,
validateRequestData: suspend (RequestData) -> Unit = {},
): ShortcutResponse =
withContext(Dispatchers.IO) {
val responseFileStorage = responseFileStorageFactory.create(sessionId, shortcut.responseHandling?.storeDirectory)
Expand All @@ -74,14 +75,26 @@ constructor(
username = Variables.rawPlaceholdersToResolvedValues(shortcut.username, variableValues),
password = Variables.rawPlaceholdersToResolvedValues(shortcut.password, variableValues),
authToken = Variables.rawPlaceholdersToResolvedValues(shortcut.authToken, variableValues),
body = Variables.rawPlaceholdersToResolvedValues(shortcut.bodyContent, variableValues),
body = if (shortcut.usesCustomBody()) Variables.rawPlaceholdersToResolvedValues(shortcut.bodyContent, variableValues) else "",
proxy = getProxyParams(shortcut, variableValues),
contentType = determineContentType(shortcut),
)

validateRequestData(requestData)

val cookieJar = if (useCookieJar) cookieManager.getCookieJar() else null

try {
makeRequest(context, shortcut, variableValues, requestData, responseFileStorage, fileUploadResult, cookieJar, certificatePins)
makeRequest(
context = context,
shortcut = shortcut,
variablesValues = variableValues,
requestData = requestData,
responseFileStorage = responseFileStorage,
fileUploadResult = fileUploadResult,
cookieJar = cookieJar,
certificatePins = certificatePins,
)
} catch (e: UnknownHostException) {
ensureActive()
if (ServiceDiscoveryHelper.isDiscoverable(requestData.uri)) {
Expand Down Expand Up @@ -170,18 +183,18 @@ constructor(
}
.userAgent(UserAgentProvider.getUserAgent(context))
.runIf(shortcut.usesCustomBody()) {
contentType(determineContentType(shortcut))
contentType(requestData.contentType)
.body(requestData.body)
}
.runIf(shortcut.usesGenericFileBody()) {
val file = fileUploadResult?.getFile(0)
runIfNotNull(file) {
contentType(determineContentType(shortcut) ?: it.mimeType)
contentType(requestData.contentType ?: it.mimeType)
.body(contentResolver.openInputStream(it.data)!!, length = it.fileSize)
}
}
.runIf(shortcut.usesRequestParameters()) {
contentType(determineContentType(shortcut))
contentType(requestData.contentType)
.run {
attachParameters(this, shortcut, variablesValues, fileUploadResult)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data class RequestData(
val authToken: String,
val body: String,
val proxy: ProxyParams?,
val contentType: String?,
) {

val uri
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ constructor(
get() = getBoolean(KEY_EXTERNAL_URL_WARNING_PERMANENTLY_HIDDEN)
set(hidden) = putBoolean(KEY_EXTERNAL_URL_WARNING_PERMANENTLY_HIDDEN, hidden)

var isMalformedJsonWarningPermanentlyHidden: Boolean
get() = getBoolean(KEY_MALFORMED_JSON_WARNING_PERMANENTLY_HIDDEN)
set(hidden) = putBoolean(KEY_MALFORMED_JSON_WARNING_PERMANENTLY_HIDDEN, hidden)

var changeLogLastVersion: String?
get() = getString(KEY_CHANGE_LOG_LAST_VERSION)
set(version) = putString(KEY_CHANGE_LOG_LAST_VERSION, version)
Expand Down Expand Up @@ -138,5 +142,6 @@ constructor(
private const val KEY_EXPERIMENTAL_EXECUTION_MODE = "experimental_execution_mode"
private const val KEY_COLOR_THEME = "color_theme"
private const val KEY_HISTORY_USE_RELATIVE_TIMES = "history_relative_times"
private const val KEY_MALFORMED_JSON_WARNING_PERMANENTLY_HIDDEN = "malformed_json_warning_permanently_hidden"
}
}
4 changes: 4 additions & 0 deletions HTTPShortcuts/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1609,4 +1609,8 @@
<string name="title_response_display">Response Display</string>
<!-- Title of screen that lets the user configure how their custom message is displayed -->
<string name="title_message_display">Message Display</string>
<!-- Title of a generic warning dialog. The dialog may be used in various situations. -->
<string name="warning_dialog_title">Warning</string>
<!-- Message of warning dialog that shows when a request contains malformed JSON in its body -->
<string name="warning_message_malformed_json">Your request body contains invalid JSON, which will likely cause the request to fail.\n\n%s</string>
</resources>

0 comments on commit fb0dc77

Please sign in to comment.