Move requestBuilder

This commit is contained in:
Philipp Heckel 2026-01-04 20:00:58 -05:00
parent 88dacc17da
commit 6a00d9221e
9 changed files with 40 additions and 54 deletions

View file

@ -1,10 +1,7 @@
package io.heckel.ntfy.msg
import android.content.Context
import android.os.Build
import com.google.gson.Gson
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.CustomHeader
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.User
@ -18,14 +15,11 @@ import io.heckel.ntfy.util.topicUrlJson
import io.heckel.ntfy.util.topicUrlJsonPoll
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Credentials
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.IOException
import java.net.URLEncoder
import java.nio.charset.StandardCharsets.UTF_8
import kotlin.random.Random
class ApiService(private val context: Context) {
@ -92,7 +86,7 @@ class ApiService(private val context: Context) {
url
}
val customHeaders = repository.getCustomHeaders(baseUrl)
val request = requestBuilder(urlWithQuery, user, customHeaders)
val request = HttpUtil.requestBuilder(urlWithQuery, user, customHeaders)
.put(body ?: message.toRequestBody())
.build()
Log.d(TAG, "Publishing to $request")
@ -126,7 +120,7 @@ class ApiService(private val context: Context) {
Log.d(TAG, "Polling topic $url")
val customHeaders = repository.getCustomHeaders(baseUrl)
val request = requestBuilder(url, user, customHeaders).build()
val request = HttpUtil.requestBuilder(url, user, customHeaders).build()
HttpUtil.defaultClient(context, baseUrl).newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw Exception("Unexpected response ${response.code} when polling topic $url")
@ -154,7 +148,7 @@ class ApiService(private val context: Context) {
val url = topicUrlJson(baseUrl, topics, sinceVal)
Log.d(TAG, "Opening subscription connection to $url")
val customHeaders = repository.getCustomHeaders(baseUrl)
val request = requestBuilder(url, user, customHeaders).build()
val request = HttpUtil.requestBuilder(url, user, customHeaders).build()
val call = HttpUtil.subscriberClient(context, baseUrl).newCall(request)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
@ -193,7 +187,7 @@ class ApiService(private val context: Context) {
}
val url = topicUrlAuth(baseUrl, topic)
val customHeaders = repository.getCustomHeaders(baseUrl)
val request = requestBuilder(url, user, customHeaders).build()
val request = HttpUtil.requestBuilder(url, user, customHeaders).build()
HttpUtil.defaultClient(context, baseUrl).newCall(request).execute().use { response ->
if (response.isSuccessful) {
return true
@ -217,7 +211,6 @@ class ApiService(private val context: Context) {
)
companion object {
val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})"
private const val TAG = "NtfyApiService"
// These constants have corresponding values in the server codebase!
@ -225,18 +218,5 @@ class ApiService(private val context: Context) {
const val EVENT_MESSAGE = "message"
const val EVENT_KEEPALIVE = "keepalive"
const val EVENT_POLL_REQUEST = "poll_request"
fun requestBuilder(url: String, user: User?, customHeaders: List<CustomHeader> = emptyList()): Request.Builder {
val builder = Request.Builder()
.url(url)
.addHeader("User-Agent", USER_AGENT)
if (user != null) {
builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8))
}
customHeaders.forEach { header ->
builder.addHeader(header.name, header.value)
}
return builder
}
}
}

View file

@ -24,7 +24,6 @@ import io.heckel.ntfy.util.HttpUtil
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.ensureSafeNewFile
import io.heckel.ntfy.util.extractBaseUrl
import okhttp3.Request
import okhttp3.Response
import java.io.File
import java.util.concurrent.TimeUnit
@ -63,12 +62,8 @@ class DownloadAttachmentWorker(private val context: Context, params: WorkerParam
Log.d(TAG, "Downloading attachment from ${attachment.url}")
try {
val request = Request.Builder()
.url(attachment.url)
.addHeader("User-Agent", ApiService.USER_AGENT)
.build()
val client = HttpUtil
.longCallClient(context, extractBaseUrl(attachment.url))
val request = HttpUtil.requestBuilder(attachment.url).build()
val client = HttpUtil.longCallClient(context, extractBaseUrl(attachment.url))
client.newCall(request).execute().use { response ->
Log.d(TAG, "Download: headers received: $response")
if (!response.isSuccessful) {

View file

@ -15,7 +15,6 @@ import io.heckel.ntfy.util.HttpUtil
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.extractBaseUrl
import io.heckel.ntfy.util.sha256
import okhttp3.Request
import okhttp3.Response
import java.io.File
import java.util.Date
@ -61,10 +60,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
private fun downloadIcon(iconFile: File) {
Log.d(TAG, "Downloading icon from ${icon.url}")
try {
val request = Request.Builder()
.url(icon.url)
.addHeader("User-Agent", ApiService.USER_AGENT)
.build()
val request = HttpUtil.requestBuilder(icon.url).build()
val client = HttpUtil.defaultClient(context, extractBaseUrl(icon.url))
client.newCall(request).execute().use { response ->
Log.d(TAG, "Headers received: $response, Content-Length: ${response.headers["Content-Length"]}")

View file

@ -17,7 +17,6 @@ import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_HTTP
import io.heckel.ntfy.util.HttpUtil
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.extractBaseUrl
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.Locale
@ -71,10 +70,8 @@ class UserActionWorker(private val context: Context, params: WorkerParameters) :
val method = action.method ?: DEFAULT_HTTP_ACTION_METHOD
val defaultBody = if (listOf("GET", "HEAD").contains(method)) null else ""
val body = action.body ?: defaultBody
val builder = Request.Builder()
.url(url)
val builder = HttpUtil.requestBuilder(url)
.method(method, body?.toRequestBody())
.addHeader("User-Agent", ApiService.USER_AGENT)
action.headers?.forEach { (key, value) ->
builder.addHeader(key, value)
}

View file

@ -9,7 +9,6 @@ import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.db.User
import io.heckel.ntfy.msg.ApiService.Companion.requestBuilder
import io.heckel.ntfy.msg.NotificationParser
import io.heckel.ntfy.util.HttpUtil
import io.heckel.ntfy.util.Log
@ -82,7 +81,7 @@ class WsConnection(
val sinceId = since.get()
val sinceVal = sinceId ?: "all"
val urlWithSince = topicUrlWs(baseUrl, topicsStr, sinceVal)
val request = requestBuilder(urlWithSince, user, customHeaders).build()
val request = HttpUtil.requestBuilder(urlWithSince, user, customHeaders).build()
Log.d(TAG, "$shortUrl (gid=$globalId): Opening $urlWithSince with listener ID $nextListenerId ...")
webSocket = client.newWebSocket(request, Listener(nextListenerId))
}

View file

@ -73,7 +73,7 @@ class CertificateSettingsFragment : BasePreferenceFragment(),
val pref = Preference(preferenceScreen.context)
pref.title = parseCommonName(cert.subjectX500Principal.name)
pref.summary = if (isValid(cert)) {
getString(R.string.settings_advanced_certificates_trusted_item_summary_not_expired, issuer, dateFormat.format(cert.notAfter))
getString(R.string.settings_advanced_certificates_trusted_item_summary_not_expired, dateFormat.format(cert.notAfter), issuer)
} else {
getString(R.string.settings_advanced_certificates_trusted_item_summary_expired, issuer)
}
@ -114,7 +114,7 @@ class CertificateSettingsFragment : BasePreferenceFragment(),
val issuer = parseCommonName(cert.issuerX500Principal.name)
pref.title = parseCommonName(cert.subjectX500Principal.name)
pref.summary = if (isValid(cert)) {
getString(R.string.settings_advanced_certificates_client_item_summary_not_expired, issuer, dateFormat.format(cert.notAfter), shortUrl(clientCert.baseUrl))
getString(R.string.settings_advanced_certificates_client_item_summary_not_expired, dateFormat.format(cert.notAfter), issuer, shortUrl(clientCert.baseUrl))
} else {
getString(R.string.settings_advanced_certificates_client_item_summary_expired, issuer, shortUrl(clientCert.baseUrl))
}

View file

@ -39,7 +39,6 @@ import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.text.SimpleDateFormat
import java.util.*
@ -769,12 +768,10 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
}
}
val gson = Gson()
val request = Request.Builder()
.url(EXPORT_LOGS_UPLOAD_URL)
val request = HttpUtil.requestBuilder(EXPORT_LOGS_UPLOAD_URL)
.put(log.toRequestBody())
.build()
val client = HttpUtil
.longCallClient(context, extractBaseUrl(EXPORT_LOGS_UPLOAD_URL))
val client = HttpUtil.longCallClient(context, extractBaseUrl(EXPORT_LOGS_UPLOAD_URL))
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {

View file

@ -1,14 +1,23 @@
package io.heckel.ntfy.util
import android.content.Context
import android.os.Build
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.CustomHeader
import io.heckel.ntfy.db.User
import okhttp3.Credentials
import okhttp3.OkHttpClient
import okhttp3.Request
import java.nio.charset.StandardCharsets.UTF_8
import java.util.concurrent.TimeUnit
/**
* Utility class for creating OkHttpClient instances with appropriate configurations.
* Utility class for creating OkHttpClient instances and Request builders.
* All clients are configured with SSL/TLS settings from CertUtil for custom certificate support.
*/
object HttpUtil {
val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})"
/**
* Client for regular API calls (auth, poll, etc.).
*/
@ -60,5 +69,18 @@ object HttpUtil {
.getInstance(context)
.withTLSConfig(OkHttpClient.Builder(), baseUrl)
}
fun requestBuilder(url: String, user: User? = null, customHeaders: List<CustomHeader> = emptyList()): Request.Builder {
val builder = Request.Builder()
.url(url)
.addHeader("User-Agent", USER_AGENT)
if (user != null) {
builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8))
}
customHeaders.forEach { header ->
builder.addHeader(header.name, header.value)
}
return builder
}
}

View file

@ -426,13 +426,13 @@
<string name="settings_advanced_certificates_title">Manage certificates</string>
<string name="settings_advanced_certificates_summary">Trust self-signed server certificates and manage client certificates for mTLS</string>
<string name="settings_advanced_certificates_trusted_header">Trusted server certificates</string>
<string name="settings_advanced_certificates_trusted_item_summary_not_expired">Issued by %1$s, expires %2$s</string>
<string name="settings_advanced_certificates_trusted_item_summary_expired">Issued by %1$s, expired</string>
<string name="settings_advanced_certificates_trusted_item_summary_not_expired">Expires %1$s, issued by %2$s</string>
<string name="settings_advanced_certificates_trusted_item_summary_expired">Expired, issued by %1$s, </string>
<string name="settings_advanced_certificates_trusted_add_title">Add a trusted certificate</string>
<string name="settings_advanced_certificates_trusted_add_summary">Import a server or CA certificate into the trust store (PEM). HTTPS and WebSocket connections will trust this certificate.</string>
<string name="settings_advanced_certificates_client_header">Client certificates (mTLS)</string>
<string name="settings_advanced_certificates_client_item_summary_not_expired">Issued by %1$s, expires %2$s, used by %3$s</string>
<string name="settings_advanced_certificates_client_item_summary_expired">Issued by %1$s, expired, used by %2$s</string>
<string name="settings_advanced_certificates_client_item_summary_not_expired">Expires %1$s, issued by %2$s, used by %3$s</string>
<string name="settings_advanced_certificates_client_item_summary_expired">Expired, issued by %1$s, used by %2$s</string>
<string name="settings_advanced_certificates_client_add_title">Add a client certificate</string>
<string name="settings_advanced_certificates_client_add_summary">Import certificate for mutual TLS authentication (PKCS#12). This certificate will be used when connecting to the server.</string>
<string name="settings_advanced_certificates_error_invalid_cert">Invalid certificate file</string>