保存時にURLからファイル名を決める際のサニタイズの改善

This commit is contained in:
tateisu 2023-01-30 08:00:37 +09:00
parent 448472cbbd
commit 0955dff8db
3 changed files with 52 additions and 12 deletions

View File

@ -55,8 +55,8 @@ suspend fun <T : Throwable?> assertThrowsSuspend(
expected += "@" + Integer.toHexString(System.identityHashCode(expectedThrowable))
actual += "@" + Integer.toHexString(System.identityHashCode(actualThrowable))
}
val mismatchMessage = (buildPrefix(message)
+ format("unexpected exception type thrown;", expected, actual))
val mismatchMessage = (buildPrefix(message) +
format("unexpected exception type thrown;", expected, actual))
// The AssertionError(String, Throwable) ctor is only available on JDK7.
val assertionError = AssertionError(mismatchMessage)

View File

@ -49,6 +49,7 @@ import kotlinx.coroutines.yield
import okhttp3.Request
import java.io.ByteArrayInputStream
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.math.max
@ -675,13 +676,49 @@ class ActMediaViewer : AppCompatActivity(), View.OnClickListener {
download_history_list.addLast(DownloadHistory(now, url))
}
val fileName = (
url.mayUri()?.pathSegments?.findLast { !it.isNullOrBlank() }
?: url
.replaceFirst("https?://".asciiRegex(), "")
.replaceAll("[^.\\w\\d]+".asciiPattern(), "-")
)
.take(20)
/**
* Linuxはフォルダ中のファイルの名前の上限が255バイトと決まっている
* その上限に収まるように文字を切りたいがそれはUTF-8の区切りを考慮する必要がある
*/
fun shortenName(src: String, limitBytes: Int): String {
val bytes = src.encodeUTF8()
if (bytes.size <= limitBytes) return src
// 制限バイト数の終端の一つ先
var pos = limitBytes
while (pos >= 0) {
if (bytes[pos].toInt().and(0x80) == 0) {
// 現在位置がUTF-8の後続ではないなら、その手前までを返す
return String(bytes, 0, pos, StandardCharsets.UTF_8)
}
// 現在位置はUTF-8の文字の2バイト目以降なので、この手前で切ると文字が壊れる
--pos
}
// UTF-8表現がおかしい
return "media"
}
var fileName = url.mayUri()?.pathSegments?.findLast { !it.isNullOrBlank() }
?: url.replaceFirst("https?://".asciiRegex(), "")
// Windowsでファイル名に使えない文字を避ける
fileName = """[\\/|"<>?*:-]+""".toRegex().replace(fileName, "-")
// 末尾から20文字以内にある最初のドット
val extDotPos = fileName.indexOf('.', startIndex = max(0, fileName.length - 20))
val fileNameMaxBytes = 255
fileName = if (extDotPos == -1) {
// 拡張子なし
shortenName(fileName, fileNameMaxBytes)
} else {
// 拡張子の手前だけを短縮する
val extPart = fileName.substring(extDotPos)
val extPartBytes = extPart.encodeUTF8().size
val namePart = shortenName(
fileName.substring(0, extDotPos),
fileNameMaxBytes - extPartBytes
)
"$namePart$extPart"
}
val request = DownloadManager.Request(url.toUri())
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)

View File

@ -163,7 +163,8 @@ class ResponseBeforeRead(
throw SendException(
response = response,
request = request,
message = parseErrorResponse(ex.withCaption("readString failed."))
message = parseErrorResponse(ex.withCaption("readString failed.")),
cause = ex,
)
}
}
@ -219,7 +220,8 @@ class ResponseBeforeRead(
throw SendException(
response = response,
request = request,
message = parseErrorResponse(ex.withCaption("readString failed."))
message = parseErrorResponse(ex.withCaption("readString failed.")),
cause = ex,
)
}
}
@ -304,7 +306,7 @@ suspend fun ResponseWith<String?>.stringToJsonObject(): JsonObject =
else -> throw SendException(
response = response,
request = response.request,
message = parseErrorResponse("not a JSON object.")
message = parseErrorResponse("not a JSON object."),
)
}
}
@ -317,6 +319,7 @@ suspend fun ResponseWith<String?>.stringToJsonObject(): JsonObject =
response = response,
request = response.request,
message = ex.withCaption("readJsonObject failed. ($errorSuffix)"),
cause = ex,
)
}
}