Thanks for the help, everyone. The browser is coming along nicely :]<br><br><div class="gmail_quote">On Tue, Feb 7, 2012 at 10:05 PM, Michael Snoyman <span dir="ltr"><<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="im">On Wed, Feb 8, 2012 at 6:28 AM, Myles C. Maxfield<br>
<<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
> I have been looking around at possibly making a Browser module for<br>
> Network.HTTP.Conduit on top of Control.Monad.State. I came across this<br>
> roadbump:<br>
><br>
> In order to implement redirection following, client code must call 'http'<br>
> with a redirection count of 0. If there is a redirect, 'http' will throw an<br>
> exception. In order to catch this exception and continue on with the<br>
<br>
</div>Actually, this is just a setting: you can override with checkStatus[1].<br>
<br>
[1] <a href="http://hackage.haskell.org/packages/archive/http-conduit/1.2.4/doc/html/Network-HTTP-Conduit.html#v:checkStatus" target="_blank">http://hackage.haskell.org/packages/archive/http-conduit/1.2.4/doc/html/Network-HTTP-Conduit.html#v:checkStatus</a><br>
<div class="HOEnZb"><div class="h5"><br>
> redirection chain, the 'catch' function must be called. The problem is that<br>
> the 'catch' function has a type of (catch :: Exception e => IO a -> (e -> IO<br>
> a) -> IO a) which means that it can only be used in the IO monad. A call to<br>
> 'http' inside the first argument of 'catch' must be wrapped in a<br>
> 'runResourceT'<br>
><br>
> This has a couple implications:<br>
><br>
> The catch function cannot yield a Source, because any connections which<br>
> 'http' opened will have to be immediately closed as soon as control reaches<br>
> the 'catch' function (because of the runResourceT). The only way around this<br>
> is to put the bind inside the catch block with some default sink, but this<br>
> loses all of the constant-memory benefits of using a conduit in the first<br>
> place.<br>
> The function that will be calling 'catch' operates as a State monad. Cookie<br>
> state must be updated both before and after making the request (both inside<br>
> the 'catch' block and outside). I could pass around an updated cookie jar<br>
> out of the IO monad as an extra entry in a tuple, but this defeats the<br>
> purpose of using a State monad in the first place. This kind of programming<br>
> is very ugly.<br>
><br>
> I see two solutions to these problems:<br>
><br>
> Make 'http' not throw a StatusCodeException if the _original_ redirection<br>
> count = 0<br>
> Make my internal module call the un-exported 'httpRaw' function. This has<br>
> the problem that it doen't solve the problem for anyone else trying to use<br>
> http-conduit; it only solves the problem for me. Perhaps this could be<br>
> alleviated by exporting httpRaw.<br>
><br>
> What do you think?<br>
> --Myles<br>
><br>
> On Tue, Feb 7, 2012 at 12:35 AM, Myles C. Maxfield<br>
> <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>><br>
>> Alright. I'll issue another pull request to you when it's done (expect in<br>
>> a couple weeks).<br>
>><br>
>> Thanks for your input so far, Aristid and Michael.<br>
>><br>
>> @Chris Wong: Do you want to talk about the Suffix List stuff some time?<br>
>><br>
>> --Myles<br>
>><br>
>><br>
>> On Mon, Feb 6, 2012 at 10:14 PM, Michael Snoyman <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>><br>
>> wrote:<br>
>>><br>
>>> +1<br>
>>><br>
>>> On Mon, Feb 6, 2012 at 11:16 PM, Aristid Breitkreuz<br>
>>> <<a href="mailto:aristidb@googlemail.com">aristidb@googlemail.com</a>> wrote:<br>
>>> > I would say: if it adds no package dependencies, put it right in.<br>
>>> ><br>
>>> > Aristid<br>
>>> ><br>
>>> > Am 06.02.2012 22:09 schrieb "Myles C. Maxfield"<br>
>>> > <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>>:<br>
>>> >><br>
>>> >> After all these commits have been flying around, I have yet another<br>
>>> >> question:<br>
>>> >><br>
>>> >> the 'HTTP' package defines Network.Browser which is a State monad<br>
>>> >> which<br>
>>> >> keeps state about a "browser" (i.e. a cookie jar, a proxy, redirection<br>
>>> >> parameters, etc.) It would be pretty straightforward to implement this<br>
>>> >> kind<br>
>>> >> of functionality on top of http-conduit.<br>
>>> >><br>
>>> >> I was originally going to do it and release it as its own package, but<br>
>>> >> it<br>
>>> >> may be beneficial to add such a module to the existing http-conduit<br>
>>> >> package.<br>
>>> >> Should I add it in to the existing package, or release it as its own<br>
>>> >> package?<br>
>>> >><br>
>>> >> --Myles<br>
>>> >><br>
>>> >> On Mon, Feb 6, 2012 at 12:15 AM, Michael Snoyman <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>><br>
>>> >> wrote:<br>
>>> >>><br>
>>> >>> Just an FYI for everyone: Myles sent an (incredibly thorough) pull<br>
>>> >>> request to handle cookies:<br>
>>> >>><br>
>>> >>> <a href="https://github.com/snoyberg/http-conduit/pull/13" target="_blank">https://github.com/snoyberg/http-conduit/pull/13</a><br>
>>> >>><br>
>>> >>> Thanks!<br>
>>> >>><br>
>>> >>> On Sun, Feb 5, 2012 at 8:20 AM, Myles C. Maxfield<br>
>>> >>> <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> > 1. The spec defines a grammar for the attributes. They're in<br>
>>> >>> > uppercase.<br>
>>> >>> > 2. Yes - 1.3 is the first version that lists DiffTime as an<br>
>>> >>> > instance of<br>
>>> >>> > RealFrac (so I can use the 'floor' function to pull out the number<br>
>>> >>> > of<br>
>>> >>> > seconds to render it)<br>
>>> >>> > 3. I'll see what I can do.<br>
>>> >>> ><br>
>>> >>> > --Myles<br>
>>> >>> ><br>
>>> >>> ><br>
>>> >>> > On Sat, Feb 4, 2012 at 9:06 PM, Michael Snoyman<br>
>>> >>> > <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>><br>
>>> >>> > wrote:<br>
>>> >>> >><br>
>>> >>> >> Looks good, a few questions/requests:<br>
>>> >>> >><br>
>>> >>> >> 1. Is there a reason to upper-case all the attributes?<br>
>>> >>> >> 2. Is the time >= 1.3 a requirements? Because that can cause a lot<br>
>>> >>> >> of<br>
>>> >>> >> trouble for people.<br>
>>> >>> >> 3. Can you send the patch as a Github pull request? It's easier to<br>
>>> >>> >> track that way.<br>
>>> >>> >><br>
>>> >>> >> Michael<br>
>>> >>> >><br>
>>> >>> >> On Sat, Feb 4, 2012 at 1:21 AM, Myles C. Maxfield<br>
>>> >>> >> <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> >> > Here is the patch to Web.Cookie. I didn't modify the tests at<br>
>>> >>> >> > all<br>
>>> >>> >> > because<br>
>>> >>> >> > they were already broken - they looked like they hadn't been<br>
>>> >>> >> > updated<br>
>>> >>> >> > since<br>
>>> >>> >> > SetCookie only had 5 parameters. I did verify by hand that the<br>
>>> >>> >> > patch<br>
>>> >>> >> > works,<br>
>>> >>> >> > though.<br>
>>> >>> >> ><br>
>>> >>> >> > Thanks,<br>
>>> >>> >> > Myles<br>
>>> >>> >> ><br>
>>> >>> >> ><br>
>>> >>> >> > On Thu, Feb 2, 2012 at 11:26 PM, Myles C. Maxfield<br>
>>> >>> >> > <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> >> >><br>
>>> >>> >> >> Alright, I'll make a small patch that adds 2 fields to<br>
>>> >>> >> >> SetCookie:<br>
>>> >>> >> >> setCookieMaxAge :: Maybe DiffTime<br>
>>> >>> >> >> setCookieSecureOnly :: Bool<br>
>>> >>> >> >><br>
>>> >>> >> >> I've also gotten started on those cookie functions. I'm<br>
>>> >>> >> >> currently<br>
>>> >>> >> >> writing<br>
>>> >>> >> >> tests for them.<br>
>>> >>> >> >><br>
>>> >>> >> >> @Chris: The best advice I can give is that Chrome (what I'm<br>
>>> >>> >> >> using<br>
>>> >>> >> >> as a<br>
>>> >>> >> >> source on all this) has the data baked into a .cc file.<br>
>>> >>> >> >> However,<br>
>>> >>> >> >> they<br>
>>> >>> >> >> have<br>
>>> >>> >> >> directions in a README and a script which will parse the list<br>
>>> >>> >> >> and<br>
>>> >>> >> >> generate<br>
>>> >>> >> >> that source file. I recommend doing this. That way, the Haskell<br>
>>> >>> >> >> module<br>
>>> >>> >> >> would<br>
>>> >>> >> >> have 2 source files: one file that reads the list and generates<br>
>>> >>> >> >> the<br>
>>> >>> >> >> second<br>
>>> >>> >> >> file, which is a very large source file that contains each<br>
>>> >>> >> >> element<br>
>>> >>> >> >> in<br>
>>> >>> >> >> the<br>
>>> >>> >> >> list. The list should export `elem`-type queries. I'm not quite<br>
>>> >>> >> >> sure<br>
>>> >>> >> >> how to<br>
>>> >>> >> >> handle wildcards that appear in the list - that part is up to<br>
>>> >>> >> >> you.<br>
>>> >>> >> >> Thanks<br>
>>> >>> >> >> for helping out with this :]<br>
>>> >>> >> >><br>
>>> >>> >> >> --Myles<br>
>>> >>> >> >><br>
>>> >>> >> >><br>
>>> >>> >> >> On Thu, Feb 2, 2012 at 10:53 PM, Michael Snoyman<br>
>>> >>> >> >> <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>><br>
>>> >>> >> >> wrote:<br>
>>> >>> >> >>><br>
>>> >>> >> >>> Looks good to me too. I agree with Aristid: let's make the<br>
>>> >>> >> >>> change<br>
>>> >>> >> >>> to<br>
>>> >>> >> >>> cookie itself. Do you want to send a pull request? I'm also<br>
>>> >>> >> >>> considering making the SetCookie constructor hidden like we<br>
>>> >>> >> >>> have<br>
>>> >>> >> >>> for<br>
>>> >>> >> >>> Request, so that if in the future we realize we need to add<br>
>>> >>> >> >>> some<br>
>>> >>> >> >>> other<br>
>>> >>> >> >>> settings, it doesn't break the API.<br>
>>> >>> >> >>><br>
>>> >>> >> >>> Chris: I would recommend compiling it into the module. Best<br>
>>> >>> >> >>> bet<br>
>>> >>> >> >>> would<br>
>>> >>> >> >>> likely being converting the source file to Haskell source.<br>
>>> >>> >> >>><br>
>>> >>> >> >>> Michael<br>
>>> >>> >> >>><br>
>>> >>> >> >>> On Fri, Feb 3, 2012 at 6:32 AM, Myles C. Maxfield<br>
>>> >>> >> >>> <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> >> >>> > Alright. After reading the spec, I have these questions /<br>
>>> >>> >> >>> > concerns:<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > The spec supports the "Max-Age" cookie attribute, which<br>
>>> >>> >> >>> > Web.Cookies<br>
>>> >>> >> >>> > doesn't.<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > I see two possible solutions to this. The first is to have<br>
>>> >>> >> >>> > parseSetCookie<br>
>>> >>> >> >>> > take a UTCTime as an argument which will represent the<br>
>>> >>> >> >>> > current<br>
>>> >>> >> >>> > time<br>
>>> >>> >> >>> > so<br>
>>> >>> >> >>> > it<br>
>>> >>> >> >>> > can populate the setCookieExpires field by adding the<br>
>>> >>> >> >>> > Max-Age<br>
>>> >>> >> >>> > attribute<br>
>>> >>> >> >>> > to<br>
>>> >>> >> >>> > the current time. Alternatively, that function can return an<br>
>>> >>> >> >>> > IO<br>
>>> >>> >> >>> > SetCookie so<br>
>>> >>> >> >>> > it can ask for the current time by itself (which I think is<br>
>>> >>> >> >>> > inferior<br>
>>> >>> >> >>> > to<br>
>>> >>> >> >>> > taking the current time as an argument). Note that the spec<br>
>>> >>> >> >>> > says<br>
>>> >>> >> >>> > to<br>
>>> >>> >> >>> > prefer<br>
>>> >>> >> >>> > Max-Age over Expires.<br>
>>> >>> >> >>> > Add a field to SetCookie of type Maybe DiffTime which<br>
>>> >>> >> >>> > represents<br>
>>> >>> >> >>> > the<br>
>>> >>> >> >>> > Max-Age<br>
>>> >>> >> >>> > attribute<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Cookie code should be aware of the Public Suffix List as a<br>
>>> >>> >> >>> > part<br>
>>> >>> >> >>> > of<br>
>>> >>> >> >>> > its<br>
>>> >>> >> >>> > domain verification. The cookie code only needs to be able<br>
>>> >>> >> >>> > to<br>
>>> >>> >> >>> > tell<br>
>>> >>> >> >>> > if a<br>
>>> >>> >> >>> > specific string is in the list (W.Ascii -> Bool)<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > I propose making an entirely unrelated package,<br>
>>> >>> >> >>> > public-suffix-list,<br>
>>> >>> >> >>> > with a<br>
>>> >>> >> >>> > module Network.PublicSuffixList, which will expose this<br>
>>> >>> >> >>> > function, as<br>
>>> >>> >> >>> > well as<br>
>>> >>> >> >>> > functions about parsing the list itself. Thoughts?<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Web.Cookie doesn't have a "secure-only" attribute. Adding<br>
>>> >>> >> >>> > one in<br>
>>> >>> >> >>> > is<br>
>>> >>> >> >>> > straightforward enough.<br>
>>> >>> >> >>> > The spec describes cookies as a property of HTTP, not of the<br>
>>> >>> >> >>> > World<br>
>>> >>> >> >>> > Wide<br>
>>> >>> >> >>> > Web.<br>
>>> >>> >> >>> > Perhaps "Web.Cookie" should be renamed? Just a thought; it<br>
>>> >>> >> >>> > doesn't<br>
>>> >>> >> >>> > really<br>
>>> >>> >> >>> > matter to me.<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > As for Network.HTTP.Conduit.Cookie, the spec describes in<br>
>>> >>> >> >>> > section<br>
>>> >>> >> >>> > 5.3<br>
>>> >>> >> >>> > "Storage Model" what fields a Cookie has. Here is my<br>
>>> >>> >> >>> > proposal<br>
>>> >>> >> >>> > for<br>
>>> >>> >> >>> > the<br>
>>> >>> >> >>> > functions it will expose:<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > receiveSetCookie :: SetCookie -> Req.Request m -> UTCTime -><br>
>>> >>> >> >>> > Bool -><br>
>>> >>> >> >>> > CookieJar -> CookieJar<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Runs the algorithm described in section 5.3 "Storage Model"<br>
>>> >>> >> >>> > The UTCTime is the current-time, the Bool is whether or not<br>
>>> >>> >> >>> > the<br>
>>> >>> >> >>> > caller<br>
>>> >>> >> >>> > is an<br>
>>> >>> >> >>> > HTTP-based API (as opposed to JavaScript or anything else)<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > updateCookieJar :: Res.Response a -> Req.Request m -><br>
>>> >>> >> >>> > UTCTime -><br>
>>> >>> >> >>> > CookieJar<br>
>>> >>> >> >>> > -> (CookieJar, Res.Response a)<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Applies "receiveSetCookie" to a Response. The output<br>
>>> >>> >> >>> > CookieJar<br>
>>> >>> >> >>> > is<br>
>>> >>> >> >>> > stripped<br>
>>> >>> >> >>> > of any Set-Cookie headers.<br>
>>> >>> >> >>> > Specifies "True" for the Bool in receiveSetCookie<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > computeCookieString :: Req.Request m -> CookieJar -> UTCTime<br>
>>> >>> >> >>> > -><br>
>>> >>> >> >>> > Bool<br>
>>> >>> >> >>> > -><br>
>>> >>> >> >>> > (W.Ascii, CookieJar)<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Runs the algorithm described in section 5.4 "The Cookie<br>
>>> >>> >> >>> > Header"<br>
>>> >>> >> >>> > The UTCTime and Bool are the same as in receiveSetCookie<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > insertCookiesIntoRequest :: Req.Request m -> CookieJar -><br>
>>> >>> >> >>> > UTCTime -><br>
>>> >>> >> >>> > (Req.Request m, CookieJar)<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Applies "computeCookieString" to a Request. The output<br>
>>> >>> >> >>> > cookie<br>
>>> >>> >> >>> > jar<br>
>>> >>> >> >>> > has<br>
>>> >>> >> >>> > updated last-accessed-times.<br>
>>> >>> >> >>> > Specifies "True" for the Bool in computeCookieString<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > evictExpiredCookies :: CookieJar -> UTCTime -> CookieJar<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Runs the algorithm described in the last part of section 5.3<br>
>>> >>> >> >>> > "Storage<br>
>>> >>> >> >>> > Model"<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > This will make the relevant part of 'http' look like:<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > go count req'' cookie_jar'' = do<br>
>>> >>> >> >>> > now <- liftIO $ getCurrentTime<br>
>>> >>> >> >>> > let (req', cookie_jar') = insertCookiesIntoRequest<br>
>>> >>> >> >>> > req''<br>
>>> >>> >> >>> > (evictExpiredCookies cookie_jar'' now) now<br>
>>> >>> >> >>> > res' <- httpRaw req' manager<br>
>>> >>> >> >>> > let (cookie_jar, res) = updateCookieJar res' req'<br>
>>> >>> >> >>> > now<br>
>>> >>> >> >>> > cookie_jar'<br>
>>> >>> >> >>> > case getRedirectedRequest req' (responseHeaders res)<br>
>>> >>> >> >>> > (W.statusCode<br>
>>> >>> >> >>> > (statusCode res)) of<br>
>>> >>> >> >>> > Just req -> go (count - 1) req cookie_jar<br>
>>> >>> >> >>> > Nothing -> return res<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > I plan to not allow for a user-supplied cookieFilter<br>
>>> >>> >> >>> > function.<br>
>>> >>> >> >>> > If<br>
>>> >>> >> >>> > they<br>
>>> >>> >> >>> > want<br>
>>> >>> >> >>> > that functionality, they can re-implement the<br>
>>> >>> >> >>> > redirection-following<br>
>>> >>> >> >>> > logic.<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Any thoughts on any of this?<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > Thanks,<br>
>>> >>> >> >>> > Myles<br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> > On Wed, Feb 1, 2012 at 5:19 PM, Myles C. Maxfield<br>
>>> >>> >> >>> > <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>><br>
>>> >>> >> >>> > wrote:<br>
>>> >>> >> >>> >><br>
>>> >>> >> >>> >> Nope. I'm not. The RFC is very explicit about how to handle<br>
>>> >>> >> >>> >> cookies.<br>
>>> >>> >> >>> >> As<br>
>>> >>> >> >>> >> soon as I'm finished making sense of it (in terms of<br>
>>> >>> >> >>> >> Haskell)<br>
>>> >>> >> >>> >> I'll<br>
>>> >>> >> >>> >> send<br>
>>> >>> >> >>> >> another proposal email.<br>
>>> >>> >> >>> >><br>
>>> >>> >> >>> >> On Feb 1, 2012 3:25 AM, "Michael Snoyman"<br>
>>> >>> >> >>> >> <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>><br>
>>> >>> >> >>> >> wrote:<br>
>>> >>> >> >>> >>><br>
>>> >>> >> >>> >>> You mean you're *not* making this proposal?<br>
>>> >>> >> >>> >>><br>
>>> >>> >> >>> >>> On Wed, Feb 1, 2012 at 7:30 AM, Myles C. Maxfield<br>
>>> >>> >> >>> >>> <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> >> >>> >>> > Well, this is embarrassing. Please disregard my previous<br>
>>> >>> >> >>> >>> > email.<br>
>>> >>> >> >>> >>> > I<br>
>>> >>> >> >>> >>> > should<br>
>>> >>> >> >>> >>> > learn to read the RFC *before* submitting proposals.<br>
>>> >>> >> >>> >>> ><br>
>>> >>> >> >>> >>> > --Myles<br>
>>> >>> >> >>> >>> ><br>
>>> >>> >> >>> >>> ><br>
>>> >>> >> >>> >>> > On Tue, Jan 31, 2012 at 6:37 PM, Myles C. Maxfield<br>
>>> >>> >> >>> >>> > <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Here are my initial ideas about supporting cookies.<br>
>>> >>> >> >>> >>> >> Note<br>
>>> >>> >> >>> >>> >> that<br>
>>> >>> >> >>> >>> >> I'm<br>
>>> >>> >> >>> >>> >> using<br>
>>> >>> >> >>> >>> >> Chrome for ideas since it's open source.<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Network/HTTP/Conduit/Cookies.hs file<br>
>>> >>> >> >>> >>> >> Exporting the following symbols:<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> type StuffedCookie = SetCookie<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> A regular SetCookie can have Nothing for its Domain and<br>
>>> >>> >> >>> >>> >> Path<br>
>>> >>> >> >>> >>> >> attributes. A<br>
>>> >>> >> >>> >>> >> StuffedCookie has to have these fields set.<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> type CookieJar = [StuffedCookie]<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Chrome's cookie jar is implemented as (the C++<br>
>>> >>> >> >>> >>> >> equivalent<br>
>>> >>> >> >>> >>> >> of)<br>
>>> >>> >> >>> >>> >> Map<br>
>>> >>> >> >>> >>> >> W.Ascii<br>
>>> >>> >> >>> >>> >> StuffedCookie. The key is the "eTLD+1" of the domain,<br>
>>> >>> >> >>> >>> >> so<br>
>>> >>> >> >>> >>> >> lookups<br>
>>> >>> >> >>> >>> >> for<br>
>>> >>> >> >>> >>> >> all<br>
>>> >>> >> >>> >>> >> cookies for a given domain are fast.<br>
>>> >>> >> >>> >>> >> I think I'll stay with just a list of StuffedCookies<br>
>>> >>> >> >>> >>> >> just<br>
>>> >>> >> >>> >>> >> to<br>
>>> >>> >> >>> >>> >> keep<br>
>>> >>> >> >>> >>> >> it<br>
>>> >>> >> >>> >>> >> simple. Perhaps a later revision can implement the<br>
>>> >>> >> >>> >>> >> faster<br>
>>> >>> >> >>> >>> >> map.<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> getRelevantCookies :: Request m -> CookieJar -> UTCTime<br>
>>> >>> >> >>> >>> >> -><br>
>>> >>> >> >>> >>> >> (CookieJar,<br>
>>> >>> >> >>> >>> >> Cookies)<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Gets all the cookies from the cookie jar that should be<br>
>>> >>> >> >>> >>> >> set<br>
>>> >>> >> >>> >>> >> for<br>
>>> >>> >> >>> >>> >> the<br>
>>> >>> >> >>> >>> >> given<br>
>>> >>> >> >>> >>> >> Request.<br>
>>> >>> >> >>> >>> >> The time argument is whatever "now" is (it's pulled out<br>
>>> >>> >> >>> >>> >> of<br>
>>> >>> >> >>> >>> >> the<br>
>>> >>> >> >>> >>> >> function so<br>
>>> >>> >> >>> >>> >> the function can remain pure and easily testable)<br>
>>> >>> >> >>> >>> >> The function will also remove expired cookies from the<br>
>>> >>> >> >>> >>> >> cookie<br>
>>> >>> >> >>> >>> >> jar<br>
>>> >>> >> >>> >>> >> (given<br>
>>> >>> >> >>> >>> >> what "now" is) and return the filtered cookie jar<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> putRelevantCookies :: Request m -> CookieJar -><br>
>>> >>> >> >>> >>> >> [StuffedCookie]<br>
>>> >>> >> >>> >>> >> -><br>
>>> >>> >> >>> >>> >> CookieJar<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Insert cookies from a server response into the cookie<br>
>>> >>> >> >>> >>> >> jar.<br>
>>> >>> >> >>> >>> >> The first argument is only used for checking to see<br>
>>> >>> >> >>> >>> >> which<br>
>>> >>> >> >>> >>> >> cookies<br>
>>> >>> >> >>> >>> >> are<br>
>>> >>> >> >>> >>> >> valid (which cookies match the requested domain, etc,<br>
>>> >>> >> >>> >>> >> so<br>
>>> >>> >> >>> >>> >> <a href="http://site1.com" target="_blank">site1.com</a><br>
>>> >>> >> >>> >>> >> can't set<br>
>>> >>> >> >>> >>> >> a cookie for <a href="http://site2.com" target="_blank">site2.com</a>)<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> stuffCookie :: Request m -> SetCookie -> StuffedCookie<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> If the SetCookie's fields are Nothing, fill them in<br>
>>> >>> >> >>> >>> >> given<br>
>>> >>> >> >>> >>> >> the<br>
>>> >>> >> >>> >>> >> Request<br>
>>> >>> >> >>> >>> >> from<br>
>>> >>> >> >>> >>> >> which it originated<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> getCookies :: Response a -> ([SetCookie], Response a)<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Pull cookies out of a server response. Return the<br>
>>> >>> >> >>> >>> >> response<br>
>>> >>> >> >>> >>> >> with<br>
>>> >>> >> >>> >>> >> the<br>
>>> >>> >> >>> >>> >> Set-Cookie headers filtered out<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> putCookies :: Request a -> Cookies -> Request a<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> A wrapper around renderCookies. Inserts some cookies<br>
>>> >>> >> >>> >>> >> into a<br>
>>> >>> >> >>> >>> >> request.<br>
>>> >>> >> >>> >>> >> Doesn't overwrite cookies that are already set in the<br>
>>> >>> >> >>> >>> >> request<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> These functions will be exported from<br>
>>> >>> >> >>> >>> >> Network.HTTP.Conduit<br>
>>> >>> >> >>> >>> >> as<br>
>>> >>> >> >>> >>> >> well, so<br>
>>> >>> >> >>> >>> >> callers can use them to re-implement redirection chains<br>
>>> >>> >> >>> >>> >> I won't implement a cookie filtering function (like<br>
>>> >>> >> >>> >>> >> what<br>
>>> >>> >> >>> >>> >> Network.Browser<br>
>>> >>> >> >>> >>> >> has)<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> If you want to have arbitrary handling of cookies,<br>
>>> >>> >> >>> >>> >> re-implement<br>
>>> >>> >> >>> >>> >> redirection following. It's not very difficult if you<br>
>>> >>> >> >>> >>> >> use<br>
>>> >>> >> >>> >>> >> the<br>
>>> >>> >> >>> >>> >> API<br>
>>> >>> >> >>> >>> >> provided,<br>
>>> >>> >> >>> >>> >> and the 'http' function is open source so you can use<br>
>>> >>> >> >>> >>> >> that<br>
>>> >>> >> >>> >>> >> as a<br>
>>> >>> >> >>> >>> >> reference.<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> I will implement the functions according to RFC 6265<br>
>>> >>> >> >>> >>> >> I will also need to write the following functions.<br>
>>> >>> >> >>> >>> >> Should<br>
>>> >>> >> >>> >>> >> they<br>
>>> >>> >> >>> >>> >> also be<br>
>>> >>> >> >>> >>> >> exported?<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> canonicalizeDomain :: W.Ascii -> W.Ascii<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> turns "..a.b.c..d.com..." to "<a href="http://a.b.c.d.com" target="_blank">a.b.c.d.com</a>"<br>
>>> >>> >> >>> >>> >> Technically necessary for domain matching (Chrome does<br>
>>> >>> >> >>> >>> >> it)<br>
>>> >>> >> >>> >>> >> Perhaps unnecessary for a first pass? Perhaps we can<br>
>>> >>> >> >>> >>> >> trust<br>
>>> >>> >> >>> >>> >> users<br>
>>> >>> >> >>> >>> >> for<br>
>>> >>> >> >>> >>> >> now?<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> domainMatches :: W.Ascii -> W.Ascii -> Maybe W.Ascii<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Does the first domain match against the second domain?<br>
>>> >>> >> >>> >>> >> If so, return the prefix of the first that isn't in the<br>
>>> >>> >> >>> >>> >> second<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> pathMatches :: W.Ascii -> W.Ascii -> Bool<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Do the paths match?<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> In order to implement domain matching, I have to have<br>
>>> >>> >> >>> >>> >> knowledge<br>
>>> >>> >> >>> >>> >> of<br>
>>> >>> >> >>> >>> >> the Public Suffix List so I know that<br>
>>> >>> >> >>> >>> >> <a href="http://sub1.sub2.pvt.k12.wy.us" target="_blank">sub1.sub2.pvt.k12.wy.us</a><br>
>>> >>> >> >>> >>> >> can<br>
>>> >>> >> >>> >>> >> set<br>
>>> >>> >> >>> >>> >> a<br>
>>> >>> >> >>> >>> >> cookie for <a href="http://sub2.pvt.k12.wy.us" target="_blank">sub2.pvt.k12.wy.us</a> but not for <a href="http://k12.wy.us" target="_blank">k12.wy.us</a><br>
>>> >>> >> >>> >>> >> (because<br>
>>> >>> >> >>> >>> >> <a href="http://pvt.k12.wy.us" target="_blank">pvt.k12.wy.us</a><br>
>>> >>> >> >>> >>> >> is a "suffix"). There are a variety of ways to<br>
>>> >>> >> >>> >>> >> implement<br>
>>> >>> >> >>> >>> >> this.<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> As far as I can tell, Chrome does it by using a script<br>
>>> >>> >> >>> >>> >> (which a<br>
>>> >>> >> >>> >>> >> human<br>
>>> >>> >> >>> >>> >> periodically runs) which parses the list at creates a<br>
>>> >>> >> >>> >>> >> .cc<br>
>>> >>> >> >>> >>> >> file<br>
>>> >>> >> >>> >>> >> that is<br>
>>> >>> >> >>> >>> >> included in the build.<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> I might be wrong about the execution of the script; it<br>
>>> >>> >> >>> >>> >> might be<br>
>>> >>> >> >>> >>> >> a<br>
>>> >>> >> >>> >>> >> build<br>
>>> >>> >> >>> >>> >> step. If it is a build step, however, it is suspicious<br>
>>> >>> >> >>> >>> >> that<br>
>>> >>> >> >>> >>> >> a<br>
>>> >>> >> >>> >>> >> build<br>
>>> >>> >> >>> >>> >> target<br>
>>> >>> >> >>> >>> >> would try to download a file...<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Any more elegant ideas?<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Feedback on any/all of the above would be very helpful<br>
>>> >>> >> >>> >>> >> before I<br>
>>> >>> >> >>> >>> >> go<br>
>>> >>> >> >>> >>> >> off<br>
>>> >>> >> >>> >>> >> into the weeds on this project.<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> Thanks,<br>
>>> >>> >> >>> >>> >> Myles C. Maxfield<br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >> On Sat, Jan 28, 2012 at 8:17 PM, Michael Snoyman<br>
>>> >>> >> >>> >>> >> <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>><br>
>>> >>> >> >>> >>> >> wrote:<br>
>>> >>> >> >>> >>> >>><br>
>>> >>> >> >>> >>> >>> Thanks, looks great! I've merged it into the Github<br>
>>> >>> >> >>> >>> >>> tree.<br>
>>> >>> >> >>> >>> >>><br>
>>> >>> >> >>> >>> >>> On Sat, Jan 28, 2012 at 8:36 PM, Myles C. Maxfield<br>
>>> >>> >> >>> >>> >>> <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> >> >>> >>> >>> > Ah, yes, you're completely right. I completely agree<br>
>>> >>> >> >>> >>> >>> > that<br>
>>> >>> >> >>> >>> >>> > moving<br>
>>> >>> >> >>> >>> >>> > the<br>
>>> >>> >> >>> >>> >>> > function into the Maybe monad increases readability.<br>
>>> >>> >> >>> >>> >>> > This<br>
>>> >>> >> >>> >>> >>> > kind<br>
>>> >>> >> >>> >>> >>> > of<br>
>>> >>> >> >>> >>> >>> > function<br>
>>> >>> >> >>> >>> >>> > is what the Maybe monad was designed for.<br>
>>> >>> >> >>> >>> >>> ><br>
>>> >>> >> >>> >>> >>> > Here is a revised patch.<br>
>>> >>> >> >>> >>> >>> ><br>
>>> >>> >> >>> >>> >>> ><br>
>>> >>> >> >>> >>> >>> > On Sat, Jan 28, 2012 at 8:28 AM, Michael Snoyman<br>
>>> >>> >> >>> >>> >>> > <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>><br>
>>> >>> >> >>> >>> >>> > wrote:<br>
>>> >>> >> >>> >>> >>> >><br>
>>> >>> >> >>> >>> >>> >> On Sat, Jan 28, 2012 at 1:20 AM, Myles C. Maxfield<br>
>>> >>> >> >>> >>> >>> >> <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> >> >>> >>> >>> >> > the fromJust should never fail, beceause of the<br>
>>> >>> >> >>> >>> >>> >> > guard<br>
>>> >>> >> >>> >>> >>> >> > statement:<br>
>>> >>> >> >>> >>> >>> >> ><br>
>>> >>> >> >>> >>> >>> >> > | 300 <= code && code < 400 && isJust l'' &&<br>
>>> >>> >> >>> >>> >>> >> > isJust<br>
>>> >>> >> >>> >>> >>> >> > l' =<br>
>>> >>> >> >>> >>> >>> >> > Just $<br>
>>> >>> >> >>> >>> >>> >> > req<br>
>>> >>> >> >>> >>> >>> >> ><br>
>>> >>> >> >>> >>> >>> >> > Because of the order of the && operators, it will<br>
>>> >>> >> >>> >>> >>> >> > only<br>
>>> >>> >> >>> >>> >>> >> > evaluate<br>
>>> >>> >> >>> >>> >>> >> > fromJust<br>
>>> >>> >> >>> >>> >>> >> > after it makes sure that the argument isJust.<br>
>>> >>> >> >>> >>> >>> >> > That<br>
>>> >>> >> >>> >>> >>> >> > function<br>
>>> >>> >> >>> >>> >>> >> > in<br>
>>> >>> >> >>> >>> >>> >> > particular<br>
>>> >>> >> >>> >>> >>> >> > shouldn't throw any exceptions - it should only<br>
>>> >>> >> >>> >>> >>> >> > return<br>
>>> >>> >> >>> >>> >>> >> > Nothing.<br>
>>> >>> >> >>> >>> >>> >> ><br>
>>> >>> >> >>> >>> >>> >> > Knowing that, I don't quite think I understand<br>
>>> >>> >> >>> >>> >>> >> > what<br>
>>> >>> >> >>> >>> >>> >> > your<br>
>>> >>> >> >>> >>> >>> >> > concern<br>
>>> >>> >> >>> >>> >>> >> > is.<br>
>>> >>> >> >>> >>> >>> >> > Can<br>
>>> >>> >> >>> >>> >>> >> > you<br>
>>> >>> >> >>> >>> >>> >> > elaborate?<br>
>>> >>> >> >>> >>> >>> >><br>
>>> >>> >> >>> >>> >>> >> You're right, but I had to squint really hard to<br>
>>> >>> >> >>> >>> >>> >> prove<br>
>>> >>> >> >>> >>> >>> >> to<br>
>>> >>> >> >>> >>> >>> >> myself<br>
>>> >>> >> >>> >>> >>> >> that<br>
>>> >>> >> >>> >>> >>> >> you're right. That's the kind of code that could<br>
>>> >>> >> >>> >>> >>> >> easily<br>
>>> >>> >> >>> >>> >>> >> be<br>
>>> >>> >> >>> >>> >>> >> broken<br>
>>> >>> >> >>> >>> >>> >> in<br>
>>> >>> >> >>> >>> >>> >> future updates by an unwitting maintainer (e.g.,<br>
>>> >>> >> >>> >>> >>> >> me).<br>
>>> >>> >> >>> >>> >>> >> To<br>
>>> >>> >> >>> >>> >>> >> protect<br>
>>> >>> >> >>> >>> >>> >> the<br>
>>> >>> >> >>> >>> >>> >> world from me, I'd prefer if the code didn't have<br>
>>> >>> >> >>> >>> >>> >> the<br>
>>> >>> >> >>> >>> >>> >> fromJust.<br>
>>> >>> >> >>> >>> >>> >> This<br>
>>> >>> >> >>> >>> >>> >> might be a good place to leverage the Monad<br>
>>> >>> >> >>> >>> >>> >> instance of<br>
>>> >>> >> >>> >>> >>> >> Maybe.<br>
>>> >>> >> >>> >>> >>> >><br>
>>> >>> >> >>> >>> >>> >> Michael<br>
>>> >>> >> >>> >>> >>> ><br>
>>> >>> >> >>> >>> >>> ><br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> >><br>
>>> >>> >> >>> >>> ><br>
>>> >>> >> >>> ><br>
>>> >>> >> >>> ><br>
>>> >>> >> >><br>
>>> >>> >> >><br>
>>> >>> >> ><br>
>>> >>> ><br>
>>> >>> ><br>
>>> >><br>
>>> >><br>
>>> >><br>
>>> >> _______________________________________________<br>
>>> >> Haskell-Cafe mailing list<br>
>>> >> <a href="mailto:Haskell-Cafe@haskell.org">Haskell-Cafe@haskell.org</a><br>
>>> >> <a href="http://www.haskell.org/mailman/listinfo/haskell-cafe" target="_blank">http://www.haskell.org/mailman/listinfo/haskell-cafe</a><br>
>>> >><br>
>>> ><br>
>><br>
>><br>
><br>
</div></div></blockquote></div><br>