@@ -6,6 +6,11 @@ import {
6
6
parseSetCookie ,
7
7
} from "cookie-es" ;
8
8
9
+ const CHUNKED_COOKIE = "__chunked__" ;
10
+
11
+ // The limit is approximately 4KB, but may vary by browser and server. We leave some room to be safe.
12
+ const CHUNKS_MAX_LENGTH = 4000 ;
13
+
9
14
/**
10
15
* Parse the request to get HTTP Cookie header string and returning an object of all cookie name-value pairs.
11
16
* @param event {HTTPEvent} H3 event or req passed by h3 handler
@@ -96,6 +101,114 @@ export function deleteCookie(
96
101
} ) ;
97
102
}
98
103
104
+ /**
105
+ * Get a chunked cookie value by name. Will join chunks together.
106
+ * @param event {HTTPEvent} { req: Request }
107
+ * @param name Name of the cookie to get
108
+ * @returns {* } Value of the cookie (String or undefined)
109
+ * ```ts
110
+ * const authorization = getCookie(request, 'Session')
111
+ * ```
112
+ */
113
+ export function getChunkedCookie (
114
+ event : HTTPEvent ,
115
+ name : string ,
116
+ ) : string | undefined {
117
+ const mainCookie = getCookie ( event , name ) ;
118
+ if ( ! mainCookie || ! mainCookie . startsWith ( CHUNKED_COOKIE ) ) {
119
+ return mainCookie ;
120
+ }
121
+
122
+ const chunksCount = getChunkedCookieCount ( mainCookie ) ;
123
+ if ( chunksCount === 0 ) {
124
+ return undefined ;
125
+ }
126
+
127
+ const chunks = [ ] ;
128
+ for ( let i = 1 ; i <= chunksCount ; i ++ ) {
129
+ const chunk = getCookie ( event , chunkCookieName ( name , i ) ) ;
130
+ if ( ! chunk ) {
131
+ return undefined ;
132
+ }
133
+ chunks . push ( chunk ) ;
134
+ }
135
+
136
+ return chunks . join ( "" ) ;
137
+ }
138
+
139
+ /**
140
+ * Set a cookie value by name. Chunked cookies will be created as needed.
141
+ * @param event {H3Event} H3 event or res passed by h3 handler
142
+ * @param name Name of the cookie to set
143
+ * @param value Value of the cookie to set
144
+ * @param options {CookieSerializeOptions} Options for serializing the cookie
145
+ * ```ts
146
+ * setCookie(res, 'Session', '<session data>')
147
+ * ```
148
+ */
149
+ export function setChunkedCookie (
150
+ event : H3Event ,
151
+ name : string ,
152
+ value : string ,
153
+ options ?: CookieSerializeOptions & { chunkMaxLength ?: number } ,
154
+ ) : void {
155
+ const chunkMaxLength = options ?. chunkMaxLength || CHUNKS_MAX_LENGTH ;
156
+ const chunkCount = Math . ceil ( value . length / chunkMaxLength ) ;
157
+
158
+ // delete any prior left over chunks if the cookie is updated
159
+ const previousCookie = getCookie ( event , name ) ;
160
+ if ( previousCookie ?. startsWith ( CHUNKED_COOKIE ) ) {
161
+ const previousChunkCount = getChunkedCookieCount ( previousCookie ) ;
162
+ if ( previousChunkCount > chunkCount ) {
163
+ for ( let i = chunkCount ; i <= previousChunkCount ; i ++ ) {
164
+ deleteCookie ( event , chunkCookieName ( name , i ) , options ) ;
165
+ }
166
+ }
167
+ }
168
+
169
+ if ( chunkCount <= 1 ) {
170
+ // If the value is small enough, just set it as a normal cookie
171
+ setCookie ( event , name , value , options ) ;
172
+ return ;
173
+ }
174
+
175
+ // If the value is too large, we need to chunk it
176
+ const mainCookieValue = `${ CHUNKED_COOKIE } ${ chunkCount } ` ;
177
+ setCookie ( event , name , mainCookieValue , options ) ;
178
+
179
+ for ( let i = 1 ; i <= chunkCount ; i ++ ) {
180
+ const start = ( i - 1 ) * chunkMaxLength ;
181
+ const end = start + chunkMaxLength ;
182
+ const chunkValue = value . slice ( start , end ) ;
183
+ setCookie ( event , chunkCookieName ( name , i ) , chunkValue , options ) ;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Remove a set of chunked cookies by name.
189
+ * @param event {H3Event} H3 event or res passed by h3 handler
190
+ * @param name Name of the cookie to delete
191
+ * @param serializeOptions {CookieSerializeOptions} Cookie options
192
+ * ```ts
193
+ * deleteCookie(res, 'Session')
194
+ * ```
195
+ */
196
+ export function deleteChunkedCookie (
197
+ event : H3Event ,
198
+ name : string ,
199
+ serializeOptions ?: CookieSerializeOptions ,
200
+ ) : void {
201
+ const mainCookie = getCookie ( event , name ) ;
202
+ deleteCookie ( event , name , serializeOptions ) ;
203
+
204
+ const chunksCount = getChunkedCookieCount ( mainCookie ) ;
205
+ if ( chunksCount >= 0 ) {
206
+ for ( let i = 0 ; i < chunksCount ; i ++ ) {
207
+ deleteCookie ( event , chunkCookieName ( name , i + 1 ) , serializeOptions ) ;
208
+ }
209
+ }
210
+ }
211
+
99
212
/**
100
213
* Cookies are unique by "cookie-name, domain-value, and path-value".
101
214
*
@@ -104,3 +217,14 @@ export function deleteCookie(
104
217
function _getDistinctCookieKey ( name : string , options : Partial < SetCookie > ) {
105
218
return [ name , options . domain || "" , options . path || "/" ] . join ( ";" ) ;
106
219
}
220
+
221
+ function getChunkedCookieCount ( cookie : string | undefined ) : number {
222
+ if ( ! cookie ?. startsWith ( CHUNKED_COOKIE ) ) {
223
+ return Number . NaN ;
224
+ }
225
+ return Number . parseInt ( cookie . slice ( CHUNKED_COOKIE . length ) ) ;
226
+ }
227
+
228
+ function chunkCookieName ( name : string , chunkNumber : number ) : string {
229
+ return `${ name } .${ chunkNumber } ` ;
230
+ }
0 commit comments