GCC Code Coverage Report


Directory: src/
File: src/msg-service.c
Date: 2024-07-13 00:54:47
Exec Total Coverage
Lines: 121 130 93.1%
Functions: 22 23 95.7%
Branches: 42 55 76.4%

Line Branch Exec Source
1 /* Copyright 2022-2024 Jan-Michael Brummer <jan-michael.brummer1@volkswagen.de>
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU Lesser General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17 #include "config.h"
18 #include "msg-service.h"
19
20 #include "msg-error.h"
21 #include "msg-json-utils.h"
22 #include "msg-private.h"
23
24 typedef struct _MsgServicePrivate MsgServicePrivate;
25 struct _MsgServicePrivate {
26 MsgAuthorizer *authorizer;
27 SoupSession *session;
28 };
29
30
5/7
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 277 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 271 times.
1106 G_DEFINE_TYPE_WITH_PRIVATE (MsgService, msg_service, G_TYPE_OBJECT);
31
32 #define MSG_SERVICE_GET_PRIVATE(o) ((MsgServicePrivate *)msg_service_get_instance_private ((o)))
33
34 enum {
35 PROP_0,
36 PROP_AUTHORIZER,
37 PROP_COUNT
38 };
39
40 static GParamSpec *properties[PROP_COUNT] = { NULL, };
41
42 gboolean
43 82 msg_service_refresh_authorization (MsgService *self,
44 GCancellable *cancellable,
45 GError **error)
46 {
47 82 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
48
49
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 81 times.
82 if (!priv->authorizer) {
50 1 g_set_error (error, MSG_ERROR, MSG_ERROR_FAILED, "Authorizer is NULL");
51 1 return FALSE;
52 }
53
54 81 return msg_authorizer_refresh_authorization (priv->authorizer, cancellable, error);
55 }
56
57 guint
58 87 msg_service_get_https_port (void)
59 {
60 const char *port_string;
61
62 /* Allow changing the HTTPS port just for testing. */
63 87 port_string = g_getenv ("MSG_HTTPS_PORT");
64
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 1 times.
87 if (port_string != NULL) {
65 86 guint64 port = g_ascii_strtoull (port_string, NULL, 10);
66
67
2/2
✓ Branch 0 taken 82 times.
✓ Branch 1 taken 4 times.
86 if (port != 0) {
68 82 g_debug ("Overriding message port to %" G_GUINT64_FORMAT ".", port);
69 82 return port;
70 }
71 }
72
73 /* Return the default. */
74 5 return 443;
75 }
76
77 gboolean
78 48 msg_service_accept_certificate_cb (__attribute__ ((unused)) SoupMessage *msg,
79 __attribute__ ((unused)) GTlsCertificate *tls_cert,
80 __attribute__ ((unused)) GTlsCertificateFlags tls_errors,
81 gpointer session)
82 {
83 48 return !!g_object_get_data (session, "msg-lax-ssl");
84 }
85
86
87 SoupMessage *
88 84 msg_service_new_message_from_uri (MsgService *self,
89 const char *method,
90 GUri *uri)
91 {
92 84 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
93 SoupMessage *ret;
94
95 84 ret = soup_message_new_from_uri (method, uri);
96
97 84 g_signal_connect (ret, "accept-certificate", G_CALLBACK (msg_service_accept_certificate_cb), priv->session);
98
99 84 return ret;
100 }
101
102
103 /**
104 * msg_service_build_message:
105 * @method: transfer method
106 * @uri: uri to access
107 * @etag: an optional etag
108 * @etag_if_match: use etag if
109 *
110 * Construct and checks a #SoupMessage for transfer
111 *
112 * Returns: (transfer full): a #SoupMessage or NULL on error.
113 */
114 SoupMessage *
115 85 msg_service_build_message (MsgService *self,
116 const char *method,
117 const char *uri,
118 const char *etag,
119 gboolean etag_if_match)
120 {
121 SoupMessage *message;
122 85 g_autoptr (GUri) _uri = NULL;
123 85 g_autoptr (GUri) _uri_parsed = NULL;
124
125 85 _uri_parsed = g_uri_parse (uri, SOUP_HTTP_URI_FLAGS, NULL);
126 170 _uri = g_uri_build_with_user (
127 g_uri_get_flags (_uri_parsed),
128 g_uri_get_scheme (_uri_parsed),
129 g_uri_get_user (_uri_parsed),
130 g_uri_get_password (_uri_parsed),
131 g_uri_get_auth_params (_uri_parsed),
132 g_uri_get_host (_uri_parsed),
133 85 msg_service_get_https_port (),
134 g_uri_get_path (_uri_parsed),
135 g_uri_get_query (_uri_parsed),
136 g_uri_get_fragment (_uri_parsed));
137
138
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 84 times.
85 if (g_strcmp0 (g_uri_get_scheme (_uri), "https") != 0)
139 1 return NULL;
140
141 84 message = msg_service_new_message_from_uri (self, method, _uri);
142
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 82 times.
84 if (etag)
143
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 soup_message_headers_append (soup_message_get_request_headers (message), (etag_if_match == TRUE) ? "If-Match" : "If-None-Match", etag);
144
145 84 return message;
146 }
147
148 /**
149 * msg_service_send:
150 * @self: a msg service
151 * @message: a #SoupMessage
152 * @cancellable: a #GCancellable
153 * @error: a #Gerror
154 *
155 * Adds authorizer information to `message` and send it.
156 *
157 * Returns: (transfer full): a #GInputStream
158 */
159 GInputStream *
160 1 msg_service_send (MsgService *self,
161 SoupMessage *message,
162 GCancellable *cancellable,
163 GError **error)
164 {
165 1 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
166
167 1 msg_authorizer_process_request (priv->authorizer, message);
168
169 1 return soup_session_send (priv->session, message, cancellable, error);
170 }
171
172 /**
173 * msg_service_send_and_read:
174 * @self: a msg service
175 * @message: a #SoupMessage
176 * @cancellable: a #GCancellable
177 * @error: a #GError
178 *
179 * Adds authorizer information to `message` and send it.
180 *
181 * Returns: (transfer full): a #GBytes or %NULL on error.
182 */
183 GBytes *
184 14 msg_service_send_and_read (MsgService *self,
185 SoupMessage *message,
186 GCancellable *cancellable,
187 GError **error)
188 {
189 14 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
190
191 14 msg_authorizer_process_request (priv->authorizer, message);
192
193 14 return soup_session_send_and_read (priv->session, message, cancellable, error);
194 }
195
196 /**
197 * msg_service_send_and_parse_response:
198 * @self: a msg service
199 * @message: a #SoupMessage
200 * @cancellable: a #GCancellable
201 * @error: a #GError
202 *
203 * A combination of `msg_service_send_and_read` and `msg_service_parse_response`
204 *
205 * Returns: (transfer full): a #JsonParser or %NULL on error
206 */
207 JsonParser *
208 65 msg_service_send_and_parse_response (MsgService *self,
209 SoupMessage *message,
210 JsonObject **object,
211 GCancellable *cancellable,
212 GError **error)
213 {
214 65 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
215 65 g_autoptr (GBytes) response = NULL;
216
217 65 msg_authorizer_process_request (priv->authorizer, message);
218
219 65 response = soup_session_send_and_read (priv->session, message, cancellable, error);
220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (!response)
221 return NULL;
222
223 65 return msg_service_parse_response (response, object, error);
224 }
225
226
227 static void
228 5 msg_service_set_authorizer (MsgService *self,
229 MsgAuthorizer *authorizer)
230 {
231 5 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
232
233
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 g_return_if_fail (MSG_IS_SERVICE (self));
234
6/10
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 4 times.
5 g_return_if_fail (authorizer == NULL || MSG_IS_AUTHORIZER (authorizer));
235
236
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 g_clear_object (&priv->authorizer);
237 5 priv->authorizer = authorizer;
238
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (priv->authorizer)
239 4 g_object_ref (priv->authorizer);
240
241 5 g_object_notify (G_OBJECT (self), "authorizer");
242 }
243
244 static void
245 5 msg_service_set_property (GObject *object,
246 guint property_id,
247 const GValue *value,
248 GParamSpec *pspec)
249 {
250 5 MsgService *self = MSG_SERVICE (object);
251
252
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 switch (property_id) {
253 5 case PROP_AUTHORIZER:
254 5 msg_service_set_authorizer (self, MSG_AUTHORIZER (g_value_get_object (value)));
255 5 break;
256 default:
257 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
258 break;
259 }
260 5 }
261
262 static void
263 msg_service_get_property (GObject *object,
264 guint property_id,
265 __attribute__ ((unused)) GValue *value,
266 GParamSpec *pspec)
267 {
268 switch (property_id) {
269 default:
270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
271 break;
272 }
273 }
274
275 static void
276 2499 soup_log_printer (__attribute__ ((unused)) SoupLogger *logger,
277 __attribute__ ((unused)) SoupLoggerLogLevel level,
278 char direction,
279 const char *data,
280 __attribute__ ((unused)) gpointer user_data)
281 {
282 2499 g_debug ("%c %s", direction, data);
283 2499 }
284
285 SoupLoggerLogLevel
286 10 msg_service_get_log_level (void)
287 {
288 static int level = -1;
289
290
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if (level < 0) {
291 5 const char *env = g_getenv ("MSG_DEBUG");
292
293
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (env != NULL) {
294 5 level = atoi (env);
295 } else {
296 level = 0;
297 }
298 }
299
300 10 return level;
301 }
302
303 static void
304 5 msg_service_init (MsgService *self)
305 {
306 5 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
307
308 /* priv->session = soup_session_new_with_options ("timeout", 0, NULL); */
309 5 priv->session = soup_session_new ();
310
311 /* Iff MSG_LAX_SSL_CERTIFICATES=1, relax SSL certificate validation to allow using invalid/unsigned certificates for testing. */
312
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 if (g_strcmp0 (g_getenv ("MSG_LAX_SSL_CERTIFICATES"), "1") == 0) {
313 5 g_object_set_data (G_OBJECT (priv->session), "msg-lax-ssl", (gpointer)TRUE);
314 }
315
316
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 if (msg_service_get_log_level () > SOUP_LOGGER_LOG_NONE) {
317 5 g_autoptr (SoupLogger) logger = NULL;
318
319 5 logger = soup_logger_new (msg_service_get_log_level ());
320 5 soup_logger_set_printer (logger, (SoupLoggerPrinter)soup_log_printer, NULL, NULL);
321 5 soup_session_add_feature (priv->session, SOUP_SESSION_FEATURE (logger));
322 }
323 5 }
324
325 static void
326 6 msg_service_class_init (MsgServiceClass *class)
327 {
328 6 GObjectClass *object_class = G_OBJECT_CLASS (class);
329
330 6 object_class->set_property = msg_service_set_property;
331 6 object_class->get_property = msg_service_get_property;
332
333 6 properties [PROP_AUTHORIZER] = g_param_spec_object ("authorizer",
334 "Authorizer",
335 "The authorizer for this service",
336 MSG_TYPE_AUTHORIZER,
337 G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
338
339 6 g_object_class_install_properties (object_class, PROP_COUNT, properties);
340 6 }
341
342 /**
343 * msg_service_parse_response:
344 * @bytes: input bytes containing response buffer
345 * @object: a pointer to the returning root object
346 * @error: a #GError
347 *
348 * Parse response data and check for errors. In case
349 * no errors are found, return json root object.
350 *
351 * Returns: (transfer full): a #JsonParser
352 */
353 JsonParser *
354 68 msg_service_parse_response (GBytes *bytes,
355 JsonObject **object,
356 GError **error)
357 {
358 68 g_autoptr (JsonParser) parser = NULL;
359 68 JsonObject *root_object = NULL;
360 68 JsonNode *root = NULL;
361 const char *content;
362 gsize len;
363
364 68 content = g_bytes_get_data (bytes, &len);
365
366 68 parser = json_parser_new ();
367
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 67 times.
68 if (!json_parser_load_from_data (parser, content, len, error))
368 1 return NULL;
369
370 67 root = json_parser_get_root (parser);
371
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 66 times.
67 if (!JSON_NODE_HOLDS_OBJECT (root)) {
372 1 g_set_error (error,
373 msg_error_quark (),
374 MSG_ERROR_FAILED,
375 "Invalid response, didn't get JSON object");
376 1 return NULL;
377 }
378
379 66 root_object = json_node_get_object (root);
380 /* g_print (json_to_string (root, TRUE)); */
381
382
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 65 times.
66 if (json_object_has_member (root_object, "error")) {
383 1 JsonObject *error_object = json_object_get_object_member (root_object, "error");
384 1 const char *message = json_object_get_string_member (error_object, "message");
385
386 1 g_set_error_literal (error,
387 MSG_ERROR,
388 MSG_ERROR_FAILED,
389 message);
390 1 return NULL;
391 }
392
393
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 1 times.
65 if (object)
394 64 *object = root_object;
395
396 65 return g_steal_pointer (&parser);
397 }
398
399 /**
400 * msg_service_get_session:
401 * @self: a #MsgService
402 *
403 * Get related soup session
404 *
405 * Returns: (transfer none): a #SoupSession
406 */
407 SoupSession *
408 6 msg_service_get_session (MsgService *self)
409 {
410 6 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
411 6 return priv->session;
412 }
413
414 /**
415 * msg_service_get_authorizer:
416 * @self: a #MsgService
417 *
418 * Get related authorizer.
419 *
420 * Returns: (transfer none): a #MsgAuthorizer
421 */
422 MsgAuthorizer *
423 2 msg_service_get_authorizer (MsgService *self)
424 {
425 2 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
426 2 return priv->authorizer;
427 }
428
429 /**
430 * msg_service_get_next_link:
431 * @object: a #JsonObject
432 *
433 * Get next link
434 *
435 * Returns: (transfer full): next link or %NULL if not available
436 */
437 char *
438 15 msg_service_get_next_link (JsonObject *object)
439 {
440 30 return g_strdup (msg_json_object_get_string (object, "@odata.nextLink"));
441 }
442