SHA256
Add real native onboarding/auth/dashboard/tasks/notes/shopping, origin-bound HTTPS and encrypted AndroidKeyStore session persistence. Archive the superseded browser renderer and tests. Verify 10 JVM tests, 6 native API-35 emulator tests, lintDebug/lintRelease and debug/unsigned release APK/AAB builds; the real upstream suite passes 7 tests and exports a nonblank native fixture screenshot. Tink host-tool update removes the old generated-protobuf warning. Full module/visual parity remains explicitly incomplete.
40 lines
1.7 KiB
Kotlin
40 lines
1.7 KiB
Kotlin
package community.yuvomi.mobile
|
|
|
|
import android.content.ContentResolver
|
|
import android.net.Uri
|
|
import java.net.URI
|
|
import javax.net.ssl.HttpsURLConnection
|
|
|
|
object FileTransfer {
|
|
/** Never forwards session cookies across a redirect, even to another HTTPS origin. */
|
|
fun save(server: ServerAddress, url: String, cookie: String?, resolver: ContentResolver, destination: Uri) {
|
|
require(server.owns(url))
|
|
val connection = URI(url).toURL().openConnection() as HttpsURLConnection
|
|
try {
|
|
connection.instanceFollowRedirects = false
|
|
connection.connectTimeout = 15_000
|
|
connection.readTimeout = 20_000
|
|
cookie?.let { connection.setRequestProperty("Cookie", it) }
|
|
require(connection.responseCode == 200) { "Download refused" }
|
|
require(connection.contentLengthLong <= 100L * 1024 * 1024) { "Download too large" }
|
|
connection.inputStream.use { input ->
|
|
requireNotNull(resolver.openOutputStream(destination, "w")).use { output ->
|
|
val buffer = ByteArray(8192)
|
|
var total = 0L
|
|
val deadline = System.nanoTime() + 120_000_000_000L
|
|
while (true) {
|
|
check(!Thread.currentThread().isInterrupted && System.nanoTime() < deadline)
|
|
val count = input.read(buffer)
|
|
if (count < 0) break
|
|
total += count
|
|
require(total <= 100L * 1024 * 1024)
|
|
output.write(buffer, 0, count)
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
connection.disconnect()
|
|
}
|
|
}
|
|
}
|