123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323(* Ocsigen
* http://www.ocsigen.org
* Module deflatemod.ml
* Copyright (C) 2007 Gabriel Kerneis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)(* Compress output sent by the server *)openLwt.Infixletsection=Logs.Src.create"ocsigen:ext:deflate"(* Content-type *)typefilter=[`Typeofstringoption*stringoption|`Extensionofstring]letshould_compress(t,t')urlchoice_list=letcheck=function|`Type(None,None)->true|`Type(None,Somex')->x'=t'|`Type(Somex,None)->x=t|`Type(Somex,Somex')->x=t&&x'=t'|`Extensionsuff->Filename.check_suffixurlsuffinmatchchoice_listwith|`Onlyl->List.existscheckl|`All_butl->List.for_all(func->not(checkc))lletcompress_level=ref6letset_compress_leveli=compress_level:=ifi>=0&&i<=9thenielse6letbuffer_size=ref8192letset_buffer_sizes=buffer_size:=ifs>0thenselse8192(* Minimal header, by X. Leroy *)letgzip_header_length=10letgzip_header=letgzip_header=Bytes.makegzip_header_length(Char.chr0)inBytes.setgzip_header0@@Char.chr0x1F;Bytes.setgzip_header1@@Char.chr0x8B;Bytes.setgzip_header2@@Char.chr8;Bytes.setgzip_header9@@Char.chr0xFF;Bytes.unsafe_to_stringgzip_header(* inspired by an auxiliary function from camlzip, by Xavier Leroy *)typeoutput_buffer={stream:Zlib.stream;buf:bytes;flush:string->unitLwt.t;mutablesize:int32;mutablecrc:int32}letwrite_int32bufoffsetn=fori=0to3doBytes.setbuf(offset+i)(Char.chr(Int32.to_int(Int32.shift_right_logicaln(8*i))land0xff))doneletcompress_flushozused_out=Logs.debug~src:section(funfmt->fmt"Flushing %d bytes"used_out);ifused_out>0thenoz.flush(Bytes.sub_stringoz.buf0used_out)elseLwt.return_unit(* gzip trailer *)letwrite_traileroz=write_int32oz.buf0oz.crc;write_int32oz.buf4oz.size;compress_flushoz8(* puts in oz the content of buf, from pos to pos + len ; *)letreccompress_outputozinbufposlen=iflen=0thenLwt.return_unitelselet(_:bool),used_in,used_out=tryZlib.deflate_stringoz.streaminbufposlenoz.buf0(Bytes.lengthoz.buf)Zlib.Z_NO_FLUSHwithZlib.Error(s,s')->raise(Ocsigen_stream.Stream_error("Error during compression: "^s^" "^s'))incompress_flushozused_out>>=fun()->compress_outputozinbuf(pos+used_in)(len-used_in)letreccompress_finishoz=Logs.debug~src:section(funfmt->fmt"Finishing");(* loop until there is nothing left to compress and flush *)letfinished,(_:int),used_out=Zlib.deflateoz.streamoz.buf00oz.buf0(Bytes.lengthoz.buf)Zlib.Z_FINISHincompress_flushozused_out>>=fun()->ifnotfinishedthencompress_finishozelseLwt.return_unit(* deflate param : true = deflate ; false = gzip (no header in this case) *)letcompress_bodydeflatebody=funflush->letzstream=Zlib.deflate_init!compress_leveldeflateinletoz=letbuffer_size=!buffer_sizein{stream=zstream;buf=Bytes.createbuffer_size;flush;size=0l;crc=0l}in(ifdeflatethenLwt.return_unitelseflushgzip_header)>>=fun()->body(funinbuf->letlen=String.lengthinbufinoz.size<-Int32.addoz.size(Int32.of_intlen);oz.crc<-Zlib.update_crc_stringoz.crcinbuf0len;compress_outputozinbuf0len)>>=fun()->compress_finishoz>>=fun()->(ifdeflatethenLwt.return_unitelsewrite_traileroz)>>=fun()->Logs.debug~src:section(funfmt->fmt"Close stream");(tryZlib.deflate_endzstreamwith(* ignore errors, deflate_end cleans everything anyway *)|Zlib.Error_->());Lwt.return_unit(* We implement Content-Encoding, not Transfer-Encoding *)typeencoding=Deflate|Gzip|Id|Star|Not_acceptableletqvalue=functionSomex->x|None->1.0letenc_compareee'=matche,e'with|(Star,_),(_,_)->-1(* star should be at the very end *)|(_,_),(Star,_)->1|(_,v),(_,v')whenv<v'->1(* then, sort by qvalue *)|(_,v),(_,v')whenv>v'->-1|(x,_),(x',_)whenx=x'->0|(Deflate,_),(_,_)->1(* and subsort by encoding *)|(_,_),(Deflate,_)->-1|(Gzip,_),(_,_)->1|(_,_),(Gzip,_)->-1|(Id,_),(_,_)->1|(_,_),(Id,_)->-1|_->assertfalseletrecfiltermapf=function|[]->[]|t::q->(matchftwithSomes->s::filtermapfq|None->filtermapfq)letconvert=function|Some"deflate",v->Some(Deflate,qvaluev)|Some"gzip",v|Some"x-gzip",v->Some(Gzip,qvaluev)|Some"identity",v->Some(Id,qvaluev)|None,v->Some(Star,qvaluev)|_->None(* Follow http's RFC to select the transfer encoding *)letselect_encodingaccept_header=leth=List.sortenc_compare(filtermapconvertaccept_header)inletexclude,accept=lete,a=List.partition(funx->sndx=0.)hinList.mapfste,List.mapfstainletrecaux=function|[]->ifList.memStarexclude||List.memIdexcludethenNot_acceptableelseId|t::q->ifList.memtexcludethenauxqelsetinauxaccept(* deflate = true -> mode deflate
deflate = false -> mode gzip *)letstream_filtercontentencodingurldeflatechoiceres=Lwt.return(Ocsigen_extensions.Ext_found(fun()->trymatchOcsigen_response.headerresOcsigen_header.Name.content_typewith|None->Lwt.returnres|Somecontenttype->(letcontenttype=tryString.subcontenttype0(String.indexcontenttype';')withNot_found->contenttypeinmatchOcsigen_header.Mime_type.parsecontenttypewith|None,_|_,None->Lwt.returnres|Somea,Somebwhenshould_compress(a,b)urlchoice->letresponse=let{Http.Response.headers;status;version}=Ocsigen_response.responseresinletheaders=letname=Ocsigen_header.Name.(to_stringetag)inmatchCohttp.Header.getheadersnamewith|Somee->Cohttp.Header.replaceheadersname((ifdeflatethen"Ddeflatemod"else"Gdeflatemod")^e)|None->headersinletheaders=Http.Header.replaceheadersOcsigen_header.Name.(to_stringcontent_encoding)contentencodinginHttp.Response.make~headers~status~version()andbody=Ocsigen_response.Body.makeCohttp.Transfer.Chunked(compress_bodydeflate(Ocsigen_response.Body.write(Ocsigen_response.bodyres)))inLwt.return(Ocsigen_response.updateres~body~response)|_->Lwt.returnres)withNot_found->Lwt.returnres))letfilterchoice_list=function|Ocsigen_extensions.Req_not_found(code,_)->Lwt.return(Ocsigen_extensions.Ext_nextcode)|Ocsigen_extensions.Req_found({Ocsigen_extensions.request_info=ri;_},res)->(matchOcsigen_request.header_multiriOcsigen_header.Name.accept_encoding|>Ocsigen_header.Accept_encoding.parse|>select_encodingwith|Deflate->stream_filter"deflate"(Ocsigen_request.sub_path_stringri)truechoice_listres|Gzip->stream_filter"gzip"(Ocsigen_request.sub_path_stringri)falsechoice_listres|Id|Star->Lwt.return(Ocsigen_extensions.Ext_found(fun()->Lwt.returnres))|Not_acceptable->Lwt.return(Ocsigen_extensions.Ext_stop_all(Ocsigen_response.cookiesres,`Not_acceptable)))letrecparse_global_config=function|[]->()|Xml.Element("compress",[("level",i)],[])::ll->leti=tryint_of_stringiwithFailure_->raise(Ocsigen_extensions.Error_in_config_file"Compress level should be an integer between 0 and 9")inset_compress_leveli;parse_global_configll|Xml.Element("buffer",[("size",s)],[])::ll->lets=tryint_of_stringswithFailure_->raise(Ocsigen_extensions.Error_in_config_file"Buffer size should be a positive integer")inset_buffer_sizes;parse_global_configll|_->raise(Ocsigen_extensions.Error_in_config_file"Unexpected content inside deflatemod config")letparse_configconfig_elem=letmode=ref`Onlyinletpages=ref[]inOcsigen_extensions.(Configuration.process_element~in_tag:"host"~other_elements:(funt__->raise(Bad_config_tag_for_extensiont))~elements:[Configuration.element~name:"deflate"~attributes:[Configuration.attribute~name:"compress"~obligatory:true(function|"only"->mode:=`Only|"allbut"->mode:=`All_but|_->badconfig"Attribute 'compress' should be 'allbut' or 'only'")]~elements:[Configuration.element~name:"type"~pcdata:(funs->leta,b=Ocsigen_header.Mime_type.parsesinpages:=`Type(a,b)::!pages)();Configuration.element~name:"extension"~pcdata:(funs->pages:=`Extensions::!pages)()]()]config_elem);match!pageswith|[]->Ocsigen_extensions.badconfig"Unexpected element inside contenttype (should be <type> or <extension>)"|l->filter(match!modewith`Only->`Onlyl|`All_but->`All_butl)let()=Ocsigen_extensions.register~name:"deflatemod"~fun_site:(fun______->parse_config)~init_fun:parse_global_config()letrun~mode()___=filtermode