{-# LANGUAGE FlexibleInstances #-}--------------------------------------------------------------------------------moduleParsers.NanoParsec(Parseable,Parser,item,some,many,sepBy,sepBy1,satisfy,oneOf,chainl,chainl1,char,string,token,reserved,spaces,runParser)where--------------------------------------------------------------------------------importqualifiedData.ByteStringasBSimportData.String(IsString)importControl.Applicative.Alternative(Alternative(empty,(<|>)))importControl.Monad.Plus(MonadPlus(mzero,mplus))---------------------------------------------------------------------------------- NanoParsec:-- http://dev.stephendiehl.com/fun/002_parsers.html#nanoparsecnewtypeParsersa=Parser{parse::s->[(a,s)]}class(Eqa,IsStringa)=>Parseableawherenil::a->Boolhd::a->Chartl::a->ainstanceParseableStringwherenil=(==[])hd=headtl=tailinstanceParseableBS.ByteStringwherenil=BS.nullhd=toEnum.fromIntegral.BS.headtl=BS.tail--------------------------------------------------------------------------------instance(Parseables)=>Functor(Parsers)wherefmapf(Parsercs)=Parser$\s->[(fa,b)|(a,b)<-css]instance(Parseables)=>Applicative(Parsers)wherepure=return(Parsercs1)<*>(Parsercs2)=Parser$\s->[(fa,s2)|(f,s1)<-cs1s,(a,s2)<-cs2s1]instance(Parseables)=>Monad(Parsers)wherereturn=unit(>>=)=bindinstance(Parseables)=>MonadPlus(Parsers)wheremzero=failuremplus=combineinstance(Parseables)=>Alternative(Parsers)whereempty=mzero(<|>)=option--------------------------------------------------------------------------------bind::(Parseables)=>Parsersa->(a->Parsersb)->Parsersbbindpf=Parser$\s->concatMap(\(a,s')->parse(fa)s')$parsepsunit::(Parseables)=>a->Parsersaunita=Parser$\s->[(a,s)]combine::(Parseables)=>Parsersa->Parsersa->Parsersacombinepq=Parser$\s->parseps++parseqsfailure::(Parseables)=>Parsersafailure=Parser$\_->[]option::(Parseables)=>Parsersa->Parsersa->Parsersaoptionpq=Parser$\s->caseparsepsof[ ]->parseqsres->res--------------------------------------------------------------------------------item::(Parseables)=>ParsersCharitem=Parser$\s->casenilsofTrue->[]False->[(hds,tls)]---------------------------------------------------------------------------------- | One or more.some::(Alternativef)=>fa->f[a]somev=some_vwheremany_v=some_v<|>pure[]some_v=(:)<$>v<*>many_v-- | Zero or more.many::(Alternativef)=>fa->f[a]manyv=many_vwheremany_v=some_v<|>pure[]some_v=(:)<$>v<*>many_v-- | One or more.sepBy1::(Alternativef)=>fa->fb->f[a]sepBy1psep=(:)<$>p<*>(many$sep*>p)-- | Zero or more.sepBy::(Alternativef)=>fa->fb->f[a]sepBypsep=sepBy1psep<|>pure[]--------------------------------------------------------------------------------satisfy::(Parseables)=>(Char->Bool)->ParsersCharsatisfyp=item`bind`\c->ifpcthenunitcelseParser$\_->[]--------------------------------------------------------------------------------oneOf::(Parseables)=>[Char]->ParsersCharoneOfs=satisfy$flipelemschainl::(Parseables)=>Parsersa->Parsers(a->a->a)->a->Parsersachainlpopa=(p`chainl1`op)<|>returnachainl1::(Parseables)=>Parsersa->Parsers(a->a->a)->Parsersap`chainl1`op=do{a<-p;resta}whereresta=(dof<-opb<-prest(fab))<|>returna--------------------------------------------------------------------------------char::(Parseables)=>Char->ParsersCharcharc=satisfy(c==)string::(Parseables)=>String->ParsersStringstring[]=return[]string(c:cs)=do{_<-charc;_<-stringcs;return(c:cs)}token::(Parseables)=>Parsersa->Parsersatokenp=do{a<-p;_<-spaces;returna}reserved::(Parseables)=>String->ParsersStringreserveds=token(strings)spaces::(Parseables)=>ParsersStringspaces=many$oneOf" \n\r"--------------------------------------------------------------------------------runParser::(Parseables)=>Parsersa->s->EitherStringarunParserms=ps$parsemswhereps[ ]=Left"Parser error."ps(x:_)=auxxauxx|nil$rest=Right$fst$x|not.nil$rest=Left$"Parser didn't consume entire stream."|otherwise=Left$"Parser error."whererest=sndx
moduleParsers.HTTP.Types(Response(..))where--------------------------------------------------------------------------------importqualifiedData.ByteStringasBS--------------------------------------------------------------------------------{- HyperText Transfer Protocol (Response message):
The response message consists of the following:
* A status line which includes the status code and reason message (e.g.,
HTTP/1.1 200 OK, which indicates that the client's request succeeded).
* Response header fields (e.g., Content-Type: text/html).
* An empty line.
* An optional message body.
-}dataResponse=Response{statusCode::Int,headers::[(String,String)],body::MaybeBS.ByteString}derivingShow-- Reference:-- -- Hypertext Transfer Protocol (Response message):-- -- https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Response_message---- List of HTTP status codes:-- -- https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
{-# LANGUAGE OverloadedStrings #-}--------------------------------------------------------------------------------moduleParsers.HTTP.Internal(response)where--------------------------------------------------------------------------------importqualifiedData.ByteStringasBSimportData.Char(isDigit)importParsers.NanoParsecimportParsers.HTTP.TypesasT--------------------------------------------------------------------------------noeol::(Parseables)=>ParsersCharnoeol=satisfy$\c->'\r'/=c&&'\n'/=cstatusCode'::(Parseables)=>ParsersIntstatusCode'=do__<-reserved"HTTP/1.1"__<-spacessc<-some$satisfyisDigit__<-manynoeol__<-reserved"\r\n"return$readscheaders'::(Parseables)=>Parsers[(String,String)]headers'=doh<-pair`sepBy`reserved"\r\n"return$hwherenono=satisfy$\c->':'/=c&&'\r'/=c&&'\n'/=cpair=dokey<-somenono___<-reserved": "val<-some$noeolreturn$(key,val)response::(Parseables)=>BS.ByteString->ParsersT.Responseresponseb=dos<-statusCode'h<-headers'_<-reserved"\r\n"return$T.Responsesh$ifb==BS.emptythenNothingelseJustb
{-# LANGUAGE OverloadedStrings #-}--------------------------------------------------------------------------------moduleParsers.HTTP(parse)where--------------------------------------------------------------------------------importqualifiedData.ByteStringasBSimportqualifiedParsers.NanoParsecasNPimportParsers.HTTP.InternalimportqualifiedParsers.HTTP.TypesasHT--------------------------------------------------------------------------------findSubByteString::BS.ByteString->BS.ByteString->MaybeIntfindSubByteStringssbs=ifaux<0thenNothingelseJustauxwhereaux=ifn<mthen-1elsesubmmmsubci0=ifcomi0thenc-melse-1subcij=ifc>nthen-1elseifcomijthensubc(i-1)(j-1)elsesubc'c'mwherec'=c+1-- O(1) length returns the length of a ByteString as an Int.n=BS.lengthbs-1m=BS.lengthss-1-- O(1) ByteString index (subscript) operator, starting from 0.comij=BS.indexbsi==BS.indexssj--------------------------------------------------------------------------------parse::BS.ByteString->EitherStringHT.Responseparseres=auxwhereaux=casefindSubByteString"\r\n\r\n"resofNothing->Left"Not a valid HTTP response."Justi->NP.runParser(responseb)pwherep=BS.take(i+2)resb=BS.drop(i+4)res
#!/usr/bin/envstack{- stack
--resolver lts-11.7
--install-ghc
runghc
--package monadplus
--package tls
--package x509-store
--
-}-- -Wall -Werror-- Issue with stack: Version 1.7.1-- Git revision 681c800873816c022739ca7ed14755e85a579565 x86_64 hpack-0.28.2-- the following flags after -- aren't read anymore and are just sent as extra-- arguments which are caught by getArgs. Therefore, they are outcommented--------------------------------------------------------------------------------{-# LANGUAGE OverloadedStrings #-}--------------------------------------------------------------------------------moduleMain(main)where--------------------------------------------------------------------------------importControl.Exception(IOException,try)importData.Maybe(fromJust,fromMaybe)importData.Default.Class(def)importqualifiedData.ByteStringasBSimportqualifiedData.ByteString.Char8asC8importqualifiedData.ByteString.Lazy.Char8asL8importData.X509.CertificateStoreasX509importNetwork.Sockethiding(recv,send)importqualifiedNetwork.TLSasTimportqualifiedNetwork.TLS.ExtraasTEimportSystem.Environment(getArgs)importqualifiedParsers.URLasURLimportqualifiedParsers.URL.TypesasURLimportqualifiedParsers.HTTPasHTTPimportqualifiedParsers.HTTP.TypesasHTTP--------------------------------------------------------------------------------parsedUrl::String->EitherString(String,String,String)resolveUrl::String->String->IO(EitherIOException[AddrInfo])response::[AddrInfo]->String->String->IOBS.ByteStringcurl::String->IO()main::IO()--------------------------------------------------------------------------------parsedUrlurl=caseURL.parseurlofLeftmsg->LeftmsgRighturi->case(host,port,comb)of(_,Justp',_)->Right$(host,p',comb)______________->Left$"Scheme not supported."whereauth=URL.authorityurihost=URL.hostauthport=case(URL.schemeuri,URL.portauth)of(URL.HTTPS,Nothing)->Just$"443"(URL.HTTPS,Justp')->Just$showp'____________________->Nothingcomb="/"++p++q++fp=fromMaybe""$URL.pathurif=fromMaybe""$("#"++)<$>URL.fragmenturiq=fromMaybe""$(\q'->caseURL.keyValuesq'of[]->""xs->foldl1(\xy->x++"&"++y)$map(\(k,mv)->k++"="++fromMaybe""mv)$xs)<$>URL.queryuriresolveUrlhostport=try$getAddrInfoNothing(Justhost)(Justport)responseinfohostpath=dox509<-cacssock<-socket(addrFamilyaddr)(addrSocketTypeaddr)(addrProtocoladdr)____<-connectsock(addrAddressaddr)ctx<-T.contextNewsock$parax509host___<-T.handshakectx___<-T.sendDatactx$L8.fromChunks[C8.pack$"GET "++path++" HTTP/1.1","\r\n",C8.pack$"Host: "++host,"\r\n","Connection: close","\r\n","\r\n"]res<-recvctxreturnreswhereaddr=head$filter(\a->addrSocketTypea==Stream)$info-- CA certificates extracted from Mozilla:-- # https://curl.haxx.se/docs/caextract.htmlcacs=readCertificateStore"cacert.pem">>=pure.fromJustparax509h=(T.defaultParamsClienthBS.empty){T.clientSupported=def{T.supportedCiphers=TE.ciphersuite_strong,T.supportedVersions=[T.TLS12]},T.clientShared=def{T.sharedCAStore=x509}}recvctx=auxBS.emptywhereauxacc=dopkg<-T.recvDatactxif0==BS.lengthpkgthenreturn$acc`BS.append`pkgelseaux$acc`BS.append`pkgcurlurl=docaseparsedUrlurlofLeftmsg->putStrLnmsgRight(host,port,path)->dovalid<-resolveUrlhostportcasevalidofLeft__->putStrLn$"Could not resolve host: "++hostRightinfo->dores<-responseinfohostpathcaseHTTP.parseresofLeftmsg->putStrLnmsgRightres'->docaseHTTP.bodyres'ofNothing->return()Justb->C8.putStrb--------------------------------------------------------------------------------main=dourls<-getArgsmapM_curlurls