|
| 1 | +from collections import Optional |
| 2 | +from lightbug_http.header import HeaderKey |
| 3 | + |
| 4 | +struct Cookie(CollectionElement): |
| 5 | + alias EXPIRES = "Expires" |
| 6 | + alias MAX_AGE = "Max-Age" |
| 7 | + alias DOMAIN = "Domain" |
| 8 | + alias PATH = "Path" |
| 9 | + alias SECURE = "Secure" |
| 10 | + alias HTTP_ONLY = "HttpOnly" |
| 11 | + alias SAME_SITE = "SameSite" |
| 12 | + alias PARTITIONED = "Partitioned" |
| 13 | + |
| 14 | + alias SEPERATOR = "; " |
| 15 | + alias EQUAL = "=" |
| 16 | + |
| 17 | + var name: String |
| 18 | + var value: String |
| 19 | + var expires: Expiration |
| 20 | + var secure: Bool |
| 21 | + var http_only: Bool |
| 22 | + var partitioned: Bool |
| 23 | + var same_site: Optional[SameSite] |
| 24 | + var domain: Optional[String] |
| 25 | + var path: Optional[String] |
| 26 | + var max_age: Optional[Duration] |
| 27 | + |
| 28 | + |
| 29 | + @staticmethod |
| 30 | + fn from_set_header(header_str: String) raises -> Self: |
| 31 | + var parts = header_str.split(Cookie.SEPERATOR) |
| 32 | + if len(parts) < 1: |
| 33 | + raise Error("invalid Cookie") |
| 34 | + |
| 35 | + var cookie = Cookie("", parts[0], path=str("/")) |
| 36 | + if Cookie.EQUAL in parts[0]: |
| 37 | + var name_value = parts[0].split(Cookie.EQUAL) |
| 38 | + cookie.name = name_value[0] |
| 39 | + cookie.value = name_value[1] |
| 40 | + |
| 41 | + for i in range(1, len(parts)): |
| 42 | + var part = parts[i] |
| 43 | + if part == Cookie.PARTITIONED: |
| 44 | + cookie.partitioned = True |
| 45 | + elif part == Cookie.SECURE: |
| 46 | + cookie.secure = True |
| 47 | + elif part == Cookie.HTTP_ONLY: |
| 48 | + cookie.http_only = True |
| 49 | + elif part.startswith(Cookie.SAME_SITE): |
| 50 | + cookie.same_site = SameSite.from_string(part.removeprefix(Cookie.SAME_SITE + Cookie.EQUAL)) |
| 51 | + elif part.startswith(Cookie.DOMAIN): |
| 52 | + cookie.domain = part.removeprefix(Cookie.DOMAIN + Cookie.EQUAL) |
| 53 | + elif part.startswith(Cookie.PATH): |
| 54 | + cookie.path = part.removeprefix(Cookie.PATH + Cookie.EQUAL) |
| 55 | + elif part.startswith(Cookie.MAX_AGE): |
| 56 | + cookie.max_age = Duration.from_string(part.removeprefix(Cookie.MAX_AGE + Cookie.EQUAL)) |
| 57 | + elif part.startswith(Cookie.EXPIRES): |
| 58 | + var expires = Expiration.from_string(part.removeprefix(Cookie.EXPIRES + Cookie.EQUAL)) |
| 59 | + if expires: |
| 60 | + cookie.expires = expires.value() |
| 61 | + |
| 62 | + return cookie |
| 63 | + |
| 64 | + fn __init__( |
| 65 | + inout self, |
| 66 | + name: String, |
| 67 | + value: String, |
| 68 | + expires: Expiration = Expiration.session(), |
| 69 | + max_age: Optional[Duration] = Optional[Duration](None), |
| 70 | + domain: Optional[String] = Optional[String](None), |
| 71 | + path: Optional[String] = Optional[String](None), |
| 72 | + same_site: Optional[SameSite] = Optional[SameSite](None), |
| 73 | + secure: Bool = False, |
| 74 | + http_only: Bool = False, |
| 75 | + partitioned: Bool = False, |
| 76 | + ): |
| 77 | + self.name = name |
| 78 | + self.value = value |
| 79 | + self.expires = expires |
| 80 | + self.max_age = max_age |
| 81 | + self.domain = domain |
| 82 | + self.path = path |
| 83 | + self.secure = secure |
| 84 | + self.http_only = http_only |
| 85 | + self.same_site = same_site |
| 86 | + self.partitioned = partitioned |
| 87 | + |
| 88 | + fn __str__(self) -> String: |
| 89 | + return "Name: " + self.name + " Value: " + self.value |
| 90 | + |
| 91 | + fn __copyinit__(inout self: Cookie, existing: Cookie): |
| 92 | + self.name = existing.name |
| 93 | + self.value = existing.value |
| 94 | + self.max_age = existing.max_age |
| 95 | + self.expires = existing.expires |
| 96 | + self.domain = existing.domain |
| 97 | + self.path = existing.path |
| 98 | + self.secure = existing.secure |
| 99 | + self.http_only = existing.http_only |
| 100 | + self.same_site = existing.same_site |
| 101 | + self.partitioned = existing.partitioned |
| 102 | + |
| 103 | + fn __moveinit__(inout self: Cookie, owned existing: Cookie): |
| 104 | + self.name = existing.name |
| 105 | + self.value = existing.value |
| 106 | + self.max_age = existing.max_age |
| 107 | + self.expires = existing.expires |
| 108 | + self.domain = existing.domain |
| 109 | + self.path = existing.path |
| 110 | + self.secure = existing.secure |
| 111 | + self.http_only = existing.http_only |
| 112 | + self.same_site = existing.same_site |
| 113 | + self.partitioned = existing.partitioned |
| 114 | + |
| 115 | + fn clear_cookie(inout self): |
| 116 | + self.max_age = Optional[Duration](None) |
| 117 | + self.expires = Expiration.invalidate() |
| 118 | + |
| 119 | + fn to_header(self) -> Header: |
| 120 | + return Header(HeaderKey.SET_COOKIE, self.build_header_value()) |
| 121 | + |
| 122 | + fn build_header_value(self) -> String: |
| 123 | + |
| 124 | + var header_value = self.name + Cookie.EQUAL + self.value |
| 125 | + if self.expires.is_datetime(): |
| 126 | + var v = self.expires.http_date_timestamp() |
| 127 | + if v: |
| 128 | + header_value += Cookie.SEPERATOR + Cookie.EXPIRES + Cookie.EQUAL + v.value() |
| 129 | + if self.max_age: |
| 130 | + header_value += Cookie.SEPERATOR + Cookie.MAX_AGE + Cookie.EQUAL + str(self.max_age.value().total_seconds) |
| 131 | + if self.domain: |
| 132 | + header_value += Cookie.SEPERATOR + Cookie.DOMAIN + Cookie.EQUAL + self.domain.value() |
| 133 | + if self.path: |
| 134 | + header_value += Cookie.SEPERATOR + Cookie.PATH + Cookie.EQUAL + self.path.value() |
| 135 | + if self.secure: |
| 136 | + header_value += Cookie.SEPERATOR + Cookie.SECURE |
| 137 | + if self.http_only: |
| 138 | + header_value += Cookie.SEPERATOR + Cookie.HTTP_ONLY |
| 139 | + if self.same_site: |
| 140 | + header_value += Cookie.SEPERATOR + Cookie.SAME_SITE + Cookie.EQUAL + str(self.same_site.value()) |
| 141 | + if self.partitioned: |
| 142 | + header_value += Cookie.SEPERATOR + Cookie.PARTITIONED |
| 143 | + return header_value |
0 commit comments