1616
1717package org .springframework .http .converter .multipart ;
1818
19- import java .io .IOException ;
2019import java .io .InputStream ;
2120import java .nio .charset .Charset ;
2221import java .nio .charset .StandardCharsets ;
@@ -81,10 +80,28 @@ private MultipartParser(
8180 }
8281
8382
83+ /**
84+ * Simple delegation to {@link State#data(DataBuffer)}.
85+ */
8486 void handleData (DataBuffer dataBuffer ) {
8587 this .state .data (dataBuffer );
8688 }
8789
90+ /**
91+ * Handle a parsing error cleaning resources in the state and in the listener
92+ * unless the error is {@link HttpMessageConversionException} in which case
93+ * it is simply propagated.
94+ */
95+ void handleException (String message , @ Nullable Throwable cause ) {
96+ if (cause instanceof HttpMessageConversionException ex ) {
97+ throw ex ;
98+ }
99+ changeState (DisposedState .INSTANCE , null );
100+ HttpMessageConversionException ex = new HttpMessageConversionException (message , cause );
101+ this .listener .onError (ex );
102+ throw ex ;
103+ }
104+
88105 private void changeState (State newState , @ Nullable DataBuffer remainder ) {
89106 if (logger .isTraceEnabled ()) {
90107 logger .trace ("Changed state: " + this .state + " -> " + newState );
@@ -118,6 +135,7 @@ private static byte[] concat(byte[]... byteArrays) {
118135 return result ;
119136 }
120137
138+
121139 /**
122140 * Parse the given stream of bytes into events published to the given {@link PartListener}.
123141 * @param input the input stream
@@ -141,9 +159,8 @@ public static void parse(InputStream input, byte[] boundary, Charset headersChar
141159 }
142160 parser .state .complete ();
143161 }
144- catch (IOException ex ) {
145- parser .state .dispose ();
146- listener .onError (new HttpMessageConversionException ("Could not decode multipart message" , ex ));
162+ catch (Throwable ex ) {
163+ parser .handleException ("Could not decode multipart message" , ex );
147164 }
148165 }
149166
@@ -155,13 +172,19 @@ interface PartListener {
155172
156173 /**
157174 * Handle {@link HttpHeaders} for a part.
175+ * <p>Expectations for exception handling are the same as for {@link #onBody}.
158176 */
159177 void onHeaders (HttpHeaders headers );
160178
161179 /**
162- * Handle a piece of data for a body part.
180+ * Handle the next chunk of body data.
181+ * <p>Implementations must release the input buffer.
182+ * <p>Implementations must handle all exceptions, cleaning up resources,
183+ * and wrapping the exception as {@link HttpMessageConversionException}.
163184 * @param buffer a chunk of body
164- * @param last whether this is the last chunk for the part
185+ * @param last whether this is the last chunk for the part
186+ * @throws HttpMessageConversionException if the buffer could not be
187+ * handled due to exceeded limits or for any other reason
165188 */
166189 void onBody (DataBuffer buffer , boolean last );
167190
@@ -171,7 +194,12 @@ interface PartListener {
171194 void onComplete ();
172195
173196 /**
174- * Handle any error thrown during the parsing phase.
197+ * Handle any error thrown during the parsing phase. The purpose of the
198+ * method call is to allow cleaning up of resources. The listener does
199+ * not need to throw or wrap and throw the error.
200+ * <p>{@link #onHeaders} and {@link #onBody} are expected to handle their
201+ * own exceptions, i.e. any exception those methods throw will not be
202+ * passed here.
175203 */
176204 void onError (Throwable error );
177205
@@ -194,10 +222,26 @@ interface PartListener {
194222 */
195223 private interface State {
196224
225+ /**
226+ * Handle the next chunk of data.
227+ * <p>If this method raises any exception other than
228+ * {@link HttpMessageConversionException}, it will be
229+ * {@link MultipartParser#handleException handled} by the parser.
230+ * An {@link HttpMessageConversionException} on the other hand is
231+ * considered fully handled and allowed to propagate as is.
232+ */
197233 void data (DataBuffer buf );
198234
235+ /**
236+ * Called when the current part is fully parsed.
237+ * <p>Expecations for exception handling are the same as for {@link #data}.
238+ */
199239 void complete ();
200240
241+ /**
242+ * Clean up resources held by the state. Called in case of errors or
243+ * when switching to a new state.
244+ */
201245 default void dispose () {
202246 }
203247
@@ -241,8 +285,7 @@ public void data(DataBuffer buf) {
241285
242286 @ Override
243287 public void complete () {
244- changeState (DisposedState .INSTANCE , null );
245- MultipartParser .this .listener .onError (new HttpMessageConversionException ("Could not find first boundary" ));
288+ handleException ("Could not find first boundary" , null );
246289 }
247290
248291 @ Override
@@ -312,7 +355,14 @@ private void emitHeaders() {
312355 if (logger .isTraceEnabled ()) {
313356 logger .trace ("Emitting headers: " + headers );
314357 }
315- MultipartParser .this .listener .onHeaders (headers );
358+ try {
359+ MultipartParser .this .listener .onHeaders (headers );
360+ }
361+ catch (Throwable ex ) {
362+ // PartListener should have cleaned its state, clean our own
363+ dispose ();
364+ throw ex ;
365+ }
316366 }
317367
318368 /**
@@ -338,12 +388,9 @@ private boolean belowMaxHeaderSize(long count) {
338388 if (count <= MultipartParser .this .maxHeadersSize ) {
339389 return true ;
340390 }
341- else {
342- MultipartParser .this .listener .onError (
343- new HttpMessageConversionException ("Part headers exceeded the memory usage limit of " +
344- MultipartParser .this .maxHeadersSize + " bytes" ));
345- return false ;
346- }
391+ MultipartParser .this .handleException (
392+ "Part headers exceeded the limit of " + MultipartParser .this .maxHeadersSize + " bytes" , null );
393+ return false ;
347394 }
348395
349396 /**
@@ -377,8 +424,7 @@ private HttpHeaders parseHeaders() {
377424
378425 @ Override
379426 public void complete () {
380- changeState (DisposedState .INSTANCE , null );
381- MultipartParser .this .listener .onError (new HttpMessageConversionException ("Could not find end of headers" ));
427+ MultipartParser .this .handleException ("Could not find end of headers" , null );
382428 }
383429
384430 @ Override
@@ -493,25 +539,32 @@ private void enqueue(DataBuffer buf) {
493539 }
494540 len += previous .readableByteCount ();
495541 }
496- emit .forEach (buffer -> MultipartParser . this . listener . onBody (buffer , false ));
542+ emit .forEach (buffer -> invokeListener (buffer , false ));
497543 }
498544
499545 private void flush () {
500546 for (Iterator <DataBuffer > iterator = this .queue .iterator (); iterator .hasNext (); ) {
501547 DataBuffer buffer = iterator .next ();
502548 boolean last = !iterator .hasNext ();
503- MultipartParser . this . listener . onBody (buffer , last );
549+ invokeListener (buffer , last );
504550 }
505551 this .queue .clear ();
506552 }
507553
554+ private void invokeListener (DataBuffer buffer , boolean last ) {
555+ try {
556+ MultipartParser .this .listener .onBody (buffer , last );
557+ }
558+ catch (Throwable ex ) {
559+ dispose ();
560+ throw ex ;
561+ }
562+ }
563+
508564 @ Override
509565 public void complete () {
510- changeState (DisposedState .INSTANCE , null );
511- String msg = "Could not find end of body (␍␊--" +
512- new String (MultipartParser .this .boundary , StandardCharsets .UTF_8 ) +
513- ")" ;
514- MultipartParser .this .listener .onError (new HttpMessageConversionException (msg ));
566+ MultipartParser .this .handleException ("Could not find end of body (␍␊--" +
567+ new String (MultipartParser .this .boundary , StandardCharsets .UTF_8 ) + ")" , null );
515568 }
516569
517570 @ Override
0 commit comments