lib/url: fix no-scheme URL parsing
[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 #define _GNU_SOURCE
24 #include <assert.h>
25 #include <stdbool.h>
26 #include <string.h>
27
28 #include "log/log.h"
29 #include "talloc/talloc.h"
30 #include "url.h"
31
32 /**
33  * pb_scheme_info - Helper for parsing URLs.
34  */
35
36 struct pb_scheme_info {
37         enum pb_url_scheme scheme;
38         const char *str;
39         unsigned int str_len;
40         bool has_host;
41 };
42
43 static const struct pb_scheme_info schemes[] = {
44         {
45                 .scheme = pb_url_file,
46                 .str = "file",
47                 .str_len = sizeof("file") - 1,
48                 .has_host = false,
49         },
50         {
51                 .scheme = pb_url_ftp,
52                 .str = "ftp",
53                 .str_len = sizeof("ftp") - 1,
54                 .has_host = true,
55         },
56         {
57                 .scheme = pb_url_http,
58                 .str = "http",
59                 .str_len = sizeof("http") - 1,
60                 .has_host = true,
61         },
62         {
63                 .scheme = pb_url_https,
64                 .str = "https",
65                 .str_len = sizeof("https") - 1,
66                 .has_host = true,
67         },
68         {
69                 .scheme = pb_url_nfs,
70                 .str = "nfs",
71                 .str_len = sizeof("nfs") - 1,
72                 .has_host = true,
73         },
74         {
75                 .scheme = pb_url_sftp,
76                 .str = "sftp",
77                 .str_len = sizeof("sftp") - 1,
78                 .has_host = true,
79         },
80         {
81                 .scheme = pb_url_tftp,
82                 .str = "tftp",
83                 .str_len = sizeof("tftp") - 1,
84                 .has_host = true,
85         },
86 };
87
88 static const struct pb_scheme_info *file_scheme = &schemes[0];
89
90 /**
91  * pb_url_find_scheme - Find the pb_scheme_info for a URL string.
92  */
93
94 static const struct pb_scheme_info *pb_url_scheme_info(
95                 enum pb_url_scheme scheme)
96 {
97         unsigned int i;
98
99         for (i = 0; i < sizeof(schemes) / sizeof(schemes[0]); i++) {
100                 const struct pb_scheme_info *info = &schemes[i];
101
102                 if (info->scheme == scheme)
103                         return info;
104
105         }
106         return NULL;
107 }
108
109 static const struct pb_scheme_info *pb_url_find_scheme(const char *url)
110 {
111         static const int sep_len = sizeof("://") - 1;
112         static const char *sep = "://";
113         unsigned int i, url_len;
114
115         url_len = strlen(url);
116
117         for (i = 0; i < sizeof(schemes) / sizeof(schemes[0]); i++) {
118                 const struct pb_scheme_info *scheme = &schemes[i];
119
120                 if (url_len < scheme->str_len + sep_len)
121                         continue;
122
123                 if (strncmp(url + scheme->str_len, sep, sep_len))
124                         continue;
125
126                 if (strncasecmp(url, scheme->str, scheme->str_len))
127                         continue;
128
129                 return scheme;
130         }
131
132         return NULL;
133 }
134
135 static void pb_url_parse_path(struct pb_url *url)
136 {
137         const char *p = strrchr(url->path, '/');
138
139         talloc_free(url->dir);
140         talloc_free(url->file);
141
142         if (p) {
143                 p++;
144                 url->dir = talloc_strndup(url, url->path, p - url->path);
145                 url->file = talloc_strdup(url, p);
146         } else {
147                 url->dir = NULL;
148                 url->file = talloc_strdup(url, url->path);
149         }
150 }
151
152 /**
153  * pb_url_parse - Parse a remote file URL.
154  * @ctx: The talloc context to associate with the returned string.
155  *
156  * Returns a talloc'ed struct pb_url instance on success, or NULL on error.
157  */
158
159 struct pb_url *pb_url_parse(void *ctx, const char *url_str)
160 {
161         const struct pb_scheme_info *si;
162         struct pb_url *url;
163         const char *p;
164
165         pb_log("%s: '%s'\n", __func__, url_str);
166
167         if (!url_str || !*url_str) {
168                 assert(0 && "bad url");
169                 return NULL;
170         }
171
172         url = talloc_zero(ctx, struct pb_url);
173
174         if (!url)
175                 return NULL;
176
177         si = pb_url_find_scheme(url_str);
178         if (si) {
179                 url->scheme = si->scheme;
180                 p = url_str + si->str_len + strlen("://");
181         } else {
182                 url->scheme = file_scheme->scheme;
183                 p = url_str;
184         }
185
186         url->full = talloc_strdup(url, url_str);
187
188         if (url->scheme == pb_url_file) {
189                 url->port = NULL;
190                 url->host = NULL;
191                 url->path = talloc_strdup(url, p);
192         } else {
193                 int len;
194                 const char *col;
195                 const char *path;
196
197                 path = strchr(p, '/');
198
199                 if (!path) {
200                         pb_log("%s: parse path failed '%s'\n", __func__ , p);
201                         goto fail;
202                 }
203
204                 col = strchr(p, ':');
205
206                 if (col) {
207                         len = path - col - 1;
208                         url->port = len ? talloc_strndup(url, col + 1, len)
209                                 : NULL;
210                         len = col - p;
211                         url->host = len ? talloc_strndup(url, p, len) : NULL;
212                 } else {
213                         url->port = NULL;
214                         url->host = talloc_strndup(url, p, path - p);
215                 }
216
217                 /* remove multiple leading slashes */
218                 for (; *path && *(path+1) == '/'; path++)
219                         ;
220
221                 url->path = talloc_strdup(url, path);
222         }
223
224         pb_url_parse_path(url);
225
226         pb_log(" scheme %d\n", url->scheme);
227         pb_log(" host '%s'\n", url->host);
228         pb_log(" port '%s'\n", url->port);
229         pb_log(" path '%s'\n", url->path);
230         pb_log(" dir '%s'\n", url->dir);
231         pb_log(" file '%s'\n", url->file);
232
233         return url;
234
235 fail:
236         talloc_free(url);
237         return NULL;
238 }
239
240 static bool is_url(const char *str)
241 {
242         return strstr(str, "://") != NULL;
243 }
244
245 static void pb_url_update_full(struct pb_url *url)
246 {
247         const struct pb_scheme_info *scheme = pb_url_scheme_info(url->scheme);
248
249         assert(scheme);
250
251         talloc_free(url->full);
252
253         url->full = talloc_asprintf(url, "%s://%s%s", scheme->str,
254                         scheme->has_host ? url->host : "", url->path);
255 }
256
257 static struct pb_url *pb_url_copy(void *ctx, const struct pb_url *url)
258 {
259         struct pb_url *new_url;
260
261         new_url = talloc(ctx, struct pb_url);
262         new_url->scheme = url->scheme;
263         new_url->full = talloc_strdup(url, url->full);
264
265         new_url->host = url->host ? talloc_strdup(url, url->host) : NULL;
266         new_url->port = url->port ? talloc_strdup(url, url->port) : NULL;
267         new_url->path = url->path ? talloc_strdup(url, url->path) : NULL;
268         new_url->dir  = url->dir  ? talloc_strdup(url, url->dir)  : NULL;
269         new_url->file = url->file ? talloc_strdup(url, url->file) : NULL;
270
271         return new_url;
272 }
273
274 struct pb_url *pb_url_join(void *ctx, const struct pb_url *url, const char *s)
275 {
276         struct pb_url *new_url;
277
278         /* complete url: just parse all info from s */
279         if (is_url(s))
280                 return pb_url_parse(ctx, s);
281
282         new_url = pb_url_copy(ctx, url);
283
284         if (s[0] == '/') {
285                 /* absolute path: replace path of new_url */
286                 talloc_free(new_url->path);
287                 new_url->path = talloc_strdup(new_url, s);
288
289         } else {
290                 /* relative path: join s to existing path. We know that
291                  * url->dir ends with a slash. */
292                 char *tmp = new_url->path;
293                 new_url->path = talloc_asprintf(new_url, "%s%s", url->dir, s);
294                 talloc_free(tmp);
295         }
296
297         /* replace ->dir and ->file with components from ->path */
298         pb_url_parse_path(new_url);
299
300         /* and re-generate the full URL */
301         pb_url_update_full(new_url);
302
303         return new_url;
304 }
305
306 const char *pb_url_scheme_name(enum pb_url_scheme scheme)
307 {
308         const struct pb_scheme_info *info = pb_url_scheme_info(scheme);
309         return info ? info->str : NULL;
310 }