/src/libsoup/libsoup/server/soup-message-body.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ |
2 | | /* |
3 | | * soup-message-body.c: SoupMessage request/response bodies |
4 | | * |
5 | | * Copyright (C) 2000-2003, Ximian, Inc. |
6 | | */ |
7 | | |
8 | | #ifdef HAVE_CONFIG_H |
9 | | #include <config.h> |
10 | | #endif |
11 | | |
12 | | #include <string.h> |
13 | | |
14 | | #include "soup-message-body.h" |
15 | | #include "soup.h" |
16 | | |
17 | | /** |
18 | | * SoupMemoryUse: |
19 | | * @SOUP_MEMORY_STATIC: The memory is statically allocated and |
20 | | * constant; libsoup can use the passed-in buffer directly and not |
21 | | * need to worry about it being modified or freed. |
22 | | * @SOUP_MEMORY_TAKE: The caller has allocated the memory and libsoup |
23 | | * will assume ownership of it and free it with [func@GLib.free]. |
24 | | * @SOUP_MEMORY_COPY: The passed-in data belongs to the caller and |
25 | | * libsoup will copy it into new memory leaving the caller free |
26 | | * to reuse the original memory. |
27 | | * |
28 | | * The lifetime of the memory being passed. |
29 | | **/ |
30 | | |
31 | | /** |
32 | | * SoupMessageBody: |
33 | | * @data: (array length=length) (element-type guint8): the data |
34 | | * @length: length of @data |
35 | | * |
36 | | * [struct@MessageBody] represents the request or response body of a |
37 | | * [class@Message]. |
38 | | * |
39 | | * Note that while @length always reflects the full length of the |
40 | | * message body, @data is normally %NULL, and will only be filled in |
41 | | * after [method@MessageBody.flatten] is called. For client-side |
42 | | * messages, this automatically happens for the response body after it |
43 | | * has been fully read. Likewise, for server-side |
44 | | * messages, the request body is automatically filled in after being |
45 | | * read. |
46 | | * |
47 | | * As an added bonus, when @data is filled in, it is always terminated |
48 | | * with a `\0` byte (which is not reflected in @length). |
49 | | **/ |
50 | | |
51 | | typedef struct { |
52 | | SoupMessageBody body; |
53 | | GSList *chunks, *last; |
54 | | GBytes *flattened; |
55 | | gboolean accumulate; |
56 | | goffset base_offset; |
57 | | } SoupMessageBodyPrivate; |
58 | | |
59 | | /** |
60 | | * soup_message_body_new: |
61 | | * |
62 | | * Creates a new [struct@MessageBody] |
63 | | * |
64 | | * [class@Message] uses this internally; you |
65 | | * will not normally need to call it yourself. |
66 | | * |
67 | | * Returns: a new #SoupMessageBody. |
68 | | **/ |
69 | | SoupMessageBody * |
70 | | soup_message_body_new (void) |
71 | 0 | { |
72 | 0 | SoupMessageBodyPrivate *priv; |
73 | |
|
74 | 0 | priv = g_atomic_rc_box_new0 (SoupMessageBodyPrivate); |
75 | 0 | priv->accumulate = TRUE; |
76 | |
|
77 | 0 | return (SoupMessageBody *)priv; |
78 | 0 | } |
79 | | |
80 | | /** |
81 | | * soup_message_body_set_accumulate: |
82 | | * @body: a #SoupMessageBody |
83 | | * @accumulate: whether or not to accumulate body chunks in @body |
84 | | * |
85 | | * Sets or clears the accumulate flag on @body. |
86 | | * |
87 | | * (The default value is %TRUE.) If set to %FALSE, @body's data field will not |
88 | | * be filled in after the body is fully sent/received, and the chunks that make |
89 | | * up @body may be discarded when they are no longer needed. |
90 | | * |
91 | | * If you set the flag to %FALSE on the [class@Message] request_body of a |
92 | | * client-side message, it will block the accumulation of chunks into |
93 | | * @body's data field, but it will not normally cause the chunks to |
94 | | * be discarded after being written like in the server-side |
95 | | * [class@Message] response_body case, because the request body needs to |
96 | | * be kept around in case the request needs to be sent a second time |
97 | | * due to redirection or authentication. |
98 | | **/ |
99 | | void |
100 | | soup_message_body_set_accumulate (SoupMessageBody *body, |
101 | | gboolean accumulate) |
102 | 0 | { |
103 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
104 | |
|
105 | 0 | priv->accumulate = accumulate; |
106 | 0 | } |
107 | | |
108 | | /** |
109 | | * soup_message_body_get_accumulate: |
110 | | * @body: a #SoupMessageBody |
111 | | * |
112 | | * Gets the accumulate flag on @body. |
113 | | * |
114 | | * See [method@MessageBody.set_accumulate. for details. |
115 | | * |
116 | | * Returns: the accumulate flag for @body. |
117 | | **/ |
118 | | gboolean |
119 | | soup_message_body_get_accumulate (SoupMessageBody *body) |
120 | 0 | { |
121 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
122 | |
|
123 | 0 | return priv->accumulate; |
124 | 0 | } |
125 | | |
126 | | static void |
127 | | append_buffer (SoupMessageBody *body, GBytes *buffer) |
128 | 0 | { |
129 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
130 | |
|
131 | 0 | if (priv->last) { |
132 | 0 | priv->last = g_slist_append (priv->last, buffer); |
133 | 0 | priv->last = priv->last->next; |
134 | 0 | } else |
135 | 0 | priv->chunks = priv->last = g_slist_append (NULL, buffer); |
136 | |
|
137 | 0 | g_clear_pointer (&priv->flattened, g_bytes_unref); |
138 | 0 | body->data = NULL; |
139 | 0 | body->length += g_bytes_get_size (buffer); |
140 | 0 | } |
141 | | |
142 | | /** |
143 | | * soup_message_body_append: |
144 | | * @body: a #SoupMessageBody |
145 | | * @use: how to use @data |
146 | | * @data: (array length=length) (element-type guint8): data to append |
147 | | * @length: length of @data |
148 | | * |
149 | | * Appends @length bytes from @data to @body according to @use. |
150 | | **/ |
151 | | void |
152 | | soup_message_body_append (SoupMessageBody *body, SoupMemoryUse use, |
153 | | gconstpointer data, gsize length) |
154 | 0 | { |
155 | 0 | GBytes *bytes; |
156 | 0 | if (length > 0) { |
157 | 0 | if (use == SOUP_MEMORY_TAKE) |
158 | 0 | bytes = g_bytes_new_take ((guchar*)data, length); |
159 | 0 | else if (use == SOUP_MEMORY_STATIC) |
160 | 0 | bytes = g_bytes_new_static (data, length); |
161 | 0 | else |
162 | 0 | bytes = g_bytes_new (data, length); |
163 | 0 | append_buffer (body, g_steal_pointer (&bytes)); |
164 | 0 | } |
165 | 0 | else if (use == SOUP_MEMORY_TAKE) |
166 | 0 | g_free ((gpointer)data); |
167 | 0 | } |
168 | | |
169 | | /** |
170 | | * soup_message_body_append_take: (rename-to soup_message_body_append) |
171 | | * @body: a #SoupMessageBody |
172 | | * @data: (array length=length) (transfer full): data to append |
173 | | * @length: length of @data |
174 | | * |
175 | | * Appends @length bytes from @data to @body. |
176 | | * |
177 | | * This function is exactly equivalent to [method@MessageBody.append] |
178 | | * with %SOUP_MEMORY_TAKE as second argument; it exists mainly for |
179 | | * convenience and simplifying language bindings. |
180 | | **/ |
181 | | void |
182 | | soup_message_body_append_take (SoupMessageBody *body, |
183 | | guchar *data, gsize length) |
184 | 0 | { |
185 | 0 | soup_message_body_append(body, SOUP_MEMORY_TAKE, data, length); |
186 | 0 | } |
187 | | |
188 | | /** |
189 | | * soup_message_body_append_bytes: |
190 | | * @body: a #SoupMessageBody |
191 | | * @buffer: a #GBytes |
192 | | * |
193 | | * Appends the data from @buffer to @body. |
194 | | **/ |
195 | | void |
196 | | soup_message_body_append_bytes (SoupMessageBody *body, GBytes *buffer) |
197 | 0 | { |
198 | 0 | g_return_if_fail (g_bytes_get_size (buffer) > 0); |
199 | 0 | append_buffer (body, g_bytes_ref (buffer)); |
200 | 0 | } |
201 | | |
202 | | /** |
203 | | * soup_message_body_truncate: |
204 | | * @body: a #SoupMessageBody |
205 | | * |
206 | | * Deletes all of the data in @body. |
207 | | **/ |
208 | | void |
209 | | soup_message_body_truncate (SoupMessageBody *body) |
210 | 0 | { |
211 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
212 | |
|
213 | 0 | g_slist_free_full (priv->chunks, (GDestroyNotify)g_bytes_unref); |
214 | 0 | priv->chunks = priv->last = NULL; |
215 | 0 | priv->base_offset = 0; |
216 | 0 | g_clear_pointer (&priv->flattened, g_bytes_unref); |
217 | 0 | body->data = NULL; |
218 | 0 | body->length = 0; |
219 | 0 | } |
220 | | |
221 | | /** |
222 | | * soup_message_body_complete: |
223 | | * @body: a #SoupMessageBody |
224 | | * |
225 | | * Tags @body as being complete. |
226 | | * |
227 | | * Call this when using chunked encoding after you have appended the last chunk. |
228 | | **/ |
229 | | void |
230 | | soup_message_body_complete (SoupMessageBody *body) |
231 | 0 | { |
232 | 0 | append_buffer (body, g_bytes_new_static (NULL, 0)); |
233 | 0 | } |
234 | | |
235 | | /** |
236 | | * soup_message_body_flatten: |
237 | | * @body: a #SoupMessageBody |
238 | | * |
239 | | * Fills in @body's data field with a buffer containing all of the |
240 | | * data in @body. |
241 | | * |
242 | | * Adds an additional `\0` byte not counted by @body's |
243 | | * length field. |
244 | | * |
245 | | * Returns: (transfer full): a #GBytes containing the same data as @body. |
246 | | * (You must [method@GLib.Bytes.unref] this if you do not want it.) |
247 | | **/ |
248 | | GBytes * |
249 | | soup_message_body_flatten (SoupMessageBody *body) |
250 | 0 | { |
251 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
252 | |
|
253 | 0 | g_return_val_if_fail (priv->accumulate == TRUE, NULL); |
254 | | |
255 | 0 | if (!priv->flattened) { |
256 | | #if GLIB_SIZEOF_SIZE_T < 8 |
257 | | g_return_val_if_fail (body->length < G_MAXSIZE, NULL); |
258 | | #endif |
259 | |
|
260 | 0 | GByteArray *array = g_byte_array_sized_new (body->length + 1); |
261 | 0 | for (GSList *iter = priv->chunks; iter; iter = iter->next) { |
262 | 0 | GBytes *chunk = iter->data; |
263 | 0 | gsize chunk_size; |
264 | 0 | const guchar *chunk_data = g_bytes_get_data (chunk, &chunk_size); |
265 | 0 | g_byte_array_append (array, chunk_data, chunk_size); |
266 | 0 | } |
267 | | // NUL terminate the array but don't reflect that in the length |
268 | 0 | g_byte_array_append (array, (guchar*)"\0", 1); |
269 | 0 | array->len -= 1; |
270 | |
|
271 | 0 | priv->flattened = g_byte_array_free_to_bytes (array); |
272 | 0 | body->data = g_bytes_get_data (priv->flattened, NULL); |
273 | 0 | } |
274 | |
|
275 | 0 | return g_bytes_ref (priv->flattened); |
276 | 0 | } |
277 | | |
278 | | /** |
279 | | * soup_message_body_get_chunk: |
280 | | * @body: a #SoupMessageBody |
281 | | * @offset: an offset |
282 | | * |
283 | | * Gets a [struct@GLib.Bytes] containing data from @body starting at @offset. |
284 | | * |
285 | | * The size of the returned chunk is unspecified. You can iterate |
286 | | * through the entire body by first calling |
287 | | * [method@MessageBody.get_chunk] with an offset of 0, and then on each |
288 | | * successive call, increment the offset by the length of the |
289 | | * previously-returned chunk. |
290 | | * |
291 | | * If @offset is greater than or equal to the total length of @body, |
292 | | * then the return value depends on whether or not |
293 | | * [method@MessageBody.complete] has been called or not; if it has, |
294 | | * then [method@MessageBody.get_chunk] will return a 0-length chunk |
295 | | * (indicating the end of @body). If it has not, then |
296 | | * [method@MessageBody.get_chunk] will return %NULL (indicating that |
297 | | * @body may still potentially have more data, but that data is not |
298 | | * currently available). |
299 | | * |
300 | | * Returns: (nullable): a #GBytes |
301 | | **/ |
302 | | GBytes * |
303 | | soup_message_body_get_chunk (SoupMessageBody *body, goffset offset) |
304 | 0 | { |
305 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
306 | 0 | GSList *iter; |
307 | 0 | GBytes *chunk = NULL; |
308 | |
|
309 | 0 | offset -= priv->base_offset; |
310 | 0 | for (iter = priv->chunks; iter; iter = iter->next) { |
311 | 0 | chunk = iter->data; |
312 | 0 | gsize chunk_length = g_bytes_get_size (chunk); |
313 | |
|
314 | 0 | if (offset < chunk_length || offset == 0) |
315 | 0 | break; |
316 | | |
317 | 0 | offset -= chunk_length; |
318 | 0 | } |
319 | |
|
320 | 0 | if (!iter) |
321 | 0 | return NULL; |
322 | | |
323 | 0 | return g_bytes_new_from_bytes (chunk, offset, g_bytes_get_size (chunk) - offset); |
324 | 0 | } |
325 | | |
326 | | /** |
327 | | * soup_message_body_got_chunk: |
328 | | * @body: a #SoupMessageBody |
329 | | * @chunk: a #GBytes received from the network |
330 | | * |
331 | | * Handles the [struct@MessageBody] part of receiving a chunk of data from |
332 | | * the network. |
333 | | * |
334 | | * Normally this means appending @chunk to @body, exactly as with |
335 | | * [method@MessageBody.append_bytes], but if you have set @body's accumulate |
336 | | * flag to %FALSE, then that will not happen. |
337 | | * |
338 | | * This is a low-level method which you should not normally need to |
339 | | * use. |
340 | | **/ |
341 | | void |
342 | | soup_message_body_got_chunk (SoupMessageBody *body, GBytes *chunk) |
343 | 0 | { |
344 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
345 | |
|
346 | 0 | if (!priv->accumulate) |
347 | 0 | return; |
348 | | |
349 | 0 | soup_message_body_append_bytes (body, chunk); |
350 | 0 | } |
351 | | |
352 | | /** |
353 | | * soup_message_body_wrote_chunk: |
354 | | * @body: a #SoupMessageBody |
355 | | * @chunk: a #GBytes returned from [method@MessageBody.get_chunk] |
356 | | * |
357 | | * Handles the [struct@MessageBody] part of writing a chunk of data to the |
358 | | * network. |
359 | | * |
360 | | * Normally this is a no-op, but if you have set @body's accumulate flag to |
361 | | * %FALSE, then this will cause @chunk to be discarded to free up memory. |
362 | | * |
363 | | * This is a low-level method which you should not need to use, and |
364 | | * there are further restrictions on its proper use which are not |
365 | | * documented here. |
366 | | **/ |
367 | | void |
368 | | soup_message_body_wrote_chunk (SoupMessageBody *body, GBytes *chunk) |
369 | 0 | { |
370 | 0 | SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; |
371 | 0 | GBytes *chunk2; |
372 | |
|
373 | 0 | if (priv->accumulate) |
374 | 0 | return; |
375 | | |
376 | 0 | chunk2 = priv->chunks->data; |
377 | 0 | g_return_if_fail (g_bytes_get_size (chunk) == g_bytes_get_size (chunk2)); |
378 | 0 | g_return_if_fail (chunk == chunk2); |
379 | | |
380 | 0 | priv->chunks = g_slist_remove (priv->chunks, chunk2); |
381 | 0 | if (!priv->chunks) |
382 | 0 | priv->last = NULL; |
383 | |
|
384 | 0 | priv->base_offset += g_bytes_get_size (chunk2); |
385 | 0 | g_bytes_unref (chunk2); |
386 | 0 | } |
387 | | |
388 | | /** |
389 | | * soup_message_body_ref: |
390 | | * @body: a #SoupMessageBody |
391 | | * |
392 | | * Atomically increments the reference count of @body by one. |
393 | | * |
394 | | * Returns: the passed in #SoupMessageBody |
395 | | */ |
396 | | SoupMessageBody * |
397 | | soup_message_body_ref (SoupMessageBody *body) |
398 | 0 | { |
399 | 0 | g_atomic_rc_box_acquire (body); |
400 | |
|
401 | 0 | return body; |
402 | 0 | } |
403 | | |
404 | | /** |
405 | | * soup_message_body_unref: |
406 | | * @body: a #SoupMessageBody |
407 | | * |
408 | | * Atomically decrements the reference count of @body by one. |
409 | | * |
410 | | * When the reference count reaches zero, the resources allocated by |
411 | | * @body are freed |
412 | | */ |
413 | | void |
414 | | soup_message_body_unref (SoupMessageBody *body) |
415 | 0 | { |
416 | 0 | g_atomic_rc_box_release_full (body, (GDestroyNotify)soup_message_body_truncate); |
417 | 0 | } |
418 | | |
419 | | G_DEFINE_BOXED_TYPE (SoupMessageBody, soup_message_body, soup_message_body_ref, soup_message_body_unref) |