123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233(*----------------------------------------------------------------------------
* Copyright (c) 2020, António Nuno Monteiro
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*---------------------------------------------------------------------------*)moduleMultipart_form=Piaf_multipart_form.Multipart_formmodulePp=Ppletcontent_typeheader=letopenMultipart_forminHeader.content_typeheaderletcontent_dispositionheader=letopenMultipart_forminHeader.content_dispositionheaderletname_of_headerheader=letopenMultipart_forminmatchHeader.content_dispositionheaderwith|Somecdispo->Multipart_form.Content_disposition.namecdispo|None->Noneletrecresult_headerst=letopenMultipart_forminletatom_to_headersaccheader=letopenFieldinletheaders=List.filter_map(function|Field.Field(_,Content_type,{ty;subty;_})->letty=Format.asprintf"%a"Pp.pp_tytyinletsubty=matchsubtywith|`Ietf_tokens|`Iana_tokens|`X_tokens->sinSome((Field_name.content_type:>string),Format.asprintf"%s/%s"tysubty)|Field.Field(_,Content_encoding,encoding)->letencoding=matchencodingwith|`Ietf_tokens|`X_tokens->s|#Content_encoding.t->Format.asprintf"%a"Content_encoding.ppencodinginSome((Field_name.content_transfer_encoding:>string),encoding)|Field.Field(_,Content_disposition,cdispo)->letty=matchContent_disposition.disposition_typecdispowith|`Ietf_tokens|`X_tokens->s|ty->Format.asprintf"%a"Pp.pp_disposition_typetyinletname=Content_disposition.namecdispoinletfilename=Content_disposition.filenamecdispoinletvalue=Format.asprintf"%s%a%a"ty(Format.pp_print_option~none:(fun_fmt()->())(funfmtname->Format.fprintffmt"; name=%S"name))name(Format.pp_print_option~none:(fun_fmt()->())(funfmtfilename->Format.fprintffmt"; filename=%S"filename))filenameinSome((Field_name.content_disposition:>string),value)|Field.Field(_,Field,_unstructured)->None)(Header.assocField_name.content_dispositionheader@Header.assocField_name.content_typeheader@Header.assocField_name.content_transfer_encodingheader)inheaders@accinmatchtwith|Multipart{header;body}->letheaders=atom_to_headers[]headerin(matchbodywith|[]->headers|xs->atom_to_headers[]header@List.concat_map(functionNone->[]|Somet->result_headerst)xs)|Leaf{header;_}->atom_to_headers[]headerletresult_fieldst=letopenMultipart_forminletrecinneracct=letatom_to_fieldsaccheader=matchname_of_headerheaderwith|Somename->(name,header)::acc|None->accinmatchtwith|Multipart{header;body:'atoptionlist}->letacc=List.concat_map(inneracc)(List.filter_map(funx->x)body)inletatom_headers=atom_to_fieldsaccheaderinatom_headers|Leaf{header;_}->atom_to_fieldsaccheaderininner[]tletparse_content_typect=Multipart_form.Content_type.of_string(ct^"\r\n")letblitsrcsrc_offdstdst_offlen=Bigstringaf.blitsrc~src_offdst~dst_off~lenmoduleQe=Ke.RkemoduleAU=Angstrom.Unbufferedletextract_parts~emit~finish~max_chunk_size~content_typestream=(* min chunk size is 1KB. *)letmax_chunk_size=maxmax_chunk_size0x400inletemittersheader=letstream,push=Piaf_stream.create128inletkey=name_of_headerheaderinemitkeystream;push,keyinletstate=AU.parse(Multipart_form.parser~max_chunk_size~emitterscontent_type)inletke=Qe.create~capacity:max_chunk_sizeBigarray.charinletreal_capacity=Qe.capacitykeinletmax_capacity=4*real_capacityinletrecon_eofstate=matchstatewith|AU.Partial{continue;committed}->Qe.N.unsafe_shiftkecommitted;ifcommitted=0thenQe.compresske;letnext_state=matchQe.N.peekkewith|[]->continueBigstringaf.empty~off:0~len:0Complete|[slice]->continueslice~off:0~len:(Bigstringaf.lengthslice)Complete|slice::_->continueslice~off:0~len:(Bigstringaf.lengthslice)Incompleteinon_eofnext_state|Fail(pos,marks,msg)->Error(Format.asprintf"multipart parser failed on position %d. Error: %s ([%s])"posmsg(String.concat"; "marks))|Done(_,v)->finish();Okvinletrecparsestate=matchPiaf_stream.takestreamwith|None->(* Stream ended. Still need to check `error`. *)on_eofstate|Some{Faraday.buffer;off;len}->(matchstatewith|Partial{continue;committed}->Qe.N.unsafe_shiftkecommitted;ifcommitted=0thenQe.compresske;Qe.N.pushke~blit~length:Bigstringaf.length~off~lenbuffer;ifQe.capacityke>max_capacitythenError"POST buffer has grown too much"elseifnot(Qe.is_emptyke)then(* NOTE(anmonteiro): It's OK to only read the first slice of the
* queue. Ke's implementation returns at most 2 buffers from `peek`:
* the second one is returned if the buffer has wrapped around its
* capacity. *)let[@warning"-8"](slice::_)=Qe.N.peekkeinletnext_state=continueslice~off:0~len:(Bigstringaf.lengthslice)Incompleteinparsenext_stateelseparsestate|Fail(pos,marks,msg)->Error(Format.asprintf"multipart parser failed on position %d. Error: %s ([%s])"posmsg(String.concat"; "marks))|Done(_,v)->Okv)inmatchparsestatewith|Okt->(* Both headers and the Hash Table are indexed by the `name` in *
`content-disposition`*)Okt|Errormsg->Error(`Msgmsg)typet=stringoptionMultipart_form.tletparse_multipart_form~content_type~max_chunk_size~emit?(finish=ignore)stream=matchparse_content_typecontent_typewith|Okcontent_type->extract_parts~emit~finish~max_chunk_size~content_typestream|Errore->Errore