1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.http.cookies;
7 
8 import diamond.core.apptype;
9 
10 static if (isWeb)
11 {
12   import vibe.d : Cookie;
13 
14   import diamond.errors.checks;
15   import diamond.core.senc;
16   import diamond.http.client;
17 
18   /// Collection of standard cookies used within Diamond.
19   private CookieInformation[] _cookieInformation;
20 
21   /// Wrapper around cookie information.
22   class CookieInformation
23   {
24     private:
25     /// The cookie name.
26     string _cookieName;
27     /// The cookie description.
28     string _cookieDescription;
29 
30     /**
31     * Creates a new cookie information wrapper.
32     * Params:
33     *   cookieName =        The name of the cookie.
34     *   cookieDescription = The description of the cookie.
35     */
36     this(string cookieName, string cookieDescription)
37     {
38       _cookieName = cookieName;
39       _cookieDescription = cookieDescription;
40     }
41 
42     public:
43     /// Gets the name of the cookie.
44     string cookieName() { return _cookieName; }
45 
46     /// Gets the description of the cookie.
47     string cookieDescription() { return _cookieDescription; }
48   }
49 
50   /// Gets information about all standard cookies used within Diamond.
51   CookieInformation[] getCookieInformation()
52   {
53     if (!_cookieInformation)
54     {
55       _cookieInformation = [
56         new CookieInformation("__D_AUTH_TOKEN", "This cookie is used to store the authentication token used by Diamond."),
57         new CookieInformation("__D_COOKIE_CONSENT", "This cookie is used to store the cookie consent used by Diamond."),
58         new CookieInformation("__D_SESSION", "This cookie is used to store the Diamond session id of a client.")
59       ];
60     }
61 
62     return _cookieInformation;
63   }
64 
65   /// Enumeration of http cookie consent types.
66   enum HttpCookieConsent : string
67   {
68     /// All cookies are allowed.
69     all = "all",
70     /// No third-party cookies are allowed.
71     noThirdParty = "noThirdParty",
72     /// Only functional required cookies are allowed. Third-party cookies etc. are not allowed.
73     functional = "functional",
74     /// No cookies are allowed.
75     none = "none"
76   }
77 
78   /// The type of cookie added.
79   enum HttpCookieType
80   {
81     /// A general cookie used for miscellaneous functionality.
82     general,
83     /// A cookie required for minimum functionality.
84     functional,
85     /// A third-party cookie.
86     thirdParty,
87     /// A session cookie. Session cookies cannot be disabled.
88     session
89   }
90 
91   /// Wrapper around a http cookie collections.
92   final class HttpCookies
93   {
94     private:
95     /// The client.
96     HttpClient _client;
97 
98     public:
99     final:
100     /**
101     * Creates a new http cookie collection.
102     * Params:
103     *   client = The client.
104     */
105     package(diamond) this(HttpClient client)
106     {
107       _client = enforceInput(client, "Cannot create a cookie collection without a client.");
108     }
109 
110     /**
111     * Checks whether a user has consent for a specific cookie type.
112     * Params:
113     *   cookieType = The type of the cookie.
114     * Returns:
115     *   True if the user accepts the cookie, false otherwise.
116     */
117     private bool hasConsent(HttpCookieType cookieType)
118     {
119       switch (cookieType)
120       {
121         case HttpCookieType.general:
122         {
123           if
124           (
125             _client.cookieConsent != HttpCookieConsent.all &&
126             _client.cookieConsent != HttpCookieConsent.noThirdParty
127           )
128           {
129             return false;
130           }
131 
132           break;
133         }
134 
135         case HttpCookieType.functional:
136         {
137           if
138           (
139             _client.cookieConsent == HttpCookieConsent.none
140           )
141           {
142             return false;
143           }
144 
145           break;
146         }
147 
148         case HttpCookieType.thirdParty:
149         {
150           if
151           (
152             _client.cookieConsent != HttpCookieConsent.all
153           )
154           {
155             return false;
156           }
157 
158           break;
159         }
160 
161         default: break;
162       }
163 
164       return _client.cookieConsent != HttpCookieConsent.none;
165     }
166 
167     /**
168     * Creates a cookie.
169     * Params:
170     *   cookieType = The type of the cookie.
171     *   name =       The name of the cookie.
172     *   value =      The value of the cookie.
173     *   maxAge =     The max-age of the cookie. (Seconds the cookie will be alive.)
174     *   path =       The path of the cookie. (Default: "/")
175     */
176     void create
177     (
178       HttpCookieType cookieType,
179       string name, string value,
180       long maxAge,
181       string path = "/"
182     )
183     {
184       if (!hasConsent(cookieType))
185       {
186         return;
187       }
188 
189       auto cookie = new Cookie;
190       cookie.path = path;
191       cookie.maxAge = maxAge;
192       cookie.setValue(value, Cookie.Encoding.none);
193 
194       _client.rawResponse.cookies[name] = cookie;
195     }
196 
197     /**
198     * Creates a cookie.
199     * Params:
200     *   cookieType = The type of the cookie.
201     *   name =      The name of the cookie.
202     *   buffer =    The buffer to encode into a SENC encoded cookie string.
203     *   maxAge =    The max-age of the cookie. (Seconds the cookie will be alive.)
204     *   path =      The path of the cookie. (Default: "/")
205     */
206     void createBuffered
207     (
208       HttpCookieType cookieType,
209       string name, ubyte[] buffer,
210       long maxAge,
211       string path = "/"
212     )
213     {
214       if (!hasConsent(cookieType))
215       {
216         return;
217       }
218 
219       create(cookieType, name, SENC.encode(buffer), maxAge, path);
220     }
221 
222     /**
223     * Gets a cookie.
224     * Params:
225     *   name =    The name of the cookie.
226     * Returns:
227     *   Returns the cookie if found, null otherwise.
228     */
229     string get(string name)
230     {
231       return _client.rawRequest.cookies.get(name);
232     }
233 
234     /**
235     * Gets a buffered cookie encoded as a SENC encoded string.
236     * Params:
237     *   name =    The name.
238     * Returns:
239     *   Returns the buffer.
240     */
241     ubyte[] getBuffered(string name)
242     {
243       return SENC.decode(get(name));
244     }
245 
246     /**
247     * Removes a cookie.
248     * Params:
249     *   name =     The name of the cookie to remove.
250     */
251     void remove(string name)
252     {
253       auto cookie = new Cookie;
254       cookie.path = "/";
255       cookie.maxAge = 1;
256       cookie.setValue(null, Cookie.Encoding.none);
257 
258       _client.rawResponse.cookies[name] = cookie;
259     }
260 
261     /**
262     * Checks whether a request has a cookie or not.
263     * Params:
264     *   name = The name of the cookie to check for existence.
265     * Returns:
266     *   True if the cookie exists, false otherwise.
267     */
268     bool has(string name)
269     {
270       return get(name) !is null;
271     }
272 
273     /**
274     * Gets the auth cookie.
275     * Returns:
276     *   Returns the auth cookie.
277     */
278     string getAuthCookie()
279     {
280       import diamondauth = diamond.authentication;
281 
282       return diamondauth.getAuthCookie(_client);
283     }
284 
285     /**
286     * Checks whether the auth cookie is present or not.
287     * Returns:
288     *   True if the auth cookie is present, false otherwise.
289     */
290     @property bool hasAuthCookie()
291     {
292       import diamondauth = diamond.authentication;
293 
294       return diamondauth.hasAuthCookie(_client);
295     }
296   }
297 }