11package klite
22
3+ import java.io.ByteArrayOutputStream
34import java.io.InputStream
5+ import java.util.*
46import kotlin.reflect.KType
5- import kotlin.text.Charsets.ISO_8859_1
67
78@Deprecated(" Use FormDataParser" )
89typealias MultipartFormDataParser = FormDataParser
@@ -12,29 +13,30 @@ class FormDataParser: BodyParser {
1213
1314 @Suppress(" UNCHECKED_CAST" )
1415 override fun <T : Any > parse (input : InputStream , type : KType ): T {
15- val reader = input.bufferedReader(ISO_8859_1 )
16- val boundary = reader.readLine()
16+ val boundary = input.readLine()!! .trimEnd()
1717 val result = mutableMapOf<String , Any ?>()
1818 var state = State ()
1919 while (true ) {
20- val line = reader .readLine() ? : break
20+ val line = input .readLine() ? : break
2121 if (state.readingHeaders) {
22- val lowerLine = line.lowercase()
22+ val lineStr = line.toString(MimeTypes .textCharset).trimEnd()
23+ val lowerLine = lineStr.lowercase()
2324 if (lowerLine.isEmpty()) state.readingHeaders = false
2425 else if (lowerLine.startsWith(" content-disposition:" )) {
25- val disposition = line .substring(" content-disposition:" .length).trim()
26+ val disposition = lineStr .substring(" content-disposition:" .length).trim()
2627 val params = disposition.split(' ;' ).associate(::keyValue)
2728 state.name = params[" name" ]
2829 state.fileName = params[" filename" ]
2930 }
3031 else if (lowerLine.startsWith(" content-type:" )) {
31- state.contentType = line .substring(" content-type:" .length).trim()
32+ state.contentType = lineStr .substring(" content-type:" .length).trim()
3233 }
3334 } else {
3435 if (line.startsWith(boundary)) {
3536 if (state.name != null ) result[state.name!! ] = state.fileName?.let {
36- FileUpload (it, state.contentType, state.content.removeSuffix(" \n " ).toString().byteInputStream(ISO_8859_1 ))
37- } ? : state.content.toString().trim()
37+ // TODO: remove last newline from content
38+ FileUpload (it, state.contentType, state.content.toByteArray().trimEnd().inputStream())
39+ } ? : state.content.toString(MimeTypes .textCharset).trim()
3840 state = State ()
3941 }
4042 else state.append(line)
@@ -43,11 +45,34 @@ class FormDataParser: BodyParser {
4345 return result as T
4446 }
4547
48+ private fun InputStream.readLine (): ByteArray? {
49+ val buf = ByteArrayOutputStream (128 )
50+ var b = read()
51+ while (b >= 0 ) {
52+ buf.write(b)
53+ if (b == ' \n ' .code) break
54+ b = read()
55+ }
56+ if (b == - 1 && buf.size() == 0 ) return null
57+ return buf.toByteArray()
58+ }
59+
60+ private fun ByteArray.trimEnd (): ByteArray {
61+ var newLen = size
62+ if (this [newLen - 1 ] == 10 .toByte()) newLen--
63+ if (this [newLen - 1 ] == 13 .toByte()) newLen--
64+ return copyOf(newLen)
65+ }
66+
67+ private fun ByteArray.startsWith (prefix : ByteArray ): Boolean {
68+ return prefix.size <= size && Arrays .equals(this , 0 , prefix.size, prefix, 0 , prefix.size)
69+ }
70+
4671 private fun keyValue (s : String ) = s.split(' =' , limit = 2 ).let { it[0 ].trim() to it.getOrNull(1 )?.trim(' "' ) }
4772
4873 private class State (var readingHeaders : Boolean = true , var name : String? = null , var fileName : String? = null , var contentType : String? = null ) {
49- val content = StringBuilder ( )
50- fun append (line : String ) = content.append (line).append( ' \n ' )
74+ val content = ByteArrayOutputStream ( 4096 )
75+ fun append (line : ByteArray ) = content.write (line)
5176 }
5277}
5378
0 commit comments