1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
package org.meshbay.client.save
/** The pure part of saving: names, types and the binary frame. JVM-tested. */
object SaveNames {
private val BIDI = Regex("[\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069]")
/**
* main.js `save:begin`: the basename only, bidirectional controls replaced
* — "invoicefdp.exe" would otherwise be listed as "invoiceexe.pdf".
*/
fun sanitize(suggested: String?): String {
val base = (suggested ?: "").replace('\\', '/').substringAfterLast('/')
.replace(BIDI, "_").replace(Regex("[\\u0000-\\u001f]"), "_").trim()
return if (base.isEmpty() || base == "." || base == "..") "download" else base
}
/**
* downloads.js `OPENABLE`, entry for entry (test_android_downloads.py):
* what may be handed to another app to open, under a type chosen from the
* name — never one guessed from the bytes.
*/
val OPENABLE = mapOf(
"pdf" to "application/pdf",
"png" to "image/png", "jpg" to "image/jpeg", "jpeg" to "image/jpeg", "gif" to "image/gif",
"webp" to "image/webp", "avif" to "image/avif", "bmp" to "image/bmp",
"mp3" to "audio/mpeg", "m4a" to "audio/mp4", "aac" to "audio/aac", "ogg" to "audio/ogg",
"oga" to "audio/ogg", "opus" to "audio/ogg", "flac" to "audio/flac", "wav" to "audio/wav",
"mp4" to "video/mp4", "m4v" to "video/mp4", "webm" to "video/webm", "ogv" to "video/ogg",
"mov" to "video/quicktime",
"txt" to "text/plain", "log" to "text/plain", "md" to "text/plain", "csv" to "text/plain",
)
fun extension(name: String): String? = Regex("\\.([A-Za-z0-9]+)$").find(name)?.groupValues?.get(1)?.lowercase()
fun openableType(name: String): String? = extension(name)?.let { OPENABLE[it] }
/** The type a file is created under: openable ones by name, the rest opaque. */
fun storedType(name: String): String = openableType(name) ?: "application/octet-stream"
/** "name (n).ext", as main.js `freeName` — never an overwrite. */
fun numbered(name: String, n: Int): String {
val ext = extension(name)?.let { ".$it" } ?: ""
val stem = if (ext.isEmpty()) name else name.dropLast(ext.length)
return "$stem ($n)$ext"
}
}
/**
* A binary bridge message: one write, no JSON, no base64.
*
* "MBB1" | u32 request id | u16 channel | u16 reserved | u32 handle | bytes
*
* all big-endian. The reply is an ordinary JSON reply carrying the id.
*/
class BinaryFrame(val id: Long, val channel: Int, val handle: Long, val bytes: ByteArray, val offset: Int) {
val length get() = bytes.size - offset
companion object {
const val HEADER = 16
const val SAVE_WRITE = 1
const val CAST_PUSH = 2
const val CAST_FILE = 3
private val MAGIC = byteArrayOf('M'.code.toByte(), 'B'.code.toByte(), 'B'.code.toByte(), '1'.code.toByte())
private fun u32(b: ByteArray, at: Int) =
((b[at].toLong() and 0xff) shl 24) or ((b[at + 1].toLong() and 0xff) shl 16) or
((b[at + 2].toLong() and 0xff) shl 8) or (b[at + 3].toLong() and 0xff)
fun parse(b: ByteArray): BinaryFrame? {
if (b.size < HEADER || !(0 until 4).all { b[it] == MAGIC[it] }) return null
val channel = ((b[8].toInt() and 0xff) shl 8) or (b[9].toInt() and 0xff)
return BinaryFrame(u32(b, 4), channel, u32(b, 12), b, HEADER)
}
}
}
|