123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148(*{{{ Copyright (c) 2012 Anil Madhavapeddy <anil@recoil.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
}}}*)openTransfermoduleMake(IO:S.IO)=structopenIOtypereader=unit->Transfer.chunkIO.ttypewriter=string->unitIO.tmoduleChunked=structletremaining_lengthchunkremaining=letread_len=Int64.of_int(String.lengthchunk)inInt64.subremainingread_lenletread_chunkicsize=letmax_read_len=Int64.of_int0x8000inletlen=minsizemax_read_leninreadic(Int64.to_intlen)letparse_chunksizechunk_size_hex=lethex=(* From https://tools.ietf.org/html/rfc7230#section-4.1.1
> The chunked encoding allows each chunk to include zero or
> more chunk extensions, immediately following the chunk-size
*)tryString.subchunk_size_hex0(String.indexchunk_size_hex';')with_->chunk_size_hexintrySome(Int64.of_string("0x"^hex))with_->Noneletrecjunk_until_empty_lineic=read_lineic>>=function|None|Some""->returnDone|Some_trailer->junk_until_empty_lineicletread~remainingic()=(* read between 0 and 32Kbytes of a chunk *)letread_chunk_fragment()=read_chunkic!remaining>>=funchunk->remaining:=remaining_lengthchunk!remaining;(if!remaining=0L(* End_of_chunk *)thenread_lineic(* Junk the CRLF at end of chunk *)elsereturnNone)>>=fun_->returnchunkinif!remaining=0Lthen(* Beginning of a chunk: read chunk size, read up to 32K bytes *)read_lineic>>=function|None->returnDone|Somechunk_size_hex->(matchparse_chunksizechunk_size_hexwith|None->returnDone|Some0L->(* TODO: Trailer header support *)junk_until_empty_lineic|Somecount->(remaining:=count;read_chunk_fragment()>>=function|""->returnDone(* 0 bytes read means EOF *)|buf->return(Chunkbuf)))else(* Middle of a chunk, read up to 32K bytes *)read_chunk_fragment()>>=function|""->returnDone(* 0 bytes read means EOF *)|buf->return(Chunkbuf)letwriteocbuf=letlen=String.lengthbufin(* do NOT send empty chunks, as it signals the end of the
chunked body *)iflen<>0thenwriteoc(Printf.sprintf"%x\r\n"len)>>=fun()->writeocbuf>>=fun()->writeoc"\r\n"elsereturn()endmoduleFixed=structletread~remainingic()=(* TODO functorise string to a bigbuffer *)match!remainingwith|0L->returnDone|len->(letmax_read_len=Int64.of_int0x8000inletread_len=Int64.to_int(minlenmax_read_len)inreadicread_len>>=function|""->returnDone|buf->remaining:=Int64.sub!remaining(Int64.of_int(String.lengthbuf));return(match!remainingwith0L->Final_chunkbuf|_->Chunkbuf))(* TODO enforce that the correct length is written? *)letwrite=writeendmoduleUnknown=struct(* If we have no idea, then read until EOF (connection shutdown by
the remote party). *)letreadic()=readic4096>>=funbuf->ifbuf=""thenreturnDoneelsereturn(Chunkbuf)letwrite=writeendletwrite_and_flushfnocbuf=fnocbuf>>=fun()->IO.flushocletmake_reader=function|Chunked->Chunked.read~remaining:(ref0L)|Fixedlen->Fixed.read~remaining:(reflen)|Unknown->Unknown.readletwrite_ignore_blankwriterios=ifString.lengths=0thenreturn()elsewriteriosletmake_writer?(flush=false)mode=matchflushwith|false->(matchmodewith|Chunked->Chunked.write|Fixed_->Fixed.write|Unknown->Unknown.write)|true->(matchmodewith|Chunked->write_and_flushChunked.write|Fixed_->write_and_flushFixed.write|Unknown->write_and_flushUnknown.write)|>write_ignore_blankletreadreader=reader()letwritewriterbuf=writerbufend