1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2012 Red Hat, Inc.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "config.h"
19
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include "spice-client.h"
24 #include "spice-uri.h"
25 #include "spice-uri-priv.h"
26
27 /**
28 * SECTION:spice-uri
29 * @short_description: URIs handling
30 * @title: SpiceURI
31 * @section_id:
32 * @stability: Stable
33 * @include: spice-client.h
34 *
35 * A SpiceURI represents a (parsed) URI.
36 * Since: 0.24
37 */
38
39 struct _SpiceURI {
40 GObject parent_instance;
41 gchar *scheme;
42 gchar *hostname;
43 guint port;
44 gchar *user;
45 gchar *password;
46 };
47
48 struct _SpiceURIClass {
49 GObjectClass parent_class;
50 };
51
52 G_DEFINE_TYPE(SpiceURI, spice_uri, G_TYPE_OBJECT)
53
54 enum {
55 SPICE_URI_DUMMY_PROPERTY,
56 SPICE_URI_SCHEME,
57 SPICE_URI_USER,
58 SPICE_URI_PASSWORD,
59 SPICE_URI_HOSTNAME,
60 SPICE_URI_PORT
61 };
62
63 #ifndef HAVE_STRTOK_R
strtok_r(char * s,const char * delim,char ** save_ptr)64 static char *strtok_r(char *s, const char *delim, char **save_ptr)
65 {
66 char *token;
67
68 if (s == NULL)
69 s = *save_ptr;
70
71 /* Scan leading delimiters. */
72 s += strspn (s, delim);
73 if (*s == '\0')
74 return NULL;
75
76 /* Find the end of the token. */
77 token = s;
78 s = strpbrk (token, delim);
79 if (s == NULL)
80 /* This token finishes the string. */
81 *save_ptr = strchr (token, '\0');
82 else
83 {
84 /* Terminate the token and make *SAVE_PTR point past it. */
85 *s = '\0';
86 *save_ptr = s + 1;
87 }
88 return token;
89 }
90 #endif
91
92 G_GNUC_INTERNAL
spice_uri_new(void)93 SpiceURI* spice_uri_new(void)
94 {
95 SpiceURI * self = NULL;
96 self = (SpiceURI*)g_object_new(SPICE_TYPE_URI, NULL);
97 return self;
98 }
99
spice_uri_reset(SpiceURI * self)100 static void spice_uri_reset(SpiceURI *self)
101 {
102 g_clear_pointer(&self->scheme, g_free);
103 g_clear_pointer(&self->hostname, g_free);
104 g_clear_pointer(&self->user, g_free);
105 g_clear_pointer(&self->password, g_free);
106 self->port = 0;
107 }
108
109 G_GNUC_INTERNAL
spice_uri_parse(SpiceURI * self,const gchar * _uri,GError ** error)110 gboolean spice_uri_parse(SpiceURI *self, const gchar *_uri, GError **error)
111 {
112 gchar *dup, *uri, **uriv = NULL;
113 const gchar *uri_port = NULL;
114 char *uri_scheme = NULL;
115 gboolean success = FALSE;
116 size_t len;
117
118 g_return_val_if_fail(self != NULL, FALSE);
119
120 spice_uri_reset(self);
121
122 g_return_val_if_fail(_uri != NULL, FALSE);
123
124 uri = dup = g_strdup(_uri);
125 /* FIXME: use GUri when it is ready... only support http atm */
126 /* the code is voluntarily not parsing thoroughly the uri */
127 uri_scheme = g_uri_parse_scheme(uri);
128 if (uri_scheme == NULL) {
129 spice_uri_set_scheme(self, "http");
130 } else {
131 spice_uri_set_scheme(self, uri_scheme);
132 uri += strlen(uri_scheme) + 3; /* scheme + "://" */
133 }
134 if (g_ascii_strcasecmp(spice_uri_get_scheme(self), "http") == 0) {
135 spice_uri_set_port(self, 3128);
136 } else if (g_ascii_strcasecmp(spice_uri_get_scheme(self), "https") == 0) {
137 spice_uri_set_port(self, 3129);
138 } else {
139 g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
140 "Invalid uri scheme for proxy: %s", spice_uri_get_scheme(self));
141 goto end;
142 }
143 /* remove trailing slash */
144 len = strlen(uri);
145 for (; len > 0; len--)
146 if (uri[len-1] == '/')
147 uri[len-1] = '\0';
148 else
149 break;
150
151
152 /* yes, that parser is bad, we need GUri... */
153 if (strstr(uri, "@")) {
154 gchar *saveptr = NULL, *saveptr2 = NULL;
155 gchar *next = strstr(uri, "@") + 1;
156 gchar *auth = strtok_r(uri, "@", &saveptr);
157 const gchar *user = strtok_r(auth, ":", &saveptr2);
158 const gchar *pass = strtok_r(NULL, ":", &saveptr2);
159 spice_uri_set_user(self, user);
160 spice_uri_set_password(self, pass);
161 uri = next;
162 }
163
164 if (*uri == '[') { /* ipv6 address */
165 uriv = g_strsplit(uri + 1, "]", 2);
166 if (uriv[1] == NULL) {
167 g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
168 "Missing ']' in ipv6 uri");
169 goto end;
170 }
171 if (*uriv[1] == ':') {
172 uri_port = uriv[1] + 1;
173 } else if (strlen(uriv[1]) > 0) { /* invalid string after the hostname */
174 g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
175 "Invalid uri address");
176 goto end;
177 }
178 } else {
179 /* max 2 parts, host:port */
180 uriv = g_strsplit(uri, ":", 2);
181 if (uriv[0] != NULL)
182 uri_port = uriv[1];
183 }
184
185 if (uriv[0] == NULL || strlen(uriv[0]) == 0) {
186 g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
187 "Invalid hostname in uri address");
188 goto end;
189 }
190
191 spice_uri_set_hostname(self, uriv[0]);
192
193 if (uri_port != NULL) {
194 gchar *endptr;
195 gint64 port = g_ascii_strtoll(uri_port, &endptr, 10);
196 if (*endptr != '\0') {
197 g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
198 "Invalid uri port: %s", uri_port);
199 goto end;
200 } else if (endptr == uri_port) {
201 g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, "Missing uri port");
202 goto end;
203 }
204 if (port <= 0 || port > 65535) {
205 g_set_error(error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED, "Port out of range");
206 goto end;
207 }
208 spice_uri_set_port(self, port);
209 }
210
211 success = TRUE;
212
213 end:
214 g_free(uri_scheme);
215 g_free(dup);
216 g_strfreev(uriv);
217 return success;
218 }
219
220 /**
221 * spice_uri_get_scheme:
222 * @uri: a #SpiceURI
223 *
224 * Gets @uri's scheme.
225 *
226 * Returns: @uri's scheme.
227 * Since: 0.24
228 **/
spice_uri_get_scheme(SpiceURI * self)229 const gchar* spice_uri_get_scheme(SpiceURI *self)
230 {
231 g_return_val_if_fail(SPICE_IS_URI(self), NULL);
232 return self->scheme;
233 }
234
235 /**
236 * spice_uri_set_scheme:
237 * @uri: a #SpiceURI
238 * @scheme: the scheme
239 *
240 * Sets @uri's scheme to @scheme.
241 * Since: 0.24
242 **/
spice_uri_set_scheme(SpiceURI * self,const gchar * scheme)243 void spice_uri_set_scheme(SpiceURI *self, const gchar *scheme)
244 {
245 g_return_if_fail(SPICE_IS_URI(self));
246
247 g_free(self->scheme);
248 self->scheme = g_strdup(scheme);
249 g_object_notify((GObject *)self, "scheme");
250 }
251
252 /**
253 * spice_uri_get_hostname:
254 * @uri: a #SpiceURI
255 *
256 * Gets @uri's hostname.
257 *
258 * Returns: @uri's hostname.
259 * Since: 0.24
260 **/
spice_uri_get_hostname(SpiceURI * self)261 const gchar* spice_uri_get_hostname(SpiceURI *self)
262 {
263 g_return_val_if_fail(SPICE_IS_URI(self), NULL);
264 return self->hostname;
265 }
266
267
268 /**
269 * spice_uri_set_hostname:
270 * @uri: a #SpiceURI
271 * @hostname: the hostname
272 *
273 * Sets @uri's hostname to @hostname.
274 * Since: 0.24
275 **/
spice_uri_set_hostname(SpiceURI * self,const gchar * hostname)276 void spice_uri_set_hostname(SpiceURI *self, const gchar *hostname)
277 {
278 g_return_if_fail(SPICE_IS_URI(self));
279
280 g_free(self->hostname);
281 self->hostname = g_strdup(hostname);
282 g_object_notify((GObject *)self, "hostname");
283 }
284
285 /**
286 * spice_uri_get_port:
287 * @uri: a #SpiceURI
288 *
289 * Gets @uri's port.
290 *
291 * Returns: @uri's port.
292 * Since: 0.24
293 **/
spice_uri_get_port(SpiceURI * self)294 guint spice_uri_get_port(SpiceURI *self)
295 {
296 g_return_val_if_fail(SPICE_IS_URI(self), 0);
297 return self->port;
298 }
299
300 /**
301 * spice_uri_set_port:
302 * @uri: a #SpiceURI
303 * @port: the port
304 *
305 * Sets @uri's port to @port.
306 * Since: 0.24
307 **/
spice_uri_set_port(SpiceURI * self,guint port)308 void spice_uri_set_port(SpiceURI *self, guint port)
309 {
310 g_return_if_fail(SPICE_IS_URI(self));
311 self->port = port;
312 g_object_notify((GObject *)self, "port");
313 }
314
spice_uri_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)315 static void spice_uri_get_property(GObject *object, guint property_id,
316 GValue *value, GParamSpec *pspec)
317 {
318 SpiceURI *self;
319 self = G_TYPE_CHECK_INSTANCE_CAST(object, SPICE_TYPE_URI, SpiceURI);
320
321 switch (property_id) {
322 case SPICE_URI_SCHEME:
323 g_value_set_string(value, spice_uri_get_scheme(self));
324 break;
325 case SPICE_URI_HOSTNAME:
326 g_value_set_string(value, spice_uri_get_hostname(self));
327 break;
328 case SPICE_URI_PORT:
329 g_value_set_uint(value, spice_uri_get_port(self));
330 break;
331 case SPICE_URI_USER:
332 g_value_set_string(value, spice_uri_get_user(self));
333 break;
334 case SPICE_URI_PASSWORD:
335 g_value_set_string(value, spice_uri_get_password(self));
336 break;
337 default:
338 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
339 break;
340 }
341 }
342
343
spice_uri_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)344 static void spice_uri_set_property(GObject *object, guint property_id,
345 const GValue *value, GParamSpec *pspec)
346 {
347 SpiceURI * self;
348 self = G_TYPE_CHECK_INSTANCE_CAST(object, SPICE_TYPE_URI, SpiceURI);
349
350 switch (property_id) {
351 case SPICE_URI_SCHEME:
352 spice_uri_set_scheme(self, g_value_get_string(value));
353 break;
354 case SPICE_URI_HOSTNAME:
355 spice_uri_set_hostname(self, g_value_get_string(value));
356 break;
357 case SPICE_URI_USER:
358 spice_uri_set_user(self, g_value_get_string(value));
359 break;
360 case SPICE_URI_PASSWORD:
361 spice_uri_set_password(self, g_value_get_string(value));
362 break;
363 case SPICE_URI_PORT:
364 spice_uri_set_port(self, g_value_get_uint(value));
365 break;
366 default:
367 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
368 break;
369 }
370 }
371
spice_uri_finalize(GObject * obj)372 static void spice_uri_finalize(GObject* obj)
373 {
374 SpiceURI *self;
375
376 self = G_TYPE_CHECK_INSTANCE_CAST(obj, SPICE_TYPE_URI, SpiceURI);
377 spice_uri_reset(self);
378
379 G_OBJECT_CLASS (spice_uri_parent_class)->finalize (obj);
380 }
381
spice_uri_init(SpiceURI * self G_GNUC_UNUSED)382 static void spice_uri_init (SpiceURI *self G_GNUC_UNUSED)
383 {
384 }
385
386
spice_uri_class_init(SpiceURIClass * klass)387 static void spice_uri_class_init(SpiceURIClass *klass)
388 {
389 spice_uri_parent_class = g_type_class_peek_parent (klass);
390
391 G_OBJECT_CLASS (klass)->get_property = spice_uri_get_property;
392 G_OBJECT_CLASS (klass)->set_property = spice_uri_set_property;
393 G_OBJECT_CLASS (klass)->finalize = spice_uri_finalize;
394
395 g_object_class_install_property(G_OBJECT_CLASS (klass),
396 SPICE_URI_SCHEME,
397 g_param_spec_string ("scheme",
398 "scheme",
399 "scheme",
400 NULL,
401 G_PARAM_STATIC_STRINGS |
402 G_PARAM_READWRITE));
403
404 g_object_class_install_property(G_OBJECT_CLASS (klass),
405 SPICE_URI_HOSTNAME,
406 g_param_spec_string ("hostname",
407 "hostname",
408 "hostname",
409 NULL,
410 G_PARAM_STATIC_STRINGS |
411 G_PARAM_READWRITE));
412
413 g_object_class_install_property(G_OBJECT_CLASS (klass),
414 SPICE_URI_PORT,
415 g_param_spec_uint ("port",
416 "port",
417 "port",
418 0, G_MAXUINT, 0,
419 G_PARAM_STATIC_STRINGS |
420 G_PARAM_READWRITE));
421
422 g_object_class_install_property(G_OBJECT_CLASS (klass),
423 SPICE_URI_USER,
424 g_param_spec_string ("user",
425 "user",
426 "user",
427 NULL,
428 G_PARAM_STATIC_STRINGS |
429 G_PARAM_READWRITE));
430
431 g_object_class_install_property(G_OBJECT_CLASS (klass),
432 SPICE_URI_PASSWORD,
433 g_param_spec_string ("password",
434 "password",
435 "password",
436 NULL,
437 G_PARAM_STATIC_STRINGS |
438 G_PARAM_READWRITE));
439 }
440
441 /**
442 * spice_uri_to_string:
443 * @uri: a #SpiceURI
444 *
445 * Returns a string representing @uri.
446 *
447 * Returns: a string representing @uri, which the caller must free.
448 * Since: 0.24
449 **/
spice_uri_to_string(SpiceURI * self)450 gchar* spice_uri_to_string(SpiceURI* self)
451 {
452 g_return_val_if_fail(SPICE_IS_URI(self), NULL);
453
454 if (self->scheme == NULL || self->hostname == NULL)
455 return NULL;
456
457 if (self->user || self->password)
458 return g_strdup_printf("%s://%s:%s@%s:%u",
459 self->scheme,
460 self->user, self->password,
461 self->hostname, self->port);
462 else
463 return g_strdup_printf("%s://%s:%u",
464 self->scheme, self->hostname, self->port);
465 }
466
467 /**
468 * spice_uri_get_user:
469 * @uri: a #SpiceURI
470 *
471 * Gets @uri's user.
472 *
473 * Returns: @uri's user.
474 * Since: 0.24
475 **/
spice_uri_get_user(SpiceURI * self)476 const gchar* spice_uri_get_user(SpiceURI *self)
477 {
478 g_return_val_if_fail(SPICE_IS_URI(self), NULL);
479 return self->user;
480 }
481
482 /**
483 * spice_uri_set_user:
484 * @uri: a #SpiceURI
485 * @user: the user, or %NULL.
486 *
487 * Sets @uri's user to @user.
488 * Since: 0.24
489 **/
spice_uri_set_user(SpiceURI * self,const gchar * user)490 void spice_uri_set_user(SpiceURI *self, const gchar *user)
491 {
492 g_return_if_fail(SPICE_IS_URI(self));
493
494 g_free(self->user);
495 self->user = g_strdup(user);
496 g_object_notify((GObject *)self, "user");
497 }
498
499 /**
500 * spice_uri_get_password:
501 * @uri: a #SpiceURI
502 *
503 * Gets @uri's password.
504 *
505 * Returns: @uri's password.
506 * Since: 0.24
507 **/
spice_uri_get_password(SpiceURI * self)508 const gchar* spice_uri_get_password(SpiceURI *self)
509 {
510 g_return_val_if_fail(SPICE_IS_URI(self), NULL);
511 return self->password;
512 }
513
514 /**
515 * spice_uri_set_password:
516 * @uri: a #SpiceURI
517 * @password: the password, or %NULL.
518 *
519 * Sets @uri's password to @password.
520 * Since: 0.24
521 **/
spice_uri_set_password(SpiceURI * self,const gchar * password)522 void spice_uri_set_password(SpiceURI *self, const gchar *password)
523 {
524 g_return_if_fail(SPICE_IS_URI(self));
525
526 g_free(self->password);
527 self->password = g_strdup(password);
528 g_object_notify((GObject *)self, "password");
529 }
530