Skip to content

Commit baef93b

Browse files
cortinicofacebook-github-bot
authored andcommitted
Migrate to Kotlin - MultipartStreamReader
Summary: This diff migrates the following file to Kotlin - MultipartStreamReader as part of our ongoing effort of migrating the codebase to Kotlin Changelog: [Internal] [Changed] - MultipartStreamReader to Kotlin Differential Revision: D72561124
1 parent dbc9356 commit baef93b

File tree

2 files changed

+168
-170
lines changed

2 files changed

+168
-170
lines changed

Diff for: packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/MultipartStreamReader.java

-170
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@file:Suppress("DEPRECATION_ERROR") // Conflicting okio versions
9+
10+
package com.facebook.react.devsupport
11+
12+
import java.io.IOException
13+
import kotlin.math.max
14+
import okio.Buffer
15+
import okio.BufferedSource
16+
import okio.ByteString
17+
18+
/** Utility class to parse the body of a response of type multipart/mixed. */
19+
internal class MultipartStreamReader(
20+
private val source: BufferedSource,
21+
private val boundary: String
22+
) {
23+
private var lastProgressEvent: Long = 0
24+
25+
interface ChunkListener {
26+
/** Invoked when a chunk of a multipart response is fully downloaded. */
27+
@Throws(IOException::class)
28+
fun onChunkComplete(headers: Map<String, String>, body: Buffer?, isLastChunk: Boolean)
29+
30+
/** Invoked as bytes of the current chunk are read. */
31+
@Throws(IOException::class)
32+
fun onChunkProgress(headers: Map<String, String>, loaded: Long, total: Long)
33+
}
34+
35+
/**
36+
* Reads all parts of the multipart response and execute the listener for each chunk received.
37+
*
38+
* @param listener Listener invoked when chunks are received.
39+
* @return If the read was successful
40+
*/
41+
@Throws(IOException::class)
42+
fun readAllParts(listener: ChunkListener): Boolean {
43+
val delimiter: ByteString = ByteString.encodeUtf8("$CRLF--$boundary$CRLF")
44+
val closeDelimiter: ByteString = ByteString.encodeUtf8("$CRLF--$boundary--$CRLF")
45+
val headersDelimiter: ByteString = ByteString.encodeUtf8(CRLF + CRLF)
46+
47+
val bufferLen = 4 * 1024
48+
var chunkStart: Long = 0
49+
var bytesSeen: Long = 0
50+
val content = Buffer()
51+
var currentHeaders: Map<String, String>? = null
52+
var currentHeadersLength: Long = 0
53+
54+
while (true) {
55+
var isCloseDelimiter = false
56+
57+
// Search only a subset of chunk that we haven't seen before + few bytes
58+
// to allow for the edge case when the delimiter is cut by read call.
59+
val searchStart =
60+
max((bytesSeen - closeDelimiter.size()).toDouble(), chunkStart.toDouble()).toLong()
61+
var indexOfDelimiter = content.indexOf(delimiter, searchStart)
62+
if (indexOfDelimiter == -1L) {
63+
isCloseDelimiter = true
64+
indexOfDelimiter = content.indexOf(closeDelimiter, searchStart)
65+
}
66+
67+
if (indexOfDelimiter == -1L) {
68+
bytesSeen = content.size()
69+
70+
if (currentHeaders == null) {
71+
val indexOfHeaders = content.indexOf(headersDelimiter, searchStart)
72+
if (indexOfHeaders >= 0) {
73+
source.read(content, indexOfHeaders)
74+
val headers = Buffer()
75+
content.copyTo(headers, searchStart, indexOfHeaders - searchStart)
76+
currentHeadersLength = headers.size() + headersDelimiter.size()
77+
currentHeaders = parseHeaders(headers)
78+
}
79+
} else {
80+
emitProgress(currentHeaders, content.size() - currentHeadersLength, false, listener)
81+
}
82+
83+
val bytesRead = source.read(content, bufferLen.toLong())
84+
if (bytesRead <= 0) {
85+
return false
86+
}
87+
continue
88+
}
89+
90+
val chunkEnd = indexOfDelimiter
91+
val length = chunkEnd - chunkStart
92+
93+
// Ignore preamble
94+
if (chunkStart > 0) {
95+
val chunk = Buffer()
96+
content.skip(chunkStart)
97+
content.read(chunk, length)
98+
// NULLSAFE_FIXME[Parameter Not Nullable]
99+
emitProgress(currentHeaders, chunk.size() - currentHeadersLength, true, listener)
100+
emitChunk(chunk, isCloseDelimiter, listener)
101+
currentHeaders = null
102+
currentHeadersLength = 0
103+
} else {
104+
content.skip(chunkEnd)
105+
}
106+
if (isCloseDelimiter) {
107+
return true
108+
}
109+
chunkStart = delimiter.size().toLong()
110+
bytesSeen = chunkStart
111+
}
112+
}
113+
114+
private fun parseHeaders(data: Buffer): Map<String, String> {
115+
val headers: MutableMap<String, String> = mutableMapOf()
116+
val text = data.readUtf8()
117+
val lines = text.split(CRLF.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
118+
for (line in lines) {
119+
val indexOfSeparator = line.indexOf(":")
120+
if (indexOfSeparator == -1) {
121+
continue
122+
}
123+
val key = line.substring(0, indexOfSeparator).trim { it <= ' ' }
124+
val value = line.substring(indexOfSeparator + 1).trim { it <= ' ' }
125+
headers[key] = value
126+
}
127+
return headers
128+
}
129+
130+
@Throws(IOException::class)
131+
private fun emitChunk(chunk: Buffer, done: Boolean, listener: ChunkListener) {
132+
val marker: ByteString = ByteString.encodeUtf8(CRLF + CRLF)
133+
val indexOfMarker = chunk.indexOf(marker)
134+
if (indexOfMarker == -1L) {
135+
listener.onChunkComplete(emptyMap(), chunk, done)
136+
} else {
137+
val headers = Buffer()
138+
val body = Buffer()
139+
chunk.read(headers, indexOfMarker)
140+
chunk.skip(marker.size().toLong())
141+
chunk.readAll(body)
142+
listener.onChunkComplete(parseHeaders(headers), body, done)
143+
}
144+
}
145+
146+
@Throws(IOException::class)
147+
private fun emitProgress(
148+
headers: Map<String, String>?,
149+
contentLength: Long,
150+
isFinal: Boolean,
151+
listener: ChunkListener?
152+
) {
153+
if (listener == null || headers == null) {
154+
return
155+
}
156+
val currentTime = System.currentTimeMillis()
157+
if (currentTime - lastProgressEvent > 16 || isFinal) {
158+
lastProgressEvent = currentTime
159+
val headersContentLength = headers.getOrDefault("Content-Length", "0").toLong()
160+
listener.onChunkProgress(headers, contentLength, headersContentLength)
161+
}
162+
}
163+
164+
companion object {
165+
// Standard line separator for HTTP.
166+
private const val CRLF = "\r\n"
167+
}
168+
}

0 commit comments

Comments
 (0)