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