diff --git a/app/src/main/java/io/heckel/ntfy/ui/CustomHeaderFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/CustomHeaderFragment.kt index 68ce533f..b32297d6 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/CustomHeaderFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/CustomHeaderFragment.kt @@ -11,6 +11,7 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout import io.heckel.ntfy.R import io.heckel.ntfy.db.CustomHeader import io.heckel.ntfy.util.AfterChangedTextWatcher @@ -25,6 +26,7 @@ class CustomHeaderFragment : DialogFragment() { private lateinit var baseUrlView: TextInputEditText private lateinit var headerNameView: TextInputEditText private lateinit var headerValueView: TextInputEditText + private lateinit var headerNameLayout: TextInputLayout private lateinit var positiveButton: Button interface CustomHeaderDialogListener { @@ -60,6 +62,7 @@ class CustomHeaderFragment : DialogFragment() { baseUrlView = view.findViewById(R.id.custom_header_dialog_base_url) headerNameView = view.findViewById(R.id.custom_header_dialog_name) headerValueView = view.findViewById(R.id.custom_header_dialog_value) + headerNameLayout = view.findViewById(R.id.custom_header_dialog_name_layout) var title: String if (header == null) { @@ -155,17 +158,32 @@ class CustomHeaderFragment : DialogFragment() { val headerName = headerNameView.text?.toString()?.trim() ?: "" val headerValue = headerValueView.text?.toString()?.trim() ?: "" + // Clear previous errors + headerNameLayout.error = null + + // Validate header name + var isValid = true + if (headerName.isNotEmpty()) { + if (!validateHeaderName(headerName)) { + headerNameLayout.error = getString(R.string.custom_headers_invalid_name) + isValid = false + } else if (isReservedHeader(headerName)) { + headerNameLayout.error = getString(R.string.custom_headers_reserved_name) + isValid = false + } + } + if (header == null) { // New header: baseUrl, name, and value required positiveButton.isEnabled = validUrl(baseUrl) && headerName.isNotEmpty() - && validateHeaderName(headerName) && headerValue.isNotEmpty() + && isValid } else { // Editing header: name and value required positiveButton.isEnabled = headerName.isNotEmpty() - && validateHeaderName(headerName) && headerValue.isNotEmpty() + && isValid } } @@ -178,6 +196,21 @@ class CustomHeaderFragment : DialogFragment() { return regex.matches(name) } + private fun isReservedHeader(name: String): Boolean { + // These headers are already set by ntfy and cannot be overridden + val nameLower = name.lowercase() + val reservedHeaders = setOf( + "user-agent", + "authorization", + "host", + "connection", + "upgrade", + "accept-encoding" + ) + // Also block all WebSocket-related headers + return reservedHeaders.contains(nameLower) || nameLower.startsWith("sec-websocket-") + } + companion object { const val TAG = "NtfyCustomHeaderFragment" private const val BUNDLE_BASE_URL = "baseUrl" diff --git a/app/src/main/res/layout/fragment_custom_header_dialog.xml b/app/src/main/res/layout/fragment_custom_header_dialog.xml index 8b82662a..dff4696e 100644 --- a/app/src/main/res/layout/fragment_custom_header_dialog.xml +++ b/app/src/main/res/layout/fragment_custom_header_dialog.xml @@ -43,6 +43,7 @@ Löschen Ungültiger Header Ungültige Zeichen im Header-Namen + Dieser Header ist reserviert und wird von ntfy gesetzt Name (z.B. CF-Access-Client-Id) Wert Einen benutzerdefinierten HTTP-Header hinzufügen, der mit jeder Anfrage an den angegebenen Server gesendet wird. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ebb67d12..dce126a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -424,6 +424,7 @@ Delete Invalid Header Header name contains invalid characters + This header is reserved and set by ntfy Name (e.g. CF-Access-Client-Id) Value Add a custom HTTP header that will be sent with every request to the specified server.