Alright, I'll make a small patch that adds 2 fields to SetCookie:<div>setCookieMaxAge :: Maybe DiffTime</div><div>setCookieSecureOnly :: Bool</div><div><br></div><div>I've also gotten started on those cookie functions. I'm currently writing tests for them.</div>
<div><br></div><div>@Chris: The best advice I can give is that Chrome (what I'm using as a source on all this) has the data baked into a .cc file. However, they have directions in a README and a script which will parse the list and generate that source file. I recommend doing this. That way, the Haskell module would have 2 source files: one file that reads the list and generates the second file, which is a very large source file that contains each element in the list. The list should export `elem`-type queries. I'm not quite sure how to handle wildcards that appear in the list - that part is up to you. Thanks for helping out with this :]</div>
<div><br></div><div>--Myles<br><br><div class="gmail_quote">On Thu, Feb 2, 2012 at 10:53 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">
Looks good to me too. I agree with Aristid: let's make the change to<br>
cookie itself. Do you want to send a pull request? I'm also<br>
considering making the SetCookie constructor hidden like we have for<br>
Request, so that if in the future we realize we need to add some other<br>
settings, it doesn't break the API.<br>
<br>
Chris: I would recommend compiling it into the module. Best bet 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>
<div class="HOEnZb"><div class="h5"><<a href="mailto:myles.maxfield@gmail.com">myles.maxfield@gmail.com</a>> wrote:<br>
> Alright. After reading the spec, I have these questions / concerns:<br>
><br>
> The spec supports the "Max-Age" cookie attribute, which Web.Cookies doesn't.<br>
><br>
> I see two possible solutions to this. The first is to have parseSetCookie<br>
> take a UTCTime as an argument which will represent the current time so it<br>
> can populate the setCookieExpires field by adding the Max-Age attribute to<br>
> the current time. Alternatively, that function can return an IO SetCookie so<br>
> it can ask for the current time by itself (which I think is inferior to<br>
> taking the current time as an argument). Note that the spec says to prefer<br>
> Max-Age over Expires.<br>
> Add a field to SetCookie of type Maybe DiffTime which represents the Max-Age<br>
> attribute<br>
><br>
> Cookie code should be aware of the Public Suffix List as a part of its<br>
> domain verification. The cookie code only needs to be able to tell if a<br>
> specific string is in the list (W.Ascii -> Bool)<br>
><br>
> I propose making an entirely unrelated package, public-suffix-list, with a<br>
> module Network.PublicSuffixList, which will expose this function, as well as<br>
> functions about parsing the list itself. Thoughts?<br>
><br>
> Web.Cookie doesn't have a "secure-only" attribute. Adding one in is<br>
> straightforward enough.<br>
> The spec describes cookies as a property of HTTP, not of the World Wide Web.<br>
> Perhaps "Web.Cookie" should be renamed? Just a thought; it doesn't really<br>
> matter to me.<br>
><br>
> As for Network.HTTP.Conduit.Cookie, the spec describes in section 5.3<br>
> "Storage Model" what fields a Cookie has. Here is my proposal for the<br>
> functions it will expose:<br>
><br>
> receiveSetCookie :: SetCookie -> Req.Request m -> UTCTime -> 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 the caller is an<br>
> HTTP-based API (as opposed to JavaScript or anything else)<br>
><br>
> updateCookieJar :: Res.Response a -> Req.Request m -> UTCTime -> CookieJar<br>
> -> (CookieJar, Res.Response a)<br>
><br>
> Applies "receiveSetCookie" to a Response. The output CookieJar is stripped<br>
> of any Set-Cookie headers.<br>
> Specifies "True" for the Bool in receiveSetCookie<br>
><br>
> computeCookieString :: Req.Request m -> CookieJar -> UTCTime -> Bool -><br>
> (W.Ascii, CookieJar)<br>
><br>
> Runs the algorithm described in section 5.4 "The Cookie Header"<br>
> The UTCTime and Bool are the same as in receiveSetCookie<br>
><br>
> insertCookiesIntoRequest :: Req.Request m -> CookieJar -> UTCTime -><br>
> (Req.Request m, CookieJar)<br>
><br>
> Applies "computeCookieString" to a Request. The output cookie jar 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 "Storage 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 req''<br>
> (evictExpiredCookies cookie_jar'' now) now<br>
> res' <- httpRaw req' manager<br>
> let (cookie_jar, res) = updateCookieJar res' req' now cookie_jar'<br>
> case getRedirectedRequest req' (responseHeaders res) (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 function. If they want<br>
> that functionality, they can re-implement the redirection-following 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 <<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 cookies. As<br>
>> soon as I'm finished making sense of it (in terms of Haskell) I'll send<br>
>> another proposal email.<br>
>><br>
>> On Feb 1, 2012 3:25 AM, "Michael Snoyman" <<a href="mailto:michael@snoyman.com">michael@snoyman.com</a>> 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 email. 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. Note that 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 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++ equivalent of) Map<br>
>>> >> W.Ascii<br>
>>> >> StuffedCookie. The key is the "eTLD+1" of the domain, so lookups for<br>
>>> >> 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<br>
>>> >> given<br>
>>> >> Request.<br>
>>> >> The time argument is whatever "now" is (it's pulled out of the<br>
>>> >> function so<br>
>>> >> the function can remain pure and easily testable)<br>
>>> >> The function will also remove expired cookies from the cookie jar<br>
>>> >> (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><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 given the 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 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<br>
>>> >> 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<br>
>>> >> provided,<br>
>>> >> and the 'http' function is open source so you can use that 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. 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<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 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<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> (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 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<br>
>>> >> build<br>
>>> >> step. If it is a build step, however, it is suspicious that a 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 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">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">myles.maxfield@gmail.com</a>> wrote:<br>
>>> >>> > Ah, yes, you're completely right. I completely agree that moving<br>
>>> >>> > 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<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 guard statement:<br>
>>> >>> >> ><br>
>>> >>> >> > | 300 <= code && code < 400 && isJust l'' && isJust l' =<br>
>>> >>> >> > 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<br>
>>> >>> >> > is.<br>
>>> >>> >> > Can<br>
>>> >>> >> > you<br>
>>> >>> >> > elaborate?<br>
>>> >>> >><br>
>>> >>> >> You're right, but I had to squint really hard to prove to myself<br>
>>> >>> >> that<br>
>>> >>> >> you're right. That's the kind of code that could easily be broken<br>
>>> >>> >> in<br>
>>> >>> >> future updates by an unwitting maintainer (e.g., me). To protect<br>
>>> >>> >> the<br>
>>> >>> >> world from me, I'd prefer if the code didn't have the fromJust.<br>
>>> >>> >> This<br>
>>> >>> >> might be a good place to leverage the Monad instance of Maybe.<br>
>>> >>> >><br>
>>> >>> >> Michael<br>
>>> >>> ><br>
>>> >>> ><br>
>>> >><br>
>>> >><br>
>>> ><br>
><br>
><br>
</div></div></blockquote></div><br></div>