1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty5.handler.codec.http.cookie;
17
18 import io.netty5.handler.codec.DateFormatter;
19 import io.netty5.handler.codec.http.HttpConstants;
20 import io.netty5.handler.codec.http.HttpResponse;
21 import io.netty5.util.internal.StringUtil;
22
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31
32 import static io.netty5.handler.codec.http.cookie.CookieUtil.add;
33 import static io.netty5.handler.codec.http.cookie.CookieUtil.addQuoted;
34 import static io.netty5.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator;
35 import static java.util.Objects.requireNonNull;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public final class ServerCookieEncoder extends CookieEncoder {
54
55
56
57
58
59
60
61 public static final ServerCookieEncoder STRICT = new ServerCookieEncoder(true);
62
63
64
65
66
67 public static final ServerCookieEncoder LAX = new ServerCookieEncoder(false);
68
69 private ServerCookieEncoder(boolean strict) {
70 super(strict);
71 }
72
73
74
75
76
77
78
79
80 public String encode(String name, String value) {
81 return encode(new DefaultCookie(name, value));
82 }
83
84
85
86
87
88
89
90 public String encode(Cookie cookie) {
91 final String name = requireNonNull(cookie, "cookie").name();
92 final String value = cookie.value() != null ? cookie.value() : "";
93
94 validateCookie(name, value);
95
96 StringBuilder buf = StringUtil.threadLocalStringBuilder();
97
98 if (cookie.wrap()) {
99 addQuoted(buf, name, value);
100 } else {
101 add(buf, name, value);
102 }
103
104 if (cookie.maxAge() != Long.MIN_VALUE) {
105 add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
106 Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis());
107 buf.append(CookieHeaderNames.EXPIRES);
108 buf.append('=');
109 DateFormatter.append(expires, buf);
110 buf.append(';');
111 buf.append(HttpConstants.SP_CHAR);
112 }
113
114 if (cookie.path() != null) {
115 add(buf, CookieHeaderNames.PATH, cookie.path());
116 }
117
118 if (cookie.domain() != null) {
119 add(buf, CookieHeaderNames.DOMAIN, cookie.domain());
120 }
121 if (cookie.isSecure()) {
122 add(buf, CookieHeaderNames.SECURE);
123 }
124 if (cookie.isHttpOnly()) {
125 add(buf, CookieHeaderNames.HTTPONLY);
126 }
127 if (cookie instanceof DefaultCookie) {
128 DefaultCookie c = (DefaultCookie) cookie;
129 if (c.sameSite() != null) {
130 add(buf, CookieHeaderNames.SAMESITE, c.sameSite().name());
131 }
132 }
133
134 return stripTrailingSeparator(buf);
135 }
136
137
138
139
140
141
142
143 private static List<String> dedup(List<String> encoded, Map<String, Integer> nameToLastIndex) {
144 boolean[] isLastInstance = new boolean[encoded.size()];
145 for (int idx : nameToLastIndex.values()) {
146 isLastInstance[idx] = true;
147 }
148 List<String> dedupd = new ArrayList<>(nameToLastIndex.size());
149 for (int i = 0, n = encoded.size(); i < n; i++) {
150 if (isLastInstance[i]) {
151 dedupd.add(encoded.get(i));
152 }
153 }
154 return dedupd;
155 }
156
157
158
159
160
161
162
163 public List<String> encode(Cookie... cookies) {
164 if (requireNonNull(cookies, "cookies").length == 0) {
165 return Collections.emptyList();
166 }
167
168 List<String> encoded = new ArrayList<>(cookies.length);
169 Map<String, Integer> nameToIndex = strict && cookies.length > 1 ? new HashMap<>() : null;
170 boolean hasDupdName = false;
171 for (int i = 0; i < cookies.length; i++) {
172 Cookie c = cookies[i];
173 encoded.add(encode(c));
174 if (nameToIndex != null) {
175 hasDupdName |= nameToIndex.put(c.name(), i) != null;
176 }
177 }
178 return hasDupdName ? dedup(encoded, nameToIndex) : encoded;
179 }
180
181
182
183
184
185
186
187 public List<String> encode(Collection<? extends Cookie> cookies) {
188 if (requireNonNull(cookies, "cookies").isEmpty()) {
189 return Collections.emptyList();
190 }
191
192 List<String> encoded = new ArrayList<>(cookies.size());
193 Map<String, Integer> nameToIndex = strict && cookies.size() > 1 ? new HashMap<>() : null;
194 int i = 0;
195 boolean hasDupdName = false;
196 for (Cookie c : cookies) {
197 encoded.add(encode(c));
198 if (nameToIndex != null) {
199 hasDupdName |= nameToIndex.put(c.name(), i++) != null;
200 }
201 }
202 return hasDupdName ? dedup(encoded, nameToIndex) : encoded;
203 }
204
205
206
207
208
209
210
211 public List<String> encode(Iterable<? extends Cookie> cookies) {
212 Iterator<? extends Cookie> cookiesIt = requireNonNull(cookies, "cookies").iterator();
213 if (!cookiesIt.hasNext()) {
214 return Collections.emptyList();
215 }
216
217 List<String> encoded = new ArrayList<>();
218 Cookie firstCookie = cookiesIt.next();
219 Map<String, Integer> nameToIndex = strict && cookiesIt.hasNext() ? new HashMap<>() : null;
220 int i = 0;
221 encoded.add(encode(firstCookie));
222 boolean hasDupdName = nameToIndex != null && nameToIndex.put(firstCookie.name(), i++) != null;
223 while (cookiesIt.hasNext()) {
224 Cookie c = cookiesIt.next();
225 encoded.add(encode(c));
226 if (nameToIndex != null) {
227 hasDupdName |= nameToIndex.put(c.name(), i++) != null;
228 }
229 }
230 return hasDupdName ? dedup(encoded, nameToIndex) : encoded;
231 }
232 }