mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
Stores: redact every store log line, engine lines included
StoresState.logLine is where every store line goes, the native engines' included; it now passes the text through StoreLog.redactLine before the Downloads log and logcat see it. Each URL keeps its scheme, host and a plain first path segment only - a GOG secure link carries its token in the path - with no query, fragment or userinfo; token-like values and Authorization / Cookie headers are blanked outside URLs as well.
This commit is contained in:
1 parent
cb2f7c0109
commit
8940b0ca32
4 files changed
+76
-1
No files matched your search
@@ -124,4 +124,41 @@ object StoreLog {
|
||||
"[url]"
|
||||
}
|
||||
}
|
||||
|
||||
private val URL_PATTERN = Regex("""[A-Za-z][A-Za-z0-9+.\-]*://[^\s"'<>]+""")
|
||||
private val SAFE_SEGMENT = Regex("""[A-Za-z0-9._\-]{1,40}""")
|
||||
private val SECRET = Regex("""(?i)(?<![A-Za-z0-9_])(__token__|f_token|hdnts|access_token|refresh_token|id_token|token)(["']?\s*[=:]\s*["']?)[^\s&;,"'}]+""")
|
||||
private val CODE = Regex("""(?i)(?<![A-Za-z0-9_])(code|exchange_code|authorizationCode)(["']?\s*=\s*["']?)[^\s&;,"'}]+""")
|
||||
private val AUTH_HEADER = Regex("""(?i)(authorization|cookie)(\s*[:=]\s*).*""")
|
||||
|
||||
/**
|
||||
* What any store line may say, for the Downloads log and logcat alike: every URL cut to
|
||||
* `scheme://host/first-segment/…` (the segment kept only when it is a plain name - a GOG secure
|
||||
* link carries its token in the path), no query, fragment or userinfo; and outside URLs, the
|
||||
* values of token-like keys and of Authorization / Cookie headers blanked.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun redactLine(text: String): String {
|
||||
var out = URL_PATTERN.replace(text) { m -> shortUrl(m.value) }
|
||||
out = SECRET.replace(out) { m -> m.groupValues[1] + m.groupValues[2] + "…" }
|
||||
out = CODE.replace(out) { m -> m.groupValues[1] + m.groupValues[2] + "…" }
|
||||
out = AUTH_HEADER.replace(out) { m -> m.groupValues[1] + m.groupValues[2] + "…" }
|
||||
return out
|
||||
}
|
||||
|
||||
private fun shortUrl(url: String): String {
|
||||
val schemeEnd = url.indexOf("://")
|
||||
val scheme = url.substring(0, schemeEnd)
|
||||
var rest = url.substring(schemeEnd + 3)
|
||||
rest = rest.substringBefore('#').substringBefore('?')
|
||||
val slash = rest.indexOf('/')
|
||||
val authority = (if (slash >= 0) rest.substring(0, slash) else rest).substringAfterLast('@')
|
||||
if (slash < 0) return "$scheme://$authority"
|
||||
val path = rest.substring(slash + 1)
|
||||
val first = path.substringBefore('/')
|
||||
val more = path.length > first.length
|
||||
if (first.isEmpty()) return "$scheme://$authority/"
|
||||
val shown = if (SAFE_SEGMENT.matches(first)) first else "…"
|
||||
return "$scheme://$authority/$shown" + if (more && shown != "…") "/…" else ""
|
||||
}
|
||||
}
|
||||
@@ -178,7 +178,9 @@ object StoresState {
|
||||
}
|
||||
|
||||
/** Appends to the engine log with a clock, keeping the last 80 lines. Any thread. */
|
||||
fun logLine(text: String) {
|
||||
fun logLine(raw: String) {
|
||||
// Every store line passes here, the engines' included: no token or full URL is kept.
|
||||
val text = StoreLog.redactLine(raw)
|
||||
val stamped = "${CLOCK.format(Date())} $text"
|
||||
Log.i(TAG, text)
|
||||
main.post {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.droiddeck.launcher.stores
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Test
|
||||
|
||||
/** Store lines reach the Downloads log and logcat; nothing in them may carry a token. */
|
||||
class StoreLogTest {
|
||||
@Test fun gogSecureLinkKeepsOnlyTheHost() {
|
||||
val line = "gog: window 32 in_flight 3 https://gog-cdn-fastly.gog.com/token=nva=1791500000~dirs=/content-system/v2/store/1207658924~token=0a1b2c3d4e5f/content-system/v2/store/1207658924/ab/cd/abcdef 12.1 MB/s"
|
||||
val out = StoreLog.redactLine(line)
|
||||
assertEquals("gog: window 32 in_flight 3 https://gog-cdn-fastly.gog.com/… 12.1 MB/s", out)
|
||||
assertFalse(out.contains("0a1b2c3d4e5f"))
|
||||
}
|
||||
|
||||
@Test fun epicAkamaiUrlLosesItsQueryAndPath() {
|
||||
val line = "epic: CDN https://epicgames-download1.akamaized.net/Builds/Org/o-abc/xyz/default/ChunksV4/12/ABCD.chunk?__token__=exp=1791500000~acl=/*~hmac=deadbeef&f_token=xyz done"
|
||||
val out = StoreLog.redactLine(line)
|
||||
assertEquals("epic: CDN https://epicgames-download1.akamaized.net/Builds/… done", out)
|
||||
}
|
||||
|
||||
@Test fun tokensOutsideUrlsAndHeadersAreBlanked() {
|
||||
val out = StoreLog.redactLine("refresh access_token=abc.def refresh_token: 'xyz' code=AUTH123 hdnts=exp~hmac __token__=q")
|
||||
assertEquals("refresh access_token=… refresh_token: '…' code=… hdnts=… __token__=…", out)
|
||||
assertEquals("Authorization: …", StoreLog.redactLine("Authorization: Bearer eyJhbGciOi"))
|
||||
assertEquals("""{"access_token":"…","expires_in":7200}""", StoreLog.redactLine("""{"access_token":"eyJ.abc","expires_in":7200}"""))
|
||||
// Ordinary numbers stay: "HTTP code 403" has no separator to read as a secret.
|
||||
assertEquals("HTTP code 403 from https://api.gog.com/products/…", StoreLog.redactLine("HTTP code 403 from https://user:[email protected]/products/1?expand=x"))
|
||||
}
|
||||
}
|
||||
@@ -135,6 +135,12 @@ without it and Setup says "Stores engine not built").
|
||||
when installed, Resume, the download percentage, the install size, or Free / price / discount.
|
||||
A thin bar over the art while downloading. Install, Resume, Uninstall, Get and Buy live on the
|
||||
game page; A on a card opens it.
|
||||
- **Store log lines are redacted at the one choke point** (`StoresState.logLine`, which the engines'
|
||||
lines reach too): every URL is cut to scheme, host and a plain first path segment (a GOG secure
|
||||
link's token sits in the path, so it goes), no query, fragment or userinfo; token-like values
|
||||
(`token`, `__token__`, `f_token`, `hdnts`, access/refresh/id tokens, `code=`) and Authorization /
|
||||
Cookie headers are blanked outside URLs too. `StoreLogTest` covers a GOG secure link and an Epic
|
||||
Akamai URL. The engine's own logcat lines (tag `EpicNative` etc.) are written on the native side.
|
||||
- **Stages named for what they do; both sizes; Cancel deletes.** Epic's check of the files already
|
||||
there is part of Manifest ("Checking 120/515"), never a 100% Verify before anything is fetched.
|
||||
Install has its own write rate and ETA from the bytes the assembler writes, and files n/N. The
|
||||
|
||||
Reference in new issue
Block a user