1 module archttp.Cookie;
2 
3 import std.uri;
4 import std.format;
5 import std.array;
6 
7 class Cookie
8 {
9 	@safe:
10 
11 	private
12 	{
13 		string _name;
14 		string _value;
15 		string _domain;
16 		string _path;
17 		string _expires;
18 		long   _maxAge;
19 		bool   _secure;
20 		bool   _httpOnly;
21 	}
22 
23 	this(string name, string value = "", string path = "/", string domain = "", string expires = "", long maxAge = 3600, bool secure = false, bool httpOnly = false)
24 	{
25 		_name = name;
26 
27 		this.value(value)
28 			.domain(domain)
29 			.path(path)
30 			.expires(expires)
31 			.maxAge(maxAge)
32 			.secure(secure)
33 			.httpOnly(httpOnly);
34 	}
35 
36 	Cookie parse(string cookieString)
37 	{
38 		// if (!cookieString.length)
39 		// 	return null;
40 
41 		// auto parts = cookieString.splitter(';');
42 		// auto idx = parts.front.indexOf('=');
43 		// if (idx == -1)
44 		// 	return null;
45 
46 		// auto name = parts.front[0 .. idx].strip();
47 		// dst.m_value = parts.front[name.length + 1 .. $].strip();
48 		// parts.popFront();
49 
50 		// if (!name.length)
51 		// 	return null;
52 
53 		// foreach(part; parts) {
54 		// 	if (!part.length)
55 		// 		continue;
56 
57 		// 	idx = part.indexOf('=');
58 		// 	if (idx == -1) {
59 		// 		idx = part.length;
60 		// 	}
61 		// 	auto key = part[0 .. idx].strip();
62 		// 	auto value = part[min(idx + 1, $) .. $].strip();
63 
64 		// 	try {
65 		// 		if (key.sicmp("httponly") == 0) {
66 		// 			dst.m_httpOnly = true;
67 		// 		} else if (key.sicmp("secure") == 0) {
68 		// 			dst.m_secure = true;
69 		// 		} else if (key.sicmp("expires") == 0) {
70 		// 			// RFC 822 got updated by RFC 1123 (which is to be used) but is valid for this
71 		// 			// this parsing is just for validation
72 		// 			parseRFC822DateTimeString(value);
73 		// 			dst.m_expires = value;
74 		// 		} else if (key.sicmp("max-age") == 0) {
75 		// 			if (value.length && value[0] != '-')
76 		// 				dst.m_maxAge = value.to!long;
77 		// 		} else if (key.sicmp("domain") == 0) {
78 		// 			if (value.length && value[0] == '.')
79 		// 				value = value[1 .. $]; // the leading . must be stripped (5.2.3)
80 
81 		// 			enforce!ConvException(value.all!(a => a >= 32), "Cookie Domain must not contain any control characters");
82 		// 			dst.m_domain = value.toLower; // must be lower (5.2.3)
83 		// 		} else if (key.sicmp("path") == 0) {
84 		// 			if (value.length && value[0] == '/') {
85 		// 				enforce!ConvException(value.all!(a => a >= 32), "Cookie Path must not contain any control characters");
86 		// 				dst.m_path = value;
87 		// 			} else {
88 		// 				dst.m_path = null;
89 		// 			}
90 		// 		} // else extension value...
91 		// 	} catch (DateTimeException) {
92 		// 	} catch (ConvException) {
93 		// 	}
94 		// 	// RFC 6265 says to ignore invalid values on all of these fields
95 		// }
96 		// return name;
97 		return null;
98 	}
99 
100 	string name() const
101 	{
102 		return _name;
103 	}
104 
105 	Cookie value(string value)
106     {
107 		_value = encode(value);
108 		return this;
109 	}
110 
111 	string value() const
112 	{
113 		return decode(_value);
114 	}
115 	
116 	Cookie domain(string value)
117     {
118 		_domain = value;
119 		return this;
120 	}
121 	
122 	string domain() const
123 	{
124 		return _domain;
125 	}
126 
127 	Cookie path(string value)
128     {
129 		_path = value;
130 		return this;
131 	}
132 	
133 	string path() const
134 	{
135 		return _path;
136 	}
137 
138 	Cookie expires(string value)
139     {
140 		_expires = value;
141 		return this;
142 	}
143 	
144 	string expires() const
145 	{
146 		return _expires;
147 	}
148 
149 	Cookie maxAge(long value)
150     {
151 		_maxAge = value;
152 		return this;
153 	}
154 
155 	long maxAge() const
156 	{
157 		return _maxAge;
158 	}
159 
160 	Cookie secure(bool value)
161     {
162 		_secure = value;
163 		return this;
164 	}
165 
166 	bool secure() const
167 	{
168 		return _secure;
169 	}
170 
171 	Cookie httpOnly(bool value)
172     {
173 		_httpOnly = value;
174 		return this;
175 	}
176 	
177 	bool httpOnly() const
178 	{
179 		return _httpOnly;
180 	}
181 
182 	override string toString()
183 	{
184         auto text = appender!string;
185 		text ~= format!"%s=%s"(this._name, this.value());
186 
187 		if (this._domain && this._domain != "")
188     	{
189 			text ~= format!"; Domain=%s"(this._domain);
190 		}
191 
192 		if (this._path != "")
193     	{
194 			text ~= format!"; Path=%s"(this._path);
195 		}
196 
197 		if (this.expires != "")
198     	{
199 			text ~= format!"; Expires=%s"(this._expires);
200 		}
201 
202 		if (this.maxAge)
203 			text ~= format!"; Max-Age=%s"(this._maxAge);
204 
205 		if (this.secure)
206 			text ~= "; Secure";
207 
208 		if (this.httpOnly)
209 			text ~= "; HttpOnly";
210 
211 		return text[];
212 	}
213 }