<p>Sounds good. I think the nicest way to handle maxage would be changing SetCookie, not handling it at parsing time.</p>
<p>Aristid </p>
<div class="gmail_quote">Am 03.02.2012 05:35 schrieb "Myles C. Maxfield" <<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>>:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
Alright. After reading the spec, I have these questions / concerns:<div><ul><li>The spec supports the "Max-Age" cookie attribute, which Web.Cookies doesn't.</li><ul><li>I see two possible solutions to this. The first is to have parseSetCookie take a UTCTime as an argument which will represent the current time so it can populate the setCookieExpires field by adding the Max-Age attribute to the current time. Alternatively, that function can return an IO SetCookie so it can ask for the current time by itself (which I think is inferior to taking the current time as an argument). Note that the spec says to prefer Max-Age over Expires.</li>
<li>Add a field to SetCookie of type Maybe DiffTime which represents the Max-Age attribute</li></ul><li>Cookie code should be aware of the <a href="http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat" target="_blank">Public Suffix List</a> as a part of its domain verification. The cookie code only needs to be able to tell if a specific string is in the list (W.Ascii -> Bool)</li>
<ul><li>I propose making an entirely unrelated package, public-suffix-list, with a module Network.PublicSuffixList, which will expose this function, as well as functions about parsing the list itself. Thoughts?</li></ul>
<li>
Web.Cookie doesn't have a "secure-only" attribute. Adding one in is straightforward enough.</li><li>The spec describes cookies as a property of HTTP, not of the World Wide Web. Perhaps "Web.Cookie" should be renamed? Just a thought; it doesn't really matter to me.</li>
</ul>As for Network.HTTP.Conduit.Cookie, the spec describes in section 5.3 "Storage Model" what fields a Cookie has. Here is my proposal for the functions it will expose:</div><div><ul><li>receiveSetCookie :: SetCookie -> Req.Request m -> UTCTime -> Bool -> CookieJar -> CookieJar</li>
<ul><li>Runs the algorithm described in section 5.3 "Storage Model"</li><li>The UTCTime is the current-time, the Bool is whether or not the caller is an HTTP-based API (as opposed to JavaScript or anything else)</li>
</ul><li>updateCookieJar :: Res.Response a -> Req.Request m -> UTCTime -> CookieJar -> (CookieJar, Res.Response a)</li><ul><li>Applies "receiveSetCookie" to a Response. The output CookieJar is stripped of any Set-Cookie headers.</li>
<li>Specifies "True" for the Bool in receiveSetCookie</li></ul><li>computeCookieString :: Req.Request m -> CookieJar -> UTCTime -> Bool -> (W.Ascii, CookieJar)</li><ul><li>Runs the algorithm described in section 5.4 "The Cookie Header"</li>
<li>The UTCTime and Bool are the same as in receiveSetCookie</li></ul><li>insertCookiesIntoRequest :: Req.Request m -> CookieJar -> UTCTime -> (Req.Request m, CookieJar)</li><ul><li>Applies "computeCookieString" to a Request. The output cookie jar has updated last-accessed-times.</li>
<li>Specifies "True" for the Bool in computeCookieString</li></ul><li>evictExpiredCookies :: CookieJar -> UTCTime -> CookieJar</li><ul><li>Runs the algorithm described in the last part of section 5.3 "Storage Model"</li>
</ul></ul><div>This will make the relevant part of 'http' look like:</div><div><br></div><div><div> go count req'' cookie_jar'' = do</div><div> now <- liftIO $ getCurrentTime</div><div>
let (req', cookie_jar') = insertCookiesIntoRequest req'' (evictExpiredCookies cookie_jar'' now) now</div><div> res' <- httpRaw req' manager</div><div> let (cookie_jar, res) = updateCookieJar res' req' now cookie_jar'</div>
<div> case getRedirectedRequest req' (responseHeaders res) (W.statusCode (statusCode res)) of</div><div> Just req -> go (count - 1) req cookie_jar</div><div> Nothing -> return res</div>
</div><div><br></div><div>I plan to not allow for a user-supplied cookieFilter function. If they want that functionality, they can re-implement the redirection-following logic.</div><div><br></div><div>Any thoughts on any of this?</div>
<div><br></div><div>Thanks,<br>Myles</div><br><div class="gmail_quote">On Wed, Feb 1, 2012 at 5:19 PM, Myles C. Maxfield <span dir="ltr"><<a href="mailto:myles.maxfield@gmail.com" target="_blank">myles.maxfield@gmail.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><p>Nope. I'm not. The RFC is very explicit about how to handle cookies. As soon as I'm finished making sense of it (in terms of Haskell) I'll send another proposal email.</p>
<div><div>
<div class="gmail_quote">On Feb 1, 2012 3:25 AM, "Michael Snoyman" <<a href="mailto:michael@snoyman.com" target="_blank">michael@snoyman.com</a>> wrote:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
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" target="_blank">myles.maxfield@gmail.com</a>> wrote:<br>
> Well, this is embarrassing. Please disregard my previous email. I 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" target="_blank">myles.maxfield@gmail.com</a>> wrote:<br>
>><br>
>> Here are my initial ideas about supporting cookies. Note that I'm 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 Path 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++ equivalent of) Map W.Ascii<br>
>> StuffedCookie. The key is the "eTLD+1" of the domain, so lookups for all<br>
>> cookies for a given domain are fast.<br>
>> I think I'll stay with just a list of StuffedCookies just to keep it<br>
>> simple. Perhaps a later revision can implement the faster map.<br>
>><br>
>> getRelevantCookies :: Request m -> CookieJar -> UTCTime -> (CookieJar,<br>
>> Cookies)<br>
>><br>
>> Gets all the cookies from the cookie jar that should be set for the given<br>
>> Request.<br>
>> The time argument is whatever "now" is (it's pulled out of the function so<br>
>> the function can remain pure and easily testable)<br>
>> The function will also remove expired cookies from the cookie jar (given<br>
>> what "now" is) and return the filtered cookie jar<br>
>><br>
>> putRelevantCookies :: Request m -> CookieJar -> [StuffedCookie] -><br>
>> CookieJar<br>
>><br>
>> Insert cookies from a server response into the cookie jar.<br>
>> The first argument is only used for checking to see which cookies are<br>
>> valid (which cookies match the requested domain, etc, so <a href="http://site1.com" target="_blank">site1.com</a> 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 given the Request from<br>
>> which it originated<br>
>><br>
>> getCookies :: Response a -> ([SetCookie], Response a)<br>
>><br>
>> Pull cookies out of a server response. Return the response with the<br>
>> Set-Cookie headers filtered out<br>
>><br>
>> putCookies :: Request a -> Cookies -> Request a<br>
>><br>
>> A wrapper around renderCookies. Inserts some cookies into a request.<br>
>> Doesn't overwrite cookies that are already set in the request<br>
>><br>
>> These functions will be exported from Network.HTTP.Conduit as well, so<br>
>> callers can use them to re-implement redirection chains<br>
>> I won't implement a cookie filtering function (like what Network.Browser<br>
>> has)<br>
>><br>
>> If you want to have arbitrary handling of cookies, re-implement<br>
>> redirection following. It's not very difficult if you use the API provided,<br>
>> and the 'http' function is open source so you can use that as a reference.<br>
>><br>
>> I will implement the functions according to RFC 6265<br>
>> I will also need to write the following functions. Should they 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 it)<br>
>> Perhaps unnecessary for a first pass? Perhaps we can trust users for 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 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 knowledge of<br>
>> the Public Suffix List so I know that <a href="http://sub1.sub2.pvt.k12.wy.us" target="_blank">sub1.sub2.pvt.k12.wy.us</a> can set 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> (because <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 implement this.<br>
>><br>
>> As far as I can tell, Chrome does it by using a script (which a human<br>
>> periodically runs) which parses the list at creates a .cc file that is<br>
>> included in the build.<br>
>><br>
>> I might be wrong about the execution of the script; it might be a build<br>
>> step. If it is a build step, however, it is suspicious that a build 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 before I go 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 <<a href="mailto:michael@snoyman.com" target="_blank">michael@snoyman.com</a>><br>
>> wrote:<br>
>>><br>
>>> Thanks, looks great! I've merged it into the Github tree.<br>
>>><br>
>>> On Sat, Jan 28, 2012 at 8:36 PM, Myles C. Maxfield<br>
>>> <<a href="mailto:myles.maxfield@gmail.com" target="_blank">myles.maxfield@gmail.com</a>> wrote:<br>
>>> > Ah, yes, you're completely right. I completely agree that moving the<br>
>>> > function into the Maybe monad increases readability. This kind 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 <<a href="mailto:michael@snoyman.com" target="_blank">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" target="_blank">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >> > the fromJust should never fail, beceause of the guard statement:<br>
>>> >> ><br>
>>> >> > | 300 <= code && code < 400 && isJust l'' && isJust l' = Just $<br>
>>> >> > req<br>
>>> >> ><br>
>>> >> > Because of the order of the && operators, it will only evaluate<br>
>>> >> > fromJust<br>
>>> >> > after it makes sure that the argument isJust. That function in<br>
>>> >> > particular<br>
>>> >> > shouldn't throw any exceptions - it should only return Nothing.<br>
>>> >> ><br>
>>> >> > Knowing that, I don't quite think I understand what your concern is.<br>
>>> >> > Can<br>
>>> >> > you<br>
>>> >> > elaborate?<br>
>>> >><br>
>>> >> You're right, but I had to squint really hard to prove to myself that<br>
>>> >> you're right. That's the kind of code that could easily be broken in<br>
>>> >> future updates by an unwitting maintainer (e.g., me). To protect the<br>
>>> >> world from me, I'd prefer if the code didn't have the fromJust. This<br>
>>> >> might be a good place to leverage the Monad instance of Maybe.<br>
>>> >><br>
>>> >> Michael<br>
>>> ><br>
>>> ><br>
>><br>
>><br>
><br>
</blockquote></div>
</div></div></blockquote></div><br></div>
<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></blockquote></div>