Merge pull request #213 from mochi/lego12239-master

fix cookie value parsing (follow up)
diff --git a/CHANGES.md b/CHANGES.md
index 45464d0..e4f10d7 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,8 @@
+Version 2.20.0 released 2019-XX-XX
+
+* Fix parsing of certain unquoted cookie values
+  https://github.com/mochi/mochiweb/pull/212
+
 Version 2.19.0 released 2019-01-17
 
 * Fix warning in 21.2.3 and crash on incompatible releases
diff --git a/src/mochiweb.app.src b/src/mochiweb.app.src
index 70c7165..6486fcb 100644
--- a/src/mochiweb.app.src
+++ b/src/mochiweb.app.src
@@ -1,7 +1,7 @@
 %% This is generated from src/mochiweb.app.src
 {application, mochiweb,
  [{description, "MochiMedia Web Server"},
-  {vsn, "2.19.0"},
+  {vsn, "2.20.0"},
   {modules, []},
   {registered, []},
   {env, []},
diff --git a/src/mochiweb_cookies.erl b/src/mochiweb_cookies.erl
index 013dbe0..c7b0fcf 100644
--- a/src/mochiweb_cookies.erl
+++ b/src/mochiweb_cookies.erl
@@ -39,6 +39,19 @@
          C =:= $[ orelse C =:= $] orelse C =:= $? orelse C =:= $= orelse
          C =:= ${ orelse C =:= $})).
 
+%% RFC 6265 cookie value allowed characters
+%%  cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
+%%                        ; US-ASCII characters excluding CTLs,
+%%                        ; whitespace DQUOTE, comma, semicolon,
+%%                        ; and backslash
+-define(IS_COOKIE_OCTET(C),
+        (C =:= 16#21
+         orelse (C >= 16#23 andalso C =< 16#2B)
+         orelse (C >= 16#2D andalso C =< 16#3A)
+         orelse (C >= 16#3C andalso C =< 16#5B)
+         orelse (C >= 16#5D andalso C =< 16#7E)
+        )).
+
 %% @type proplist() = [{Key::string(), Value::string()}].
 %% @type header() = {Name::string(), Value::string()}.
 %% @type int_seconds() = integer().
@@ -208,11 +221,15 @@
         [?QUOTE | _] ->
             read_quoted(Value1);
         _ ->
-            read_token(Value1)
+            read_value_(Value1)
     end;
 read_value(String) ->
     {"", String}.
 
+read_value_(String) ->
+    F = fun (C) -> ?IS_COOKIE_OCTET(C) end,
+    lists:splitwith(F, String).
+
 read_quoted([?QUOTE | String]) ->
     read_quoted(String, []).
 
@@ -302,6 +319,12 @@
     ?assertEqual(
        [{"foo", "bar"}, {"baz", "wibble"}],
        parse_cookie("foo=bar , baz=wibble ")),
+    ?assertEqual(
+       [{"foo", "base64=="}, {"bar", "base64="}],
+       parse_cookie("foo=\"base64==\";bar=\"base64=\"")),
+    ?assertEqual(
+       [{"foo", "base64=="}, {"bar", "base64="}],
+       parse_cookie("foo=base64==;bar=base64=")),
     ok.
 
 domain_test() ->