Well, this is embarrassing. Please disregard my previous email. I should learn to read the RFC *before* submitting proposals.

--Myles

On Tue, Jan 31, 2012 at 6:37 PM, Myles C. Maxfield <myles.maxfield@gmail.com> wrote:
Here are my initial ideas about supporting cookies. Note that I'm using Chrome for ideas since it's open source.
  • Network/HTTP/Conduit/Cookies.hs file
  • Exporting the following symbols:
    • type StuffedCookie = SetCookie
      • A regular SetCookie can have Nothing for its Domain and Path attributes. A StuffedCookie has to have these fields set.
    • type CookieJar = [StuffedCookie]
      • Chrome's cookie jar is implemented as (the C++ equivalent of) Map W.Ascii StuffedCookie. The key is the "eTLD+1" of the domain, so lookups for all cookies for a given domain are fast.
      • I think I'll stay with just a list of StuffedCookies just to keep it simple. Perhaps a later revision can implement the faster map.
    • getRelevantCookies :: Request m -> CookieJar -> UTCTime -> (CookieJar, Cookies)
      • Gets all the cookies from the cookie jar that should be set for the given Request.
      • The time argument is whatever "now" is (it's pulled out of the function so the function can remain pure and easily testable)
      • The function will also remove expired cookies from the cookie jar (given what "now" is) and return the filtered cookie jar
    • putRelevantCookies :: Request m -> CookieJar -> [StuffedCookie] -> CookieJar
      • Insert cookies from a server response into the cookie jar.
      • The first argument is only used for checking to see which cookies are valid (which cookies match the requested domain, etc, so site1.com can't set a cookie for site2.com)
    • stuffCookie :: Request m -> SetCookie -> StuffedCookie
      • If the SetCookie's fields are Nothing, fill them in given the Request from which it originated
    • getCookies :: Response a -> ([SetCookie], Response a)
      • Pull cookies out of a server response. Return the response with the Set-Cookie headers filtered out
    • putCookies :: Request a -> Cookies -> Request a
      • A wrapper around renderCookies. Inserts some cookies into a request.
      • Doesn't overwrite cookies that are already set in the request
  • These functions will be exported from Network.HTTP.Conduit as well, so callers can use them to re-implement redirection chains
  • I won't implement a cookie filtering function (like what Network.Browser has)
    • If you want to have arbitrary handling of cookies, re-implement redirection following. It's not very difficult if you use the API provided, and the 'http' function is open source so you can use that as a reference.
  • I will implement the functions according to RFC 6265
  • I will also need to write the following functions. Should they also be exported?
    • canonicalizeDomain :: W.Ascii -> W.Ascii
      • turns "..a.b.c..d.com..." to "a.b.c.d.com"
      • Technically necessary for domain matching (Chrome does it)
      • Perhaps unnecessary for a first pass? Perhaps we can trust users for now?
    • domainMatches :: W.Ascii -> W.Ascii -> Maybe W.Ascii
      • Does the first domain match against the second domain?
      • If so, return the prefix of the first that isn't in the second
    • pathMatches :: W.Ascii -> W.Ascii -> Bool
      • Do the paths match?
  • In order to implement domain matching, I have to have knowledge of the Public Suffix List so I know that sub1.sub2.pvt.k12.wy.us can set a cookie for sub2.pvt.k12.wy.us but not for k12.wy.us (because pvt.k12.wy.us is a "suffix"). There are a variety of ways to implement this.
    • As far as I can tell, Chrome does it by using a script (which a human periodically runs) which parses the list at creates a .cc file that is included in the build.
      • I might be wrong about the execution of the script; it might be a build step. If it is a build step, however, it is suspicious that a build target would try to download a file...
    • Any more elegant ideas?
Feedback on any/all of the above would be very helpful before I go off into the weeds on this project.

Thanks,
Myles C. Maxfield

On Sat, Jan 28, 2012 at 8:17 PM, Michael Snoyman <michael@snoyman.com> wrote:
Thanks, looks great! I've merged it into the Github tree.

On Sat, Jan 28, 2012 at 8:36 PM, Myles C. Maxfield
<myles.maxfield@gmail.com> wrote:
> Ah, yes, you're completely right. I completely agree that moving the
> function into the Maybe monad increases readability. This kind of function
> is what the Maybe monad was designed for.
>
> Here is a revised patch.
>
>
> On Sat, Jan 28, 2012 at 8:28 AM, Michael Snoyman <michael@snoyman.com>
> wrote:
>>
>> On Sat, Jan 28, 2012 at 1:20 AM, Myles C. Maxfield
>> <myles.maxfield@gmail.com> wrote:
>> > the fromJust should never fail, beceause of the guard statement:
>> >
>> >     | 300 <= code && code < 400 && isJust l'' && isJust l' = Just $ req
>> >
>> > Because of the order of the && operators, it will only evaluate fromJust
>> > after it makes sure that the argument isJust. That function in
>> > particular
>> > shouldn't throw any exceptions - it should only return Nothing.
>> >
>> > Knowing that, I don't quite think I understand what your concern is. Can
>> > you
>> > elaborate?
>>
>> You're right, but I had to squint really hard to prove to myself that
>> you're right. That's the kind of code that could easily be broken in
>> future updates by an unwitting maintainer (e.g., me). To protect the
>> world from me, I'd prefer if the code didn't have the fromJust. This
>> might be a good place to leverage the Monad instance of Maybe.
>>
>> Michael
>
>