Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adding response Set-Cookie support

  • Loading branch information...
commit ceef76381145f3883539cc29fda3ac96fe441379 1 parent 054b985
@lodejard lodejard authored
View
21 src/Main/Gate/Headers.cs
@@ -43,6 +43,27 @@ public static class Headers
return headers;
}
+ public static IDictionary<string, IEnumerable<string>> AddHeader(this IDictionary<string, IEnumerable<string>> headers,
+ string name, string value)
+ {
+ return AddHeader(headers, name, new[] {value});
+ }
+
+ public static IDictionary<string, IEnumerable<string>> AddHeader(this IDictionary<string, IEnumerable<string>> headers,
+ string name, IEnumerable<string> value)
+ {
+ IEnumerable<string> values;
+ if (headers.TryGetValue(name, out values))
+ {
+ headers[name] = values.Concat(value).ToArray();
+ }
+ else
+ {
+ headers[name] = value;
+ }
+ return headers;
+ }
+
public static IEnumerable<string> GetHeaders(this IDictionary<string, IEnumerable<string>> headers,
string name)
{
View
114 src/Main/Gate/Response.cs
@@ -49,7 +49,7 @@ public Response(ResultDelegate result, string status, IDictionary<string, IEnume
public Encoding Encoding { get; set; }
public bool Buffer { get; set; }
- string GetHeader(string name)
+ public string GetHeader(string name)
{
var values = GetHeaders(name);
if (values == null)
@@ -92,18 +92,120 @@ string GetHeader(string name)
return sb.ToString();
}
- IEnumerable<string> GetHeaders(string name)
+ public IEnumerable<string> GetHeaders(string name)
{
- IEnumerable<string> value;
- return Headers.TryGetValue(name, out value) ? value : null;
+ IEnumerable<string> existingValues;
+ return Headers.TryGetValue(name, out existingValues) ? existingValues : null;
}
- void SetHeader(string name, string value)
+ public Response SetHeader(string name, string value)
{
if (string.IsNullOrWhiteSpace(value))
Headers.Remove(value);
else
Headers[name] = new[] { value };
+ return this;
+ }
+
+ public Response SetCookie(string key, string value)
+ {
+ Headers.AddHeader("Set-Cookie", Uri.EscapeDataString(key) + "=" + Uri.EscapeDataString(value) + "; path=/");
+ return this;
+ }
+
+ public Response SetCookie(string key, Cookie cookie)
+ {
+ var domainHasValue = !string.IsNullOrEmpty(cookie.Domain);
+ var pathHasValue = !string.IsNullOrEmpty(cookie.Path);
+ var expiresHasValue = cookie.Expires.HasValue;
+
+ var setCookieValue = string.Concat(
+ Uri.EscapeDataString(key),
+ "=",
+ Uri.EscapeDataString(cookie.Value ?? ""), //TODO: concat complex value type with '&'?
+ !domainHasValue ? null : "; domain=",
+ !domainHasValue ? null : cookie.Domain,
+ !pathHasValue ? null : "; path=",
+ !pathHasValue ? null : cookie.Path,
+ !expiresHasValue ? null : "; expires=",
+ !expiresHasValue ? null : cookie.Expires.Value.ToString("ddd, dd-MMM-yyyy HH:mm:ss ") + "GMT",
+ !cookie.Secure ? null : "; secure",
+ !cookie.HttpOnly ? null : "; HttpOnly"
+ );
+ Headers.AddHeader("Set-Cookie", setCookieValue);
+ return this;
+ }
+
+ public Response DeleteCookie(string key)
+ {
+ Func<string, bool> predicate = value => value.StartsWith(key + "=", StringComparison.InvariantCultureIgnoreCase);
+
+ var deleteCookies = new[] { Uri.EscapeDataString(key) + "=; expires=Thu, 01-Jan-1970 00:00:00 GMT" };
+ var existingValues = Headers.GetHeaders("Set-Cookie");
+ if (existingValues == null)
+ {
+ Headers["Set-Cookie"] = deleteCookies;
+ return this;
+ }
+
+ Headers["Set-Cookie"] = existingValues.Where(value => !predicate(value)).Concat(deleteCookies).ToArray();
+ return this;
+ }
+
+ public Response DeleteCookie(string key, Cookie cookie)
+ {
+ var domainHasValue = !string.IsNullOrEmpty(cookie.Domain);
+ var pathHasValue = !string.IsNullOrEmpty(cookie.Path);
+
+ Func<string, bool> rejectPredicate;
+ if (domainHasValue)
+ {
+ rejectPredicate = value =>
+ value.StartsWith(key + "=", StringComparison.InvariantCultureIgnoreCase) &&
+ value.IndexOf("domain=" + cookie.Domain, StringComparison.InvariantCultureIgnoreCase) != -1;
+ }
+ else if (pathHasValue)
+ {
+ rejectPredicate = value =>
+ value.StartsWith(key + "=", StringComparison.InvariantCultureIgnoreCase) &&
+ value.IndexOf("path=" + cookie.Path, StringComparison.InvariantCultureIgnoreCase) != -1;
+ }
+ else
+ {
+ rejectPredicate = value => value.StartsWith(key + "=", StringComparison.InvariantCultureIgnoreCase);
+ }
+ var existingValues = Headers.GetHeaders("Set-Cookie");
+ if (existingValues != null)
+ {
+ Headers["Set-Cookie"] = existingValues.Where(value => !rejectPredicate(value));
+ }
+
+ return SetCookie(key, new Cookie
+ {
+ Path = cookie.Path,
+ Domain = cookie.Domain,
+ Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
+ });
+ }
+
+
+ public class Cookie
+ {
+ public Cookie()
+ {
+ Path = "/";
+ }
+ public Cookie(string value)
+ {
+ Path = "/";
+ Value = value;
+ }
+ public string Value { get; set; }
+ public string Domain { get; set; }
+ public string Path { get; set; }
+ public DateTime? Expires { get; set; }
+ public bool Secure { get; set; }
+ public bool HttpOnly { get; set; }
}
public string ContentType
@@ -296,5 +398,7 @@ void EarlyResponseEnd(Exception ex)
OnStart(() => OnEnd(ex));
Autostart();
}
+
+
}
}
View
38 src/Samples/Sample.HelloWorld/Startup.cs
@@ -1,4 +1,7 @@
-using Gate;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gate;
using Owin;
namespace Sample.HelloWorld
@@ -11,6 +14,9 @@ public void Configuration(IAppBuilder builder)
{
resp.Status = "200 OK";
resp.ContentType = "text/html";
+ resp.SetCookie("my-cookie", "my-value");
+ resp.SetCookie("my-30-day-cookie", new Response.Cookie("hello-this-month") { Expires = DateTime.UtcNow.AddDays(30) });
+ resp.SetCookie("last-path-cookie", new Response.Cookie("hello-path " + req.Path) { Path = req.PathBase + req.Path });
resp.Write("<html>")
.Write("<head><title>Hello world</title></head>")
@@ -18,8 +24,37 @@ public void Configuration(IAppBuilder builder)
.Write("<p>Hello world!</p>")
.Write("<ul>");
+ resp.Write("<h3>Environment</h3>");
foreach (var kv in req)
{
+ if (kv.Value is IDictionary<string, IEnumerable<string>>)
+ {
+ resp.Write("<li>&laquo;")
+ .Write(kv.Key)
+ .Write("&raquo;<br/><ul>");
+ foreach (var kv2 in kv.Value as IDictionary<string, IEnumerable<string>>)
+ {
+ resp.Write("<li>&laquo;")
+ .Write(kv2.Key)
+ .Write("&raquo; = ")
+ .Write(string.Join(", ", kv2.Value.ToArray()))
+ .Write("</code></li>");
+ }
+ resp.Write("</ul></li>");
+ }
+ else
+ {
+ resp.Write("<li>&laquo;")
+ .Write(kv.Key)
+ .Write("&raquo;<br/><code>")
+ .Write(kv.Value.ToString())
+ .Write("</code></li>");
+ }
+ }
+
+ resp.Write("<h3>Cookies</h3>");
+ foreach (var kv in req.Cookies)
+ {
resp.Write("<li>&laquo;")
.Write(kv.Key)
.Write("&raquo;<br/><code>")
@@ -27,6 +62,7 @@ public void Configuration(IAppBuilder builder)
.Write("</code></li>");
}
+
resp
.Write("</ul>")
.Write("</body>")
View
105 src/Tests/Gate.Tests/ResponseTests.cs
@@ -76,5 +76,110 @@ public void Write_calls_will_spool_until_finish_is_called()
var data = Encoding.UTF8.GetString(Consume());
Assert.That(data, Is.EqualTo("thisisatest"));
}
+
+ [Test]
+ public void ItCanSetCookies()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", "bar");
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[] { "foo=bar; path=/" }));
+ response.SetCookie("foo2", "bar2");
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[] { "foo=bar; path=/", "foo2=bar2; path=/" }));
+ response.SetCookie("foo3", "bar3");
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[] { "foo=bar; path=/", "foo2=bar2; path=/", "foo3=bar3; path=/" }));
+ }
+
+ [Test]
+ public void ItCanSetCookiesWithTheSameNameForMultipleDomains()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", Domain = "sample.example.com" });
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", Domain = ".example.com" });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[]
+ {
+ "foo=bar; domain=sample.example.com; path=/",
+ "foo=bar; domain=.example.com; path=/"
+ }));
+ }
+
+ [Test]
+ public void ItFormatsTheCookieExpirationDataAccordinglyToRfc2109()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", Expires = new DateTime(1971, 10, 14, 12, 34, 56) });
+ Assert.That(response.GetHeader("Set-Cookie"), Is.StringMatching(@"expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d ..."));
+ }
+
+ [Test]
+ public void ItCanSetSecureCookies()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", Secure = true });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[] { @"foo=bar; path=/; secure" }));
+ }
+
+
+ [Test]
+ public void ItCanSetHttpOnlyCookies()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", HttpOnly = true });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[] { @"foo=bar; path=/; HttpOnly" }));
+ }
+
+ [Test]
+ public void ItCanDeleteCookies()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", "bar");
+ response.SetCookie("foo2", "bar2");
+ response.DeleteCookie("foo");
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[] { "foo2=bar2; path=/", "foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT" }));
+ }
+
+ [Test]
+ public void ItCanDeleteCookiesWithTheSameNameFromMultipleDomains()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", Domain = "sample.example.com" });
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", Domain = ".example.com" });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[]
+ {
+ "foo=bar; domain=sample.example.com; path=/",
+ "foo=bar; domain=.example.com; path=/"
+ }));
+ response.DeleteCookie("foo", new Response.Cookie { Domain = ".example.com" });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[]
+ {
+ "foo=bar; domain=sample.example.com; path=/",
+ "foo=; domain=.example.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
+ }));
+ response.DeleteCookie("foo", new Response.Cookie { Domain = "sample.example.com" });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[]
+ {
+ "foo=; domain=.example.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT",
+ "foo=; domain=sample.example.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
+ }));
+ }
+
+
+ [Test]
+ public void ItCanDeleteCookiesWithTheSameNameWithDifferentPaths()
+ {
+ var response = new Response(Result);
+ response.SetCookie("foo", "bar");
+ response.SetCookie("foo", new Response.Cookie { Value = "bar", Path = "/path" });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[]
+ {
+ "foo=bar; path=/",
+ "foo=bar; path=/path"
+ }));
+ response.DeleteCookie("foo", new Response.Cookie { Path = "/path" });
+ Assert.That(response.GetHeaders("Set-Cookie"), Is.EquivalentTo(new[]
+ {
+ "foo=bar; path=/",
+ "foo=; path=/path; expires=Thu, 01-Jan-1970 00:00:00 GMT"
+ }));
+ }
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.