Skip to content

Instantly share code, notes, and snippets.

@jonasbark
Created January 3, 2019 10:54
Show Gist options
  • Save jonasbark/3a0054dcf0c8ac9a896a0d805574914c to your computer and use it in GitHub Desktop.
Save jonasbark/3a0054dcf0c8ac9a896a0d805574914c to your computer and use it in GitHub Desktop.
internal class MultiPartContent(val parts: List<Part>) : OutgoingContent.WriteChannelContent() {
//val uuid = "RANDOM234"
//val time = System.currentTimeMillis()
//val time = "123134234234"
//val boundary = "***ktor-$uuid-ktor-$time***"
val boundary = generateBoundary()
private fun generateBoundary(): String = buildString {
repeat(32) {
append(Random.nextInt().absoluteValue.toString(16))
}
}.take(70)
data class Part(val name: String, val size: Long, val filename: String? = null, val headers: Headers = Headers.Empty, val writer: suspend ByteWriteChannel.() -> Unit)
override suspend fun writeTo(channel: ByteWriteChannel) {
for (part in parts) {
channel.writeStringUtf8("--$boundary\r\n")
val partHeaders = Headers.build {
val fileNamePart = if (part.filename != null) "; filename=\"${part.filename}\"" else ""
append(HttpHeaders.ContentDisposition, "form-data; name=\"${part.name}\"$fileNamePart")
append(HttpHeaders.ContentLength, part.size.toString())
appendAll(part.headers)
}
for ((key, value) in partHeaders.flattenEntries()) {
channel.writeStringUtf8("$key: $value\r\n")
}
channel.writeStringUtf8("\r\n")
part.writer(channel)
channel.writeStringUtf8("\r\n")
}
channel.writeStringUtf8("--$boundary--\r\n")
}
override val contentType = ContentType.MultiPart.FormData
.withParameter("boundary", boundary)
override val contentLength: Long?
get() {
var length = 0L
for (part in parts) {
length += ("--$boundary\r\n").length
val partHeaders = Headers.build {
val fileNamePart = if (part.filename != null) "; filename=\"${part.filename}\"" else ""
append(HttpHeaders.ContentDisposition, "form-data; name=\"${part.name}\"$fileNamePart")
append(HttpHeaders.ContentLength, part.size.toString())
appendAll(part.headers)
}
for ((key, value) in partHeaders.flattenEntries()) {
length += ("$key: $value\r\n").length
}
length += ("\r\n").length
length += part.size
length += ("\r\n").length
}
length += ("--$boundary--\r\n").length
print("CONTENT LENGTH: $length")
return length
}
class Builder {
val parts = arrayListOf<Part>()
private fun add(part: Part) {
parts += part
}
private fun add(name: String, size: Long, filename: String? = null, contentType: ContentType? = null, headers: Headers = Headers.Empty, writer: suspend ByteWriteChannel.() -> Unit) {
val contentTypeHeaders: Headers = if (contentType != null) headersOf(HttpHeaders.ContentType, contentType.toString()) else headersOf()
add(Part(name, size, filename, headers + contentTypeHeaders, writer))
}
fun add(name: String, size: Long, text: String, contentType: ContentType? = null, filename: String? = null) {
add(name, size, filename, contentType) { writeStringUtf8(text) }
}
fun add(name: String, size: Long, data: ByteArray, contentType: ContentType? = ContentType.Application.OctetStream, filename: String? = null) {
add(name, size, filename, contentType) { writeFully(data) }
}
internal fun build(): MultiPartContent = MultiPartContent(parts.toList())
}
companion object {
fun build(callback: Builder.() -> Unit) = Builder().apply(callback).build()
}
}
private operator fun Headers.plus(other: Headers): Headers = when {
this.isEmpty() -> other
other.isEmpty() -> this
else -> Headers.build {
appendAll(this@plus)
appendAll(other)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment