GCC Code Coverage Report


Directory: src/
File: src/msg-service.c
Date: 2024-05-18 00:53:33
Exec Total Coverage
Lines: 120 128 93.8%
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 85 _uri = soup_uri_copy (_uri_parsed, SOUP_URI_PORT, msg_service_get_https_port (), SOUP_URI_NONE);
127
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 84 times.
85 if (g_strcmp0 (g_uri_get_scheme (_uri), "https") != 0)
128 1 return NULL;
129
130 84 message = msg_service_new_message_from_uri (self, method, _uri);
131
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 82 times.
84 if (etag)
132
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);
133
134 84 return message;
135 }
136
137 /**
138 * msg_service_send:
139 * @self: a msg service
140 * @message: a #SoupMessage
141 * @cancellable: a #GCancellable
142 * @error: a #Gerror
143 *
144 * Adds authorizer information to `message` and send it.
145 *
146 * Returns: (transfer full): a #GInputStream
147 */
148 GInputStream *
149 1 msg_service_send (MsgService *self,
150 SoupMessage *message,
151 GCancellable *cancellable,
152 GError **error)
153 {
154 1 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
155
156 1 msg_authorizer_process_request (priv->authorizer, message);
157
158 1 return soup_session_send (priv->session, message, cancellable, error);
159 }
160
161 /**
162 * msg_service_send_and_read:
163 * @self: a msg service
164 * @message: a #SoupMessage
165 * @cancellable: a #GCancellable
166 * @error: a #GError
167 *
168 * Adds authorizer information to `message` and send it.
169 *
170 * Returns: (transfer full): a #GBytes or %NULL on error.
171 */
172 GBytes *
173 14 msg_service_send_and_read (MsgService *self,
174 SoupMessage *message,
175 GCancellable *cancellable,
176 GError **error)
177 {
178 14 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
179
180 14 msg_authorizer_process_request (priv->authorizer, message);
181
182 14 return soup_session_send_and_read (priv->session, message, cancellable, error);
183 }
184
185 /**
186 * msg_service_send_and_parse_response:
187 * @self: a msg service
188 * @message: a #SoupMessage
189 * @cancellable: a #GCancellable
190 * @error: a #GError
191 *
192 * A combination of `msg_service_send_and_read` and `msg_service_parse_response`
193 *
194 * Returns: (transfer full): a #JsonParser or %NULL on error
195 */
196 JsonParser *
197 65 msg_service_send_and_parse_response (MsgService *self,
198 SoupMessage *message,
199 JsonObject **object,
200 GCancellable *cancellable,
201 GError **error)
202 {
203 65 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
204 65 g_autoptr (GBytes) response = NULL;
205
206 65 msg_authorizer_process_request (priv->authorizer, message);
207
208 65 response = soup_session_send_and_read (priv->session, message, cancellable, error);
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
65 if (!response)
210 return NULL;
211
212 65 return msg_service_parse_response (response, object, error);
213 }
214
215
216 static void
217 5 msg_service_set_authorizer (MsgService *self,
218 MsgAuthorizer *authorizer)
219 {
220 5 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
221
222
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 g_return_if_fail (MSG_IS_SERVICE (self));
223
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));
224
225
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 g_clear_object (&priv->authorizer);
226 5 priv->authorizer = authorizer;
227
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 if (priv->authorizer)
228 4 g_object_ref (priv->authorizer);
229
230 5 g_object_notify (G_OBJECT (self), "authorizer");
231 }
232
233 static void
234 5 msg_service_set_property (GObject *object,
235 guint property_id,
236 const GValue *value,
237 GParamSpec *pspec)
238 {
239 5 MsgService *self = MSG_SERVICE (object);
240
241
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 switch (property_id) {
242 5 case PROP_AUTHORIZER:
243 5 msg_service_set_authorizer (self, MSG_AUTHORIZER (g_value_get_object (value)));
244 5 break;
245 default:
246 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
247 break;
248 }
249 5 }
250
251 static void
252 msg_service_get_property (GObject *object,
253 guint property_id,
254 __attribute__ ((unused)) GValue *value,
255 GParamSpec *pspec)
256 {
257 switch (property_id) {
258 default:
259 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
260 break;
261 }
262 }
263
264 static void
265 2499 soup_log_printer (__attribute__ ((unused)) SoupLogger *logger,
266 __attribute__ ((unused)) SoupLoggerLogLevel level,
267 char direction,
268 const char *data,
269 __attribute__ ((unused)) gpointer user_data)
270 {
271 2499 g_debug ("%c %s", direction, data);
272 2499 }
273
274 SoupLoggerLogLevel
275 10 msg_service_get_log_level (void)
276 {
277 static int level = -1;
278
279
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 if (level < 0) {
280 5 const char *env = g_getenv ("MSG_DEBUG");
281
282
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (env != NULL) {
283 5 level = atoi (env);
284 }
285 }
286
287 10 return level;
288 }
289
290 static void
291 5 msg_service_init (MsgService *self)
292 {
293 5 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
294
295 /* priv->session = soup_session_new_with_options ("timeout", 0, NULL); */
296 5 priv->session = soup_session_new ();
297
298 /* Iff MSG_LAX_SSL_CERTIFICATES=1, relax SSL certificate validation to allow using invalid/unsigned certificates for testing. */
299
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 if (g_strcmp0 (g_getenv ("MSG_LAX_SSL_CERTIFICATES"), "1") == 0) {
300 5 g_object_set_data (G_OBJECT (priv->session), "msg-lax-ssl", (gpointer)TRUE);
301 }
302
303
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 if (msg_service_get_log_level () > SOUP_LOGGER_LOG_NONE) {
304 5 g_autoptr (SoupLogger) logger = NULL;
305
306 5 logger = soup_logger_new (msg_service_get_log_level ());
307 5 soup_logger_set_printer (logger, (SoupLoggerPrinter)soup_log_printer, NULL, NULL);
308 5 soup_session_add_feature (priv->session, SOUP_SESSION_FEATURE (logger));
309 }
310 5 }
311
312 static void
313 6 msg_service_class_init (MsgServiceClass *class)
314 {
315 6 GObjectClass *object_class = G_OBJECT_CLASS (class);
316
317 6 object_class->set_property = msg_service_set_property;
318 6 object_class->get_property = msg_service_get_property;
319
320 6 properties [PROP_AUTHORIZER] = g_param_spec_object ("authorizer",
321 "Authorizer",
322 "The authorizer for this service",
323 MSG_TYPE_AUTHORIZER,
324 G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
325
326 6 g_object_class_install_properties (object_class, PROP_COUNT, properties);
327 6 }
328
329 /**
330 * msg_service_parse_response:
331 * @bytes: input bytes containing response buffer
332 * @object: a pointer to the returning root object
333 * @error: a #GError
334 *
335 * Parse response data and check for errors. In case
336 * no errors are found, return json root object.
337 *
338 * Returns: (transfer full): a #JsonParser
339 */
340 JsonParser *
341 68 msg_service_parse_response (GBytes *bytes,
342 JsonObject **object,
343 GError **error)
344 {
345 68 JsonParser *parser = NULL;
346 68 JsonObject *root_object = NULL;
347 68 JsonNode *root = NULL;
348 const char *content;
349 gsize len;
350
351 68 content = g_bytes_get_data (bytes, &len);
352
353 68 parser = json_parser_new ();
354
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 67 times.
68 if (!json_parser_load_from_data (parser, content, len, error))
355 1 return NULL;
356
357 67 root = json_parser_get_root (parser);
358
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 66 times.
67 if (!JSON_NODE_HOLDS_OBJECT (root)) {
359 1 g_set_error (error,
360 msg_error_quark (),
361 MSG_ERROR_FAILED,
362 "Invalid response, didn't get JSON object");
363 1 return NULL;
364 }
365
366 66 root_object = json_node_get_object (root);
367 /* g_print (json_to_string (root, TRUE)); */
368
369
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 65 times.
66 if (json_object_has_member (root_object, "error")) {
370 1 JsonObject *error_object = json_object_get_object_member (root_object, "error");
371 1 const char *message = json_object_get_string_member (error_object, "message");
372
373 1 g_set_error_literal (error,
374 MSG_ERROR,
375 MSG_ERROR_FAILED,
376 message);
377 1 return NULL;
378 }
379
380
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 1 times.
65 if (object)
381 64 *object = root_object;
382
383 65 return parser;
384 }
385
386 /**
387 * msg_service_get_session:
388 * @self: a #MsgService
389 *
390 * Get related soup session
391 *
392 * Returns: (transfer none): a #SoupSession
393 */
394 SoupSession *
395 6 msg_service_get_session (MsgService *self)
396 {
397 6 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
398 6 return priv->session;
399 }
400
401 /**
402 * msg_service_get_authorizer:
403 * @self: a #MsgService
404 *
405 * Get related authorizer.
406 *
407 * Returns: (transfer none): a #MsgAuthorizer
408 */
409 MsgAuthorizer *
410 2 msg_service_get_authorizer (MsgService *self)
411 {
412 2 MsgServicePrivate *priv = MSG_SERVICE_GET_PRIVATE (self);
413 2 return priv->authorizer;
414 }
415
416 /**
417 * msg_service_get_next_link:
418 * @object: a #JsonObject
419 *
420 * Get next link
421 *
422 * Returns: (transfer full): next link or %NULL if not available
423 */
424 char *
425 15 msg_service_get_next_link (JsonObject *object)
426 {
427 30 return g_strdup (msg_json_object_get_string (object, "@odata.nextLink"));
428 }
429