@@ -4,157 +4,11 @@ package multipart
44import cats .effect ._
55import cats .implicits .{catsSyntaxEither => _ , _ }
66import fs2 ._
7- import scala .annotation .tailrec
8- import scodec .bits .ByteVector
97import org .http4s .util ._
108
119/** A low-level multipart-parsing pipe. Most end users will prefer EntityDecoder[Multipart]. */
1210object MultipartParser {
1311
14- private [this ] val logger = org.log4s.getLogger
15-
16- private val CRLFBytes = ByteVector ('\r ' , '\n ' )
17- private val DashDashBytes = ByteVector ('-' , '-' )
18- private val boundaryBytes : Boundary => ByteVector = boundary =>
19- ByteVector (boundary.value.getBytes)
20- private val startLineBytes : Boundary => ByteVector = boundaryBytes.andThen(DashDashBytes ++ _)
21- private val endLineBytes : Boundary => ByteVector = startLineBytes.andThen(_ ++ DashDashBytes )
22- private val expectedBytes : Boundary => ByteVector = startLineBytes.andThen(CRLFBytes ++ _)
23-
24- final case class Out [+ A ](a : A , tail : Option [ByteVector ] = None )
25-
26- def parse [F [_]: Sync ](boundary : Boundary ): Pipe [F , Byte , Either [Headers , ByteVector ]] = s => {
27- val bufferedMultipartT = s.compile.toVector.map(ByteVector (_))
28- val parts = bufferedMultipartT.flatMap(parseToParts(_)(boundary))
29- val listT = parts.map(splitParts(_)(boundary)(List .empty[Either [Headers , ByteVector ]]))
30-
31- Stream
32- .eval(listT)
33- .flatMap(list => Stream .emits(list))
34- }
35-
36- /**
37- * parseToParts - Removes Prelude and Trailer
38- *
39- * splitParts - Splits Into Parts
40- * splitPart - Takes a Single Part of the Front
41- * generatePart - Generates a tuple of Headers and a ByteVector of the Body, effectively a Part
42- *
43- * generateHeaders - Generate Headers from ByteVector
44- * splitHeader - Splits a Header into the Name and Value
45- */
46- def parseToParts [F [_]](byteVector : ByteVector )(boundary : Boundary )(
47- implicit F : Sync [F ]): F [ByteVector ] = {
48- val startLine = startLineBytes(boundary)
49- val startIndex = byteVector.indexOfSlice(startLine)
50- val endLine = endLineBytes(boundary)
51- val endIndex = byteVector.indexOfSlice(endLine)
52-
53- if (startIndex >= 0 && endIndex >= 0 ) {
54- val parts = byteVector.slice(
55- startIndex + startLine.length + CRLFBytes .length,
56- endIndex - CRLFBytes .length)
57- F .delay(parts)
58- } else {
59- F .raiseError(MalformedMessageBodyFailure (" Expected a multipart start or end line" ))
60- }
61- }
62-
63- def splitPart (byteVector : ByteVector )(boundary : Boundary ): Option [(ByteVector , ByteVector )] = {
64- val expected = expectedBytes(boundary)
65- val index = byteVector.indexOfSlice(expected)
66-
67- if (index >= 0L ) {
68- val (part, restWithExpected) = byteVector.splitAt(index)
69- val rest = restWithExpected.drop(expected.length)
70- Option ((part, rest))
71- } else {
72- Option ((byteVector, ByteVector .empty))
73- }
74- }
75- @ tailrec
76- def splitParts (byteVector : ByteVector )(boundary : Boundary )(
77- acc : List [Either [Headers , ByteVector ]]): List [Either [Headers , ByteVector ]] = {
78-
79- val expected = expectedBytes(boundary)
80- val containsExpected = byteVector.containsSlice(expected)
81-
82- val partOpt = if (! containsExpected) {
83-
84- Option ((byteVector, ByteVector .empty))
85- } else {
86- splitPart(byteVector)(boundary)
87- }
88-
89- partOpt match {
90- case Some ((part, rest)) =>
91- logger.trace(s " splitParts part: ${part.decodeUtf8.toOption}" )
92-
93- val (headers, body) = generatePart(part)
94-
95- val newAcc = Either .right(body) :: Either .left(headers) :: acc
96- logger.trace(s " splitParts newAcc: $newAcc" )
97-
98- if (rest.isEmpty) {
99- newAcc.reverse
100- } else {
101- splitParts(rest)(boundary)(newAcc)
102- }
103- case None => acc.reverse
104- }
105- }
106-
107- def generatePart (byteVector : ByteVector ): (Headers , ByteVector ) = {
108- val doubleCRLF = CRLFBytes ++ CRLFBytes
109- val index = byteVector.indexOfSlice(doubleCRLF)
110- // Each Part Should Contain this Separation between bodies and headers -- We could handle failure.
111- val (headersSplit, bodyWithCRLFs) = byteVector.splitAt(index)
112- val body = bodyWithCRLFs.drop(doubleCRLF.length)
113-
114- logger.trace(s " GeneratePart HeadersSplit ${headersSplit.decodeAscii}" )
115- logger.trace(s " GenerateParts Body ${body.decodeAscii}" )
116-
117- val headers = generateHeaders(headersSplit ++ CRLFBytes )(Headers .empty)
118-
119- (headers, body)
120- }
121-
122- @ tailrec
123- def generateHeaders (byteVector : ByteVector )(acc : Headers ): Headers = {
124- val headerO = splitHeader(byteVector)
125-
126- headerO match {
127- case Some ((lineBV, rest)) =>
128- val headerO = for {
129- line <- lineBV.decodeAscii.right.toOption
130- idx <- Some (line.indexOf(':' ))
131- if idx >= 0
132- header = Header (line.substring(0 , idx), line.substring(idx + 1 ).trim)
133- } yield header
134-
135- val newHeaders = acc ++ headerO
136-
137- logger.trace(s " Generate Headers Header0 = $headerO" )
138- generateHeaders(rest)(newHeaders)
139- case None => acc
140- }
141-
142- }
143-
144- def splitHeader (byteVector : ByteVector ): Option [(ByteVector , ByteVector )] = {
145- val index = byteVector.indexOfSlice(CRLFBytes )
146-
147- if (index >= 0L ) {
148- val (line, rest) = byteVector.splitAt(index)
149-
150- logger.trace(s " Split Header Line: ${line.decodeAscii}" )
151- logger.trace(s " Split Header Rest: ${rest.decodeAscii}" )
152- Option ((line, rest.drop(CRLFBytes .length)))
153- } else {
154- Option .empty[(ByteVector , ByteVector )]
155- }
156- }
157-
15812 private val CRLFBytesN = Array [Byte ]('\r ' , '\n ' )
15913 private val DoubleCRLFBytesN = Array [Byte ]('\r ' , '\n ' , '\r ' , '\n ' )
16014 private val DashDashBytesN = Array [Byte ]('-' , '-' )
0 commit comments