/src/libsoup/libsoup/http2/soup-body-input-stream-http2.c
Line | Count | Source |
1 | | /* GIO - GLib Input, Output and Streaming Library |
2 | | * |
3 | | * Copyright (C) 2006-2007 Red Hat, Inc. |
4 | | * Copyright 2021 Igalia S.L. |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General |
17 | | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | | * |
19 | | * Author: Christian Kellner <gicmo@gnome.org> |
20 | | */ |
21 | | |
22 | | #include "config.h" |
23 | | |
24 | | #include "soup-body-input-stream-http2.h" |
25 | | #include <glib/gi18n-lib.h> |
26 | | |
27 | | /* |
28 | | * SoupBodyInputStreamHttp2 |
29 | | * @short_description: Streaming input operations on memory chunks |
30 | | * |
31 | | * [type@BodyInputStreamHttp2] is a class for using arbitrary |
32 | | * memory chunks as input for GIO streaming input operations. |
33 | | * |
34 | | * It differs from #GMemoryInputStream in that it frees older chunks |
35 | | * after they have been read, returns #G_IO_ERROR_WOULDBLOCK at the end |
36 | | * of data until soup_body_input_stream_http2_complete() is called, and implements |
37 | | * g_pollable_input_stream_is_readable(). |
38 | | */ |
39 | | |
40 | | struct _SoupBodyInputStreamHttp2 { |
41 | | GInputStream parent_instance; |
42 | | }; |
43 | | |
44 | | typedef struct { |
45 | | GQueue *chunks; |
46 | | gsize start_offset; |
47 | | gsize len; |
48 | | gsize pos; |
49 | | gboolean completed; |
50 | | GCancellable *need_more_data_cancellable; |
51 | | } SoupBodyInputStreamHttp2Private; |
52 | | |
53 | | static void soup_body_input_stream_http2_pollable_iface_init (GPollableInputStreamInterface *iface); |
54 | | |
55 | 0 | G_DEFINE_FINAL_TYPE_WITH_CODE (SoupBodyInputStreamHttp2, soup_body_input_stream_http2, G_TYPE_INPUT_STREAM, |
56 | 0 | G_ADD_PRIVATE (SoupBodyInputStreamHttp2) |
57 | 0 | G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM, |
58 | 0 | soup_body_input_stream_http2_pollable_iface_init);) |
59 | 0 |
|
60 | 0 | enum { |
61 | 0 | NEED_MORE_DATA, |
62 | 0 | READ_DATA, |
63 | 0 | LAST_SIGNAL |
64 | 0 | }; |
65 | 0 |
|
66 | 0 | static guint signals [LAST_SIGNAL] = { 0 }; |
67 | 0 |
|
68 | 0 | /** |
69 | 0 | * soup_body_input_stream_http2_new: |
70 | 0 | * |
71 | 0 | * Creates a new empty [type@BodyInputStreamHttp2]. |
72 | 0 | * |
73 | 0 | * Returns: a new #GInputStream |
74 | 0 | */ |
75 | 0 | GInputStream * |
76 | 0 | soup_body_input_stream_http2_new (void) |
77 | 0 | { |
78 | 0 | return G_INPUT_STREAM (g_object_new (SOUP_TYPE_BODY_INPUT_STREAM_HTTP2, NULL)); |
79 | 0 | } |
80 | | |
81 | | gsize |
82 | | soup_body_input_stream_http2_get_buffer_size (SoupBodyInputStreamHttp2 *stream) |
83 | 0 | { |
84 | 0 | SoupBodyInputStreamHttp2Private *priv; |
85 | |
|
86 | 0 | g_return_val_if_fail (SOUP_IS_BODY_INPUT_STREAM_HTTP2 (stream), 0); |
87 | | |
88 | 0 | priv = soup_body_input_stream_http2_get_instance_private (stream); |
89 | |
|
90 | 0 | g_assert (priv->len >= priv->pos); |
91 | 0 | return priv->len - priv->pos; |
92 | 0 | } |
93 | | |
94 | | void |
95 | | soup_body_input_stream_http2_add_data (SoupBodyInputStreamHttp2 *stream, |
96 | | const guint8 *data, |
97 | | gsize size) |
98 | 0 | { |
99 | 0 | SoupBodyInputStreamHttp2Private *priv; |
100 | |
|
101 | 0 | g_return_if_fail (SOUP_IS_BODY_INPUT_STREAM_HTTP2 (stream)); |
102 | 0 | g_return_if_fail (data != NULL); |
103 | | |
104 | 0 | priv = soup_body_input_stream_http2_get_instance_private (stream); |
105 | |
|
106 | 0 | g_queue_push_tail (priv->chunks, g_bytes_new (data, size)); |
107 | 0 | priv->len += size; |
108 | 0 | if (priv->need_more_data_cancellable) { |
109 | 0 | g_cancellable_cancel (priv->need_more_data_cancellable); |
110 | 0 | g_clear_object (&priv->need_more_data_cancellable); |
111 | 0 | } |
112 | 0 | } |
113 | | |
114 | | gboolean |
115 | | soup_body_input_stream_http2_is_blocked (SoupBodyInputStreamHttp2 *stream) |
116 | 0 | { |
117 | 0 | SoupBodyInputStreamHttp2Private *priv; |
118 | |
|
119 | 0 | g_return_val_if_fail (SOUP_IS_BODY_INPUT_STREAM_HTTP2 (stream), FALSE); |
120 | | |
121 | 0 | priv = soup_body_input_stream_http2_get_instance_private (stream); |
122 | 0 | return priv->need_more_data_cancellable != NULL; |
123 | 0 | } |
124 | | |
125 | | static gboolean |
126 | | have_more_data_coming (SoupBodyInputStreamHttp2 *stream) |
127 | 0 | { |
128 | 0 | SoupBodyInputStreamHttp2Private *priv = soup_body_input_stream_http2_get_instance_private (stream); |
129 | |
|
130 | 0 | return !priv->completed || priv->pos < priv->len; |
131 | 0 | } |
132 | | |
133 | | static gssize |
134 | | soup_body_input_stream_http2_read_real (GInputStream *stream, |
135 | | gboolean blocking, |
136 | | void *buffer, |
137 | | gsize read_count, |
138 | | GCancellable *cancellable, |
139 | | GError **error) |
140 | 0 | { |
141 | 0 | SoupBodyInputStreamHttp2 *memory_stream; |
142 | 0 | SoupBodyInputStreamHttp2Private *priv; |
143 | 0 | GList *l; |
144 | 0 | GBytes *chunk; |
145 | 0 | gsize len; |
146 | 0 | gsize offset, start, rest, size; |
147 | 0 | gsize count; |
148 | |
|
149 | 0 | memory_stream = SOUP_BODY_INPUT_STREAM_HTTP2 (stream); |
150 | 0 | priv = soup_body_input_stream_http2_get_instance_private (memory_stream); |
151 | | |
152 | | /* We have a list of chunked bytes that we continually read from. |
153 | | * Once a chunk is fully read it is removed from our list and we |
154 | | * keep the offset of where the chunks start. |
155 | | */ |
156 | |
|
157 | 0 | count = MIN (read_count, priv->len - priv->pos); |
158 | |
|
159 | 0 | offset = priv->start_offset; |
160 | 0 | for (l = g_queue_peek_head_link(priv->chunks); l; l = l->next) { |
161 | 0 | chunk = (GBytes *)l->data; |
162 | 0 | len = g_bytes_get_size (chunk); |
163 | |
|
164 | 0 | if (offset + len > priv->pos) |
165 | 0 | break; |
166 | | |
167 | 0 | offset += len; |
168 | 0 | } |
169 | |
|
170 | 0 | priv->start_offset = offset; |
171 | 0 | start = priv->pos - offset; |
172 | 0 | rest = count; |
173 | |
|
174 | 0 | while (l && rest > 0) { |
175 | 0 | GList *next = l->next; |
176 | |
|
177 | 0 | const guint8 *chunk_data; |
178 | 0 | chunk = (GBytes *)l->data; |
179 | |
|
180 | 0 | chunk_data = g_bytes_get_data (chunk, &len); |
181 | |
|
182 | 0 | size = MIN (rest, len - start); |
183 | |
|
184 | 0 | memcpy ((guint8 *)buffer + (count - rest), chunk_data + start, size); |
185 | 0 | rest -= size; |
186 | | |
187 | | /* Remove fully read chunk from list, note that we are always near the start of the list */ |
188 | 0 | if (start + size == len) { |
189 | 0 | priv->start_offset += len; |
190 | 0 | g_queue_delete_link (priv->chunks, l); |
191 | 0 | g_bytes_unref (chunk); |
192 | 0 | } |
193 | |
|
194 | 0 | start = 0; |
195 | 0 | l = next; |
196 | 0 | } |
197 | |
|
198 | 0 | gsize bytes_read = count - rest; |
199 | 0 | priv->pos += bytes_read; |
200 | |
|
201 | 0 | if (bytes_read > 0) |
202 | 0 | g_signal_emit (memory_stream, signals[READ_DATA], 0, (guint64)bytes_read); |
203 | | |
204 | | /* When doing blocking reads we must always request more data. |
205 | | * Even when doing non-blocking, a read consuming data may trigger a new WINDOW_UPDATE. */ |
206 | 0 | if (have_more_data_coming (memory_stream) && bytes_read == 0) { |
207 | 0 | GError *read_error = NULL; |
208 | 0 | g_signal_emit (memory_stream, signals[NEED_MORE_DATA], 0, |
209 | 0 | blocking, cancellable, &read_error); |
210 | |
|
211 | 0 | if (read_error) { |
212 | 0 | g_propagate_error (error, read_error); |
213 | 0 | return -1; |
214 | 0 | } |
215 | | |
216 | 0 | if (blocking) { |
217 | 0 | return soup_body_input_stream_http2_read_real ( |
218 | 0 | stream, blocking, buffer, read_count, cancellable, error |
219 | 0 | ); |
220 | 0 | } |
221 | 0 | } |
222 | | |
223 | 0 | return count; |
224 | 0 | } |
225 | | |
226 | | static gssize |
227 | | soup_body_input_stream_http2_read (GInputStream *stream, |
228 | | void *buffer, |
229 | | gsize count, |
230 | | GCancellable *cancellable, |
231 | | GError **error) |
232 | 0 | { |
233 | 0 | return soup_body_input_stream_http2_read_real (stream, TRUE, buffer, count, cancellable, error); |
234 | 0 | } |
235 | | |
236 | | static gssize |
237 | | soup_body_input_stream_http2_read_nonblocking (GPollableInputStream *stream, |
238 | | void *buffer, |
239 | | gsize count, |
240 | | GError **error) |
241 | 0 | { |
242 | 0 | SoupBodyInputStreamHttp2 *memory_stream = SOUP_BODY_INPUT_STREAM_HTTP2 (stream); |
243 | 0 | GError *inner_error = NULL; |
244 | |
|
245 | 0 | gsize read = soup_body_input_stream_http2_read_real (G_INPUT_STREAM (stream), FALSE, buffer, count, NULL, &inner_error); |
246 | |
|
247 | 0 | if (read == 0 && have_more_data_coming (memory_stream) && !inner_error) { |
248 | 0 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, _("Operation would block")); |
249 | 0 | return -1; |
250 | 0 | } |
251 | | |
252 | 0 | if (inner_error) |
253 | 0 | g_propagate_error (error, inner_error); |
254 | |
|
255 | 0 | return read; |
256 | 0 | } |
257 | | |
258 | | void |
259 | | soup_body_input_stream_http2_complete (SoupBodyInputStreamHttp2 *stream) |
260 | 0 | { |
261 | 0 | SoupBodyInputStreamHttp2Private *priv = soup_body_input_stream_http2_get_instance_private (stream); |
262 | 0 | priv->completed = TRUE; |
263 | 0 | if (priv->need_more_data_cancellable) { |
264 | 0 | g_cancellable_cancel (priv->need_more_data_cancellable); |
265 | 0 | g_clear_object (&priv->need_more_data_cancellable); |
266 | 0 | } |
267 | 0 | } |
268 | | |
269 | | static gssize |
270 | | soup_body_input_stream_http2_skip (GInputStream *stream, |
271 | | gsize count, |
272 | | GCancellable *cancellable, |
273 | | GError **error) |
274 | 0 | { |
275 | 0 | SoupBodyInputStreamHttp2 *memory_stream; |
276 | 0 | SoupBodyInputStreamHttp2Private *priv; |
277 | |
|
278 | 0 | memory_stream = SOUP_BODY_INPUT_STREAM_HTTP2 (stream); |
279 | 0 | priv = soup_body_input_stream_http2_get_instance_private (memory_stream); |
280 | |
|
281 | 0 | count = MIN (count, priv->len - priv->pos); |
282 | 0 | priv->pos += count; |
283 | 0 | if (count) |
284 | 0 | g_signal_emit (memory_stream, signals[READ_DATA], 0, (guint64)count); |
285 | | |
286 | | /* Remove all skipped chunks */ |
287 | 0 | gsize offset = priv->start_offset; |
288 | 0 | for (GList *l = g_queue_peek_head_link(priv->chunks); l; l = l->next) { |
289 | 0 | GBytes *chunk = (GBytes *)l->data; |
290 | 0 | gsize chunk_len = g_bytes_get_size (chunk); |
291 | |
|
292 | 0 | if (offset + chunk_len <= priv->pos) { |
293 | 0 | g_queue_delete_link (priv->chunks, l); |
294 | 0 | g_bytes_unref (chunk); |
295 | 0 | offset += chunk_len; |
296 | 0 | } |
297 | 0 | break; |
298 | 0 | } |
299 | 0 | priv->start_offset = offset; |
300 | |
|
301 | 0 | return count; |
302 | 0 | } |
303 | | |
304 | | static gboolean |
305 | | soup_body_input_stream_http2_close (GInputStream *stream, |
306 | | GCancellable *cancellable, |
307 | | GError **error) |
308 | 0 | { |
309 | 0 | return TRUE; |
310 | 0 | } |
311 | | |
312 | | static void |
313 | | soup_body_input_stream_http2_skip_async (GInputStream *stream, |
314 | | gsize count, |
315 | | int io_priority, |
316 | | GCancellable *cancellable, |
317 | | GAsyncReadyCallback callback, |
318 | | gpointer user_data) |
319 | 0 | { |
320 | 0 | GTask *task; |
321 | 0 | gssize nskipped; |
322 | 0 | GError *error = NULL; |
323 | |
|
324 | 0 | nskipped = G_INPUT_STREAM_GET_CLASS (stream)->skip (stream, count, cancellable, &error); |
325 | 0 | task = g_task_new (stream, cancellable, callback, user_data); |
326 | 0 | g_task_set_source_tag (task, soup_body_input_stream_http2_skip_async); |
327 | |
|
328 | 0 | if (error) |
329 | 0 | g_task_return_error (task, error); |
330 | 0 | else |
331 | 0 | g_task_return_int (task, nskipped); |
332 | 0 | g_object_unref (task); |
333 | 0 | } |
334 | | |
335 | | static gssize |
336 | | soup_body_input_stream_http2_skip_finish (GInputStream *stream, |
337 | | GAsyncResult *result, |
338 | | GError **error) |
339 | 0 | { |
340 | 0 | g_return_val_if_fail (g_task_is_valid (result, stream), -1); |
341 | | |
342 | 0 | return g_task_propagate_int (G_TASK (result), error); |
343 | 0 | } |
344 | | |
345 | | static void |
346 | | soup_body_input_stream_http2_close_async (GInputStream *stream, |
347 | | int io_priority, |
348 | | GCancellable *cancellable, |
349 | | GAsyncReadyCallback callback, |
350 | | gpointer user_data) |
351 | 0 | { |
352 | 0 | GTask *task; |
353 | |
|
354 | 0 | task = g_task_new (stream, cancellable, callback, user_data); |
355 | 0 | g_task_set_source_tag (task, soup_body_input_stream_http2_close_async); |
356 | 0 | g_task_return_boolean (task, TRUE); |
357 | 0 | g_object_unref (task); |
358 | 0 | } |
359 | | |
360 | | static gboolean |
361 | | soup_body_input_stream_http2_close_finish (GInputStream *stream, |
362 | | GAsyncResult *result, |
363 | | GError **error) |
364 | 0 | { |
365 | 0 | return TRUE; |
366 | 0 | } |
367 | | |
368 | | static gboolean |
369 | | soup_body_input_stream_http2_is_readable (GPollableInputStream *stream) |
370 | 0 | { |
371 | 0 | SoupBodyInputStreamHttp2Private *priv = soup_body_input_stream_http2_get_instance_private (SOUP_BODY_INPUT_STREAM_HTTP2 (stream)); |
372 | |
|
373 | 0 | return priv->pos < priv->len || priv->completed; |
374 | 0 | } |
375 | | |
376 | | static GSource * |
377 | | soup_body_input_stream_http2_create_source (GPollableInputStream *stream, |
378 | | GCancellable *cancellable) |
379 | 0 | { |
380 | 0 | SoupBodyInputStreamHttp2Private *priv = soup_body_input_stream_http2_get_instance_private (SOUP_BODY_INPUT_STREAM_HTTP2 (stream)); |
381 | 0 | GSource *base_source, *pollable_source; |
382 | |
|
383 | 0 | if (priv->pos < priv->len) { |
384 | 0 | base_source = g_timeout_source_new (0); |
385 | 0 | } else { |
386 | 0 | if (!priv->need_more_data_cancellable) |
387 | 0 | priv->need_more_data_cancellable = g_cancellable_new (); |
388 | 0 | base_source = g_cancellable_source_new (priv->need_more_data_cancellable); |
389 | 0 | } |
390 | |
|
391 | 0 | pollable_source = g_pollable_source_new_full (stream, base_source, cancellable); |
392 | 0 | g_source_set_name (pollable_source, "SoupMemoryStreamSource"); |
393 | 0 | g_source_unref (base_source); |
394 | |
|
395 | 0 | return pollable_source; |
396 | 0 | } |
397 | | |
398 | | static void |
399 | | soup_body_input_stream_http2_dispose (GObject *object) |
400 | 0 | { |
401 | 0 | SoupBodyInputStreamHttp2 *stream = SOUP_BODY_INPUT_STREAM_HTTP2 (object); |
402 | 0 | SoupBodyInputStreamHttp2Private *priv = soup_body_input_stream_http2_get_instance_private (stream); |
403 | |
|
404 | 0 | priv->completed = TRUE; |
405 | 0 | if (priv->need_more_data_cancellable) { |
406 | 0 | g_cancellable_cancel (priv->need_more_data_cancellable); |
407 | 0 | g_clear_object (&priv->need_more_data_cancellable); |
408 | 0 | } |
409 | |
|
410 | 0 | G_OBJECT_CLASS (soup_body_input_stream_http2_parent_class)->dispose (object); |
411 | 0 | } |
412 | | |
413 | | static void |
414 | | soup_body_input_stream_http2_finalize (GObject *object) |
415 | 0 | { |
416 | 0 | SoupBodyInputStreamHttp2 *stream = SOUP_BODY_INPUT_STREAM_HTTP2 (object); |
417 | 0 | SoupBodyInputStreamHttp2Private *priv = soup_body_input_stream_http2_get_instance_private (stream); |
418 | |
|
419 | 0 | g_queue_free_full (priv->chunks, (GDestroyNotify)g_bytes_unref); |
420 | |
|
421 | 0 | G_OBJECT_CLASS (soup_body_input_stream_http2_parent_class)->finalize (object); |
422 | 0 | } |
423 | | |
424 | | static void |
425 | | soup_body_input_stream_http2_pollable_iface_init (GPollableInputStreamInterface *iface) |
426 | 0 | { |
427 | 0 | iface->is_readable = soup_body_input_stream_http2_is_readable; |
428 | 0 | iface->create_source = soup_body_input_stream_http2_create_source; |
429 | 0 | iface->read_nonblocking = soup_body_input_stream_http2_read_nonblocking; |
430 | 0 | } |
431 | | |
432 | | static void |
433 | | soup_body_input_stream_http2_init (SoupBodyInputStreamHttp2 *stream) |
434 | 0 | { |
435 | 0 | SoupBodyInputStreamHttp2Private *priv; |
436 | |
|
437 | 0 | priv = soup_body_input_stream_http2_get_instance_private (stream); |
438 | 0 | priv->chunks = g_queue_new (); |
439 | 0 | } |
440 | | |
441 | | static void |
442 | | soup_body_input_stream_http2_class_init (SoupBodyInputStreamHttp2Class *klass) |
443 | 0 | { |
444 | 0 | GObjectClass *object_class; |
445 | 0 | GInputStreamClass *istream_class; |
446 | |
|
447 | 0 | object_class = G_OBJECT_CLASS (klass); |
448 | 0 | object_class->finalize = soup_body_input_stream_http2_finalize; |
449 | 0 | object_class->dispose = soup_body_input_stream_http2_dispose; |
450 | |
|
451 | 0 | istream_class = G_INPUT_STREAM_CLASS (klass); |
452 | 0 | istream_class->read_fn = soup_body_input_stream_http2_read; |
453 | 0 | istream_class->skip = soup_body_input_stream_http2_skip; |
454 | 0 | istream_class->close_fn = soup_body_input_stream_http2_close; |
455 | |
|
456 | 0 | istream_class->skip_async = soup_body_input_stream_http2_skip_async; |
457 | 0 | istream_class->skip_finish = soup_body_input_stream_http2_skip_finish; |
458 | 0 | istream_class->close_async = soup_body_input_stream_http2_close_async; |
459 | 0 | istream_class->close_finish = soup_body_input_stream_http2_close_finish; |
460 | |
|
461 | 0 | signals[NEED_MORE_DATA] = |
462 | 0 | g_signal_new ("need-more-data", |
463 | 0 | G_OBJECT_CLASS_TYPE (object_class), |
464 | 0 | G_SIGNAL_RUN_FIRST, |
465 | 0 | 0, |
466 | 0 | NULL, NULL, |
467 | 0 | NULL, |
468 | 0 | G_TYPE_ERROR, |
469 | 0 | 2, G_TYPE_BOOLEAN, |
470 | 0 | G_TYPE_CANCELLABLE); |
471 | |
|
472 | 0 | signals[READ_DATA] = |
473 | 0 | g_signal_new ("read-data", |
474 | 0 | G_OBJECT_CLASS_TYPE (object_class), |
475 | 0 | G_SIGNAL_RUN_FIRST, |
476 | 0 | 0, |
477 | 0 | NULL, NULL, |
478 | 0 | NULL, |
479 | 0 | G_TYPE_NONE, 1, |
480 | 0 | G_TYPE_UINT64); |
481 | 0 | } |