lib/url: Include port in pb_url_to_string()
[petitboot] / lib / url / url.c
1 /*
2  *  Copyright (C) 2009 Sony Computer Entertainment Inc.
3  *  Copyright 2009 Sony Corp.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; version 2 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #if defined(HAVE_CONFIG_H)
20 #include "config.h"
21 #endif
22
23 #include <assert.h>
24 #include <string.h>
25
26 #include "log/log.h"
27 #include "talloc/talloc.h"
28 #include "url.h"
29
30 /**
31  * pb_scheme_info - Helper for parsing URLs.
32  */
33
34 struct pb_scheme_info {
35         enum pb_url_scheme scheme;
36         const char *str;
37         unsigned int str_len;
38         bool has_host;
39 };
40
41 static const struct pb_scheme_info schemes[] = {
42         {
43                 .scheme = pb_url_file,
44                 .str = "file",
45                 .str_len = sizeof("file") - 1,
46                 .has_host = false,
47         },
48         {
49                 .scheme = pb_url_ftp,
50                 .str = "ftp",
51                 .str_len = sizeof("ftp") - 1,
52                 .has_host = true,
53         },
54         {
55                 .scheme = pb_url_http,
56                 .str = "http",
57                 .str_len = sizeof("http") - 1,
58                 .has_host = true,
59         },
60         {
61                 .scheme = pb_url_https,
62                 .str = "https",
63                 .str_len = sizeof("https") - 1,
64                 .has_host = true,
65         },
66         {
67                 .scheme = pb_url_nfs,
68                 .str = "nfs",
69                 .str_len = sizeof("nfs") - 1,
70                 .has_host = true,
71         },
72         {
73                 .scheme = pb_url_sftp,
74                 .str = "sftp",
75                 .str_len = sizeof("sftp") - 1,
76                 .has_host = true,
77         },
78         {
79                 .scheme = pb_url_tftp,
80                 .str = "tftp",
81                 .str_len = sizeof("tftp") - 1,
82                 .has_host = true,
83         },
84 };
85
86 static const struct pb_scheme_info *file_scheme = &schemes[0];
87
88 /**
89  * pb_url_find_scheme - Find the pb_scheme_info for a URL string.
90  */
91
92 static const struct pb_scheme_info *pb_url_scheme_info(
93                 enum pb_url_scheme scheme)
94 {
95         unsigned int i;
96
97         for (i = 0; i < sizeof(schemes) / sizeof(schemes[0]); i++) {
98                 const struct pb_scheme_info *info = &schemes[i];
99
100                 if (info->scheme == scheme)
101                         return info;
102
103         }
104         return NULL;
105 }
106
107 static const struct pb_scheme_info *pb_url_find_scheme(const char *url)
108 {
109         static const int sep_len = sizeof("://") - 1;
110         static const char *sep = "://";
111         unsigned int i, url_len;
112
113         url_len = strlen(url);
114
115         for (i = 0; i < sizeof(schemes) / sizeof(schemes[0]); i++) {
116                 const struct pb_scheme_info *scheme = &schemes[i];
117
118                 if (url_len < scheme->str_len + sep_len)
119                         continue;
120
121                 if (strncmp(url + scheme->str_len, sep, sep_len))
122                         continue;
123
124                 if (strncasecmp(url, scheme->str, scheme->str_len))
125                         continue;
126
127                 return scheme;
128         }
129
130         return NULL;
131 }
132
133 static void pb_url_parse_path(struct pb_url *url)
134 {
135         const char *p = strrchr(url->path, '/');
136
137         talloc_free(url->dir);
138         talloc_free(url->file);
139
140         if (p) {
141                 p++;
142                 url->dir = talloc_strndup(url, url->path, p - url->path);
143                 url->file = talloc_strdup(url, p);
144         } else {
145                 url->dir = NULL;
146                 url->file = talloc_strdup(url, url->path);
147         }
148 }
149
150 /**
151  * pb_url_parse - Parse a remote file URL.
152  * @ctx: The talloc context to associate with the returned string.
153  *
154  * Returns a talloc'ed struct pb_url instance on success, or NULL on error.
155  */
156
157 struct pb_url *pb_url_parse(void *ctx, const char *url_str)
158 {
159         const struct pb_scheme_info *si;
160         struct pb_url *url;
161         const char *p;
162
163         if (!url_str || !*url_str) {
164                 assert(0 && "bad url");
165                 return NULL;
166         }
167
168         url = talloc_zero(ctx, struct pb_url);
169
170         if (!url)
171                 return NULL;
172
173         si = pb_url_find_scheme(url_str);
174         if (si) {
175                 url->scheme = si->scheme;
176                 p = url_str + si->str_len + strlen("://");
177         } else {
178                 url->scheme = file_scheme->scheme;
179                 p = url_str;
180         }
181
182         url->full = talloc_strdup(url, url_str);
183
184         if (url->scheme == pb_url_file) {
185                 url->port = NULL;
186                 url->host = NULL;
187                 url->path = talloc_strdup(url, p);
188         } else {
189                 int len;
190                 const char *col;
191                 const char *path;
192
193                 path = strchr(p, '/');
194
195                 if (!path) {
196                         pb_log_fn("parse path failed '%s'\n" , p);
197                         goto fail;
198                 }
199
200                 col = strrchr(p, ':');
201                 if (col <= p)
202                         col = NULL;
203                 if (col && strchr(col , ']')) {
204                         /*
205                          * This is likely an IPv6 address with no port.
206                          * See https://www.ietf.org/rfc/rfc2732.txt
207                          */
208                         col = NULL;
209                 }
210
211                 if (col) {
212                         len = path - col - 1;
213                         url->port = len ? talloc_strndup(url, col + 1, len)
214                                 : NULL;
215                         len = col - p;
216                 } else {
217                         url->port = NULL;
218                         len = path - p;
219                 }
220
221                 if (p[0] == '[' && p[len - 1] == ']')
222                         /* IPv6 */
223                         url->host = talloc_strndup(url, p + 1, len - 2);
224                 else
225                         /* IPv4 */
226                         url->host = talloc_strndup(url, p, len);
227
228
229                 /* remove multiple leading slashes */
230                 for (; *path && *(path+1) == '/'; path++)
231                         ;
232
233                 url->path = talloc_strdup(url, path);
234         }
235
236         pb_url_parse_path(url);
237
238         return url;
239
240 fail:
241         talloc_free(url);
242         return NULL;
243 }
244
245 bool is_url(const char *str)
246 {
247         return strstr(str, "://") != NULL;
248 }
249
250 int addr_scheme(const char *address)
251 {
252         uint8_t buf[sizeof(struct in6_addr)];
253         int rc;
254
255         rc = inet_pton(AF_INET, address , buf);
256         if (rc == 1)
257                 return AF_INET;
258         rc = inet_pton(AF_INET6, address , buf);
259         if (rc == 1)
260                 return AF_INET6;
261
262         return 0;
263 }
264
265 char *pb_url_to_string(struct pb_url *url)
266 {
267         const struct pb_scheme_info *scheme = pb_url_scheme_info(url->scheme);
268         char *str, *port;
269         assert(scheme);
270
271         port = url->port ? talloc_asprintf(url, ":%s", url->port) : NULL;
272
273         if (scheme->has_host && addr_scheme(url->host) == AF_INET6)
274                 str = talloc_asprintf(url, "%s://[%s]%s%s", scheme->str,
275                                 url->host, port ?: "", url->path);
276         else
277                 str = talloc_asprintf(url, "%s://%s%s%s", scheme->str,
278                                 scheme->has_host ? url->host : "",
279                                 port ?: "", url->path);
280
281         talloc_free(port);
282         return str;
283 }
284
285 static void pb_url_update_full(struct pb_url *url)
286 {
287         talloc_free(url->full);
288         url->full = pb_url_to_string(url);
289 }
290
291 struct pb_url *pb_url_copy(void *ctx, const struct pb_url *url)
292 {
293         struct pb_url *new_url;
294
295         new_url = talloc(ctx, struct pb_url);
296         new_url->scheme = url->scheme;
297         new_url->full = talloc_strdup(new_url, url->full);
298
299         new_url->host = url->host ? talloc_strdup(new_url, url->host) : NULL;
300         new_url->port = url->port ? talloc_strdup(new_url, url->port) : NULL;
301         new_url->path = url->path ? talloc_strdup(new_url, url->path) : NULL;
302         new_url->dir  = url->dir  ? talloc_strdup(new_url, url->dir)  : NULL;
303         new_url->file = url->file ? talloc_strdup(new_url, url->file) : NULL;
304
305         return new_url;
306 }
307
308 struct pb_url *pb_url_join(void *ctx, const struct pb_url *url, const char *s)
309 {
310         struct pb_url *new_url;
311
312         /* complete url: just parse all info from s */
313         if (is_url(s))
314                 return pb_url_parse(ctx, s);
315
316         new_url = pb_url_copy(ctx, url);
317
318         if (s[0] == '/') {
319                 /* absolute path: replace path of new_url */
320                 talloc_free(new_url->path);
321                 new_url->path = talloc_strdup(new_url, s);
322
323         } else {
324                 /* relative path: join s to existing path. We know that
325                  * url->dir ends with a slash. */
326                 char *tmp = new_url->path;
327                 new_url->path = talloc_asprintf(new_url, "%s%s", url->dir, s);
328                 talloc_free(tmp);
329         }
330
331         /* replace ->dir and ->file with components from ->path */
332         pb_url_parse_path(new_url);
333
334         /* and re-generate the full URL */
335         pb_url_update_full(new_url);
336
337         return new_url;
338 }
339
340 const char *pb_url_scheme_name(enum pb_url_scheme scheme)
341 {
342         const struct pb_scheme_info *info = pb_url_scheme_info(scheme);
343         return info ? info->str : NULL;
344 }