@@ -774,12 +774,12 @@ defmodule Plug.Conn do
774774
775775 ## Options
776776
777- * `:length` - sets the maximum number of bytes to read from the body for each
778- chunk, defaults to 8_000_000 bytes
779- * `:read_length` - sets the amount of bytes to read at one time from the
780- underlying socket to fill the chunk, defaults to 1_000_000 bytes
781- * `:read_timeout` - sets the timeout for each socket read, defaults to
782- 15_000 ms
777+ * `:length` - sets the maximum number of bytes to read from the body for
778+ each chunk, defaults to 8_000_000 bytes
779+ * `:read_length` - sets the amount of bytes to read at one time from the
780+ underlying socket to fill the chunk, defaults to 1_000_000 bytes
781+ * `:read_timeout` - sets the timeout for each socket read, defaults to
782+ 15_000ms
783783
784784 The values above are not meant to be exact. For example, setting the
785785 length to 8_000_000 may end up reading some hundred bytes more from
@@ -804,6 +804,122 @@ defmodule Plug.Conn do
804804 end
805805 end
806806
807+ @ doc """
808+ Reads the headers of a multipart request.
809+
810+ It returns `{:ok, headers, conn}` with the headers or
811+ `{:done, conn}` if there are no more parts.
812+
813+ Once `read_part_headers/2` is invoked, a developer may call
814+ `read_part_body/2` to read the body associated to the headers.
815+ If `read_part_headers/2` is called instead, the body is automatically
816+ skipped until the next part headers.
817+
818+ ## Options
819+
820+ * `:length` - sets the maximum number of bytes to read from the body for
821+ each chunk, defaults to 64_000 bytes
822+ * `:read_length` - sets the amount of bytes to read at one time from the
823+ underlying socket to fill the chunk, defaults to 64_000 bytes
824+ * `:read_timeout` - sets the timeout for each socket read, defaults to
825+ 5_000ms
826+
827+ """
828+ @ spec read_part_headers ( t , Keyword . t ) :: { :ok , headers , t } | { :done , t }
829+ def read_part_headers ( % Conn { adapter: { adapter , state } } = conn , opts \\ [ ] ) do
830+ opts = opts ++ [ length: 64_000 , read_length: 64_000 , read_timeout: 5000 ]
831+ case init_multipart ( conn ) do
832+ { boundary , buffer } ->
833+ { data , state } = read_multipart_from_buffer_or_adapter ( buffer , adapter , state , opts )
834+ read_part_headers ( conn , data , boundary , adapter , state , opts )
835+ :done ->
836+ { :done , conn }
837+ end
838+ end
839+
840+ defp read_part_headers ( conn , data , boundary , adapter , state , opts ) do
841+ case :plug_multipart . parse_headers ( data , boundary ) do
842+ { :ok , headers , rest } ->
843+ { :ok , headers , store_multipart ( conn , { boundary , rest } , adapter , state ) }
844+ :more ->
845+ { _ , next , state } = next_multipart ( adapter , state , opts )
846+ read_part_headers ( conn , data <> next , boundary , adapter , state , opts )
847+ { :more , rest } ->
848+ { _ , next , state } = next_multipart ( adapter , state , opts )
849+ read_part_headers ( conn , rest <> next , boundary , adapter , state , opts )
850+ { :done , _ } ->
851+ { :done , store_multipart ( conn , :done , adapter , state ) }
852+ end
853+ end
854+
855+ @ doc """
856+ Reads the body of a multipart request.
857+
858+ Returns `{:ok, body, conn}` if all body has been read,
859+ `{:more, binary, conn}` otherwise.
860+
861+ It accepts the same options as `read_body/2`.
862+ """
863+ @ spec read_part_body ( t , Keyword . t ) :: { :ok , binary , t } | { :more , binary , t }
864+ def read_part_body ( % { adapter: { adapter , state } } = conn , opts ) do
865+ case init_multipart ( conn ) do
866+ { boundary , buffer } ->
867+ length = Keyword . get ( opts , :length , 8_000_000 )
868+ { data , state } = read_multipart_from_buffer_or_adapter ( buffer , adapter , state , opts )
869+ read_part_body ( conn , data , "" , length , boundary , adapter , state , opts )
870+ :done ->
871+ { :done , conn }
872+ end
873+ end
874+
875+ defp read_part_body ( conn , data , acc , length , boundary , adapter , state , _opts ) when byte_size ( acc ) > length do
876+ { :more , acc , store_multipart ( conn , { boundary , data } , adapter , state ) }
877+ end
878+ defp read_part_body ( conn , data , acc , length , boundary , adapter , state , opts ) do
879+ case :plug_multipart . parse_body ( data , boundary ) do
880+ { :ok , body } ->
881+ { _ , next , state } = next_multipart ( adapter , state , opts )
882+ read_part_body ( conn , next , acc <> body , length , boundary , adapter , state , opts )
883+ { :ok , body , rest } ->
884+ { _ , next , state } = next_multipart ( adapter , state , opts )
885+ read_part_body ( conn , rest <> next , acc <> body , length , boundary , adapter , state , opts )
886+ :done ->
887+ { :ok , acc , store_multipart ( conn , { boundary , "" } , adapter , state ) }
888+ { :done , body } ->
889+ { :ok , acc <> body , store_multipart ( conn , { boundary , "" } , adapter , state ) }
890+ { :done , body , rest } ->
891+ { :ok , acc <> body , store_multipart ( conn , { boundary , rest } , adapter , state ) }
892+ end
893+ end
894+
895+ defp init_multipart ( % { private: % { plug_multipart: plug_multipart } } ) do
896+ plug_multipart
897+ end
898+ defp init_multipart ( % { req_headers: req_headers } ) do
899+ { _ , content_type } = List . keyfind ( req_headers , "content-type" , 0 )
900+ { :ok , "multipart" , _ , % { "boundary" => boundary } } = Plug.Conn.Utils . content_type ( content_type )
901+ { boundary , "" }
902+ end
903+
904+ defp next_multipart ( adapter , state , opts ) do
905+ case adapter . read_req_body ( state , opts ) do
906+ { :ok , "" , _ } -> raise "invalid multipart, body terminated too soon"
907+ valid -> valid
908+ end
909+ end
910+
911+ defp store_multipart ( conn , multipart , adapter , state ) do
912+ % { put_in ( conn . private [ :plug_multipart ] , multipart ) | adapter: { adapter , state } }
913+ end
914+
915+ defp read_multipart_from_buffer_or_adapter ( "" , adapter , state , opts ) do
916+ { _ , data , state } = adapter . read_req_body ( state , opts )
917+ { data , state }
918+ end
919+ defp read_multipart_from_buffer_or_adapter ( buffer , _adapter , state , _opts ) do
920+ { buffer , state }
921+ end
922+
807923 @ doc """
808924 Fetches cookies from the request headers.
809925 """
0 commit comments