Add facility for resolving base+relative URIs (and paths).
[gpxe.git] / src / core / uri.c
1 /*
2  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 /** @file
20  *
21  * Uniform Resource Identifiers
22  *
23  */
24
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <libgen.h>
29 #include <gpxe/vsprintf.h>
30 #include <gpxe/uri.h>
31
32 /**
33  * Dump URI for debugging
34  *
35  * @v uri               URI
36  */
37 static void dump_uri ( struct uri *uri ) {
38         if ( uri->scheme )
39                 DBG ( " scheme \"%s\"", uri->scheme );
40         if ( uri->opaque )
41                 DBG ( " opaque \"%s\"", uri->opaque );
42         if ( uri->user )
43                 DBG ( " user \"%s\"", uri->user );
44         if ( uri->password )
45                 DBG ( " password \"%s\"", uri->password );
46         if ( uri->host )
47                 DBG ( " host \"%s\"", uri->host );
48         if ( uri->port )
49                 DBG ( " port \"%s\"", uri->port );
50         if ( uri->path )
51                 DBG ( " path \"%s\"", uri->path );
52         if ( uri->query )
53                 DBG ( " query \"%s\"", uri->query );
54         if ( uri->fragment )
55                 DBG ( " fragment \"%s\"", uri->fragment );
56 }
57
58 /**
59  * Parse URI
60  *
61  * @v uri_string        URI as a string
62  * @ret uri             URI
63  *
64  * Splits a URI into its component parts.  The return URI structure is
65  * dynamically allocated and must eventually be freed by calling
66  * uri_put().
67  */
68 struct uri * parse_uri ( const char *uri_string ) {
69         struct uri *uri;
70         char *raw;
71         char *tmp;
72         char *path = NULL;
73         char *authority = NULL;
74         size_t raw_len;
75
76         /* Allocate space for URI struct and a copy of the string */
77         raw_len = ( strlen ( uri_string ) + 1 /* NUL */ );
78         uri = malloc ( sizeof ( *uri ) + raw_len );
79         if ( ! uri )
80                 return NULL;
81         raw = ( ( ( char * ) uri ) + sizeof ( *uri ) );
82
83         /* Zero URI struct and copy in the raw string */
84         memset ( uri, 0, sizeof ( *uri ) );
85         memcpy ( raw, uri_string, raw_len );
86
87         /* Start by chopping off the fragment, if it exists */
88         if ( ( tmp = strchr ( raw, '#' ) ) ) {
89                 *(tmp++) = '\0';
90                 uri->fragment = tmp;
91         }
92
93         /* Identify absolute/relative URI */
94         if ( ( tmp = strchr ( raw, ':' ) ) ) {
95                 /* Absolute URI: identify hierarchical/opaque */
96                 uri->scheme = raw;
97                 *(tmp++) = '\0';
98                 if ( *tmp == '/' ) {
99                         /* Absolute URI with hierarchical part */
100                         path = tmp;
101                 } else {
102                         /* Absolute URI with opaque part */
103                         uri->opaque = tmp;
104                 }
105         } else {
106                 /* Relative URI */
107                 path = raw;
108         }
109
110         /* If we don't have a path (i.e. we have an absolute URI with
111          * an opaque portion, we're already finished processing
112          */
113         if ( ! path )
114                 goto done;
115
116         /* Chop off the query, if it exists */
117         if ( ( tmp = strchr ( path, '?' ) ) ) {
118                 *(tmp++) = '\0';
119                 uri->query = tmp;
120         }
121
122         /* Identify net/absolute/relative path */
123         if ( strncmp ( path, "//", 2 ) == 0 ) {
124                 /* Net path.  If this is terminated by the first '/'
125                  * of an absolute path, then we have no space for a
126                  * terminator after the authority field, so shuffle
127                  * the authority down by one byte, overwriting one of
128                  * the two slashes.
129                  */
130                 authority = ( path + 2 );
131                 if ( ( tmp = strchr ( authority, '/' ) ) ) {
132                         /* Shuffle down */
133                         uri->path = tmp;
134                         memmove ( ( authority - 1 ), authority,
135                                   ( tmp - authority ) );
136                         authority--;
137                         *(--tmp) = '\0';
138                 }
139         } else {
140                 /* Absolute/relative path */
141                 uri->path = path;
142         }
143
144         /* Split authority into user[:password] and host[:port] portions */
145         if ( ( tmp = strchr ( authority, '@' ) ) ) {
146                 /* Has user[:password] */
147                 *(tmp++) = '\0';
148                 uri->host = tmp;
149                 uri->user = authority;
150                 if ( ( tmp = strchr ( authority, ':' ) ) ) {
151                         /* Has password */
152                         *(tmp++) = '\0';
153                         uri->password = tmp;
154                 }
155         } else {
156                 /* No user:password */
157                 uri->host = authority;
158         }
159
160         /* Split host into host[:port] */
161         if ( ( tmp = strchr ( uri->host, ':' ) ) ) {
162                 *(tmp++) = '\0';
163                 uri->port = tmp;
164         }
165
166  done:
167         DBG ( "URI \"%s\" split into", uri_string );
168         dump_uri ( uri );
169         DBG ( "\n" );
170
171         return uri;
172 }
173
174 /**
175  * Get port from URI
176  *
177  * @v uri               URI
178  * @v default_port      Default port to use if none specified in URI
179  * @ret port            Port
180  */
181 unsigned int uri_port ( struct uri *uri, unsigned int default_port ) {
182         return ( uri->port ? strtoul ( uri->port, NULL, 0 ) : default_port );
183 }
184
185 /**
186  * Unparse URI
187  *
188  * @v buf               Buffer to fill with URI string
189  * @v size              Size of buffer
190  * @v uri               URI to write into buffer
191  * @ret len             Length of URI string
192  */
193 int unparse_uri ( char *buf, size_t size, struct uri *uri ) {
194         int used = 0;
195
196         DBG ( "URI unparsing" );
197         dump_uri ( uri );
198         DBG ( "\n" );
199
200         /* Special-case opaque URIs */
201         if ( uri->opaque ) {
202                 return ssnprintf ( ( buf + used ), ( size - used ),
203                                    "%s:%s", uri->scheme, uri->opaque );
204         }
205
206         /* scheme:// */
207         if ( uri->scheme ) {
208                 used += ssnprintf ( ( buf + used ), ( size - used ),
209                                     "%s://", uri->scheme );
210         }
211
212         /* [user[:password]@]host[:port] */
213         if ( uri->host ) {
214                 if ( uri->user ) {
215                         used += ssnprintf ( ( buf + used ), ( size - used ),
216                                             "%s", uri->user );
217                         if ( uri->password ) {
218                                 used += ssnprintf ( ( buf + used ),
219                                                     ( size - used ),
220                                                     ":%s", uri->password );
221                         }
222                         used += ssnprintf ( ( buf + used ), ( size - used ),
223                                             "@" );
224                 }
225                 used += ssnprintf ( ( buf + used ), ( size - used ), "%s",
226                                     uri->host );
227                 if ( uri->port ) {
228                         used += ssnprintf ( ( buf + used ), ( size - used ),
229                                             ":%s", uri->port );
230                 }
231         }
232
233         /* /path */
234         if ( uri->path ) {
235                 used += ssnprintf ( ( buf + used ), ( size - used ),
236                                     "%s", uri->path );
237         }
238
239         /* ?query */
240         if ( uri->query ) {
241                 used += ssnprintf ( ( buf + used ), ( size - used ),
242                                     "?%s", uri->query );
243         }
244
245         /* #fragment */
246         if ( uri->fragment ) {
247                 used += ssnprintf ( ( buf + used ), ( size - used ),
248                                     "#%s", uri->fragment );
249         }
250
251         return used;
252 }
253
254 /**
255  * Duplicate URI
256  *
257  * @v uri               URI
258  * @ret uri             Duplicate URI
259  *
260  * Creates a modifiable copy of a URI.
261  */
262 struct uri * uri_dup ( struct uri *uri ) {
263         size_t len = ( unparse_uri ( NULL, 0, uri ) + 1 );
264         char buf[len];
265
266         unparse_uri ( buf, len, uri );
267         return parse_uri ( buf );
268 }
269
270 /**
271  * Resolve base+relative path
272  *
273  * @v base_uri          Base path
274  * @v relative_uri      Relative path
275  * @ret resolved_uri    Resolved path
276  *
277  * Takes a base path (e.g. "/var/lib/tftpboot/vmlinuz" and a relative
278  * path (e.g. "initrd.gz") and produces a new path
279  * (e.g. "/var/lib/tftpboot/initrd.gz").  Note that any non-directory
280  * portion of the base path will automatically be stripped; this
281  * matches the semantics used when resolving the path component of
282  * URIs.
283  */
284 char * resolve_path ( const char *base_path,
285                       const char *relative_path ) {
286         size_t base_len = ( strlen ( base_path ) + 1 );
287         char base_path_copy[base_len];
288         char *base_tmp = base_path_copy;
289         char *resolved;
290
291         /* If relative path is absolute, just re-use it */
292         if ( relative_path[0] == '/' )
293                 return strdup ( relative_path );
294
295         /* Create modifiable copy of path for dirname() */
296         memcpy ( base_tmp, base_path, base_len );
297         base_tmp = dirname ( base_tmp );
298
299         /* Process "./" and "../" elements */
300         while ( *relative_path == '.' ) {
301                 relative_path++;
302                 if ( *relative_path == 0 ) {
303                         /* Do nothing */
304                 } else if ( *relative_path == '/' ) {
305                         relative_path++;
306                 } else if ( *relative_path == '.' ) {
307                         relative_path++;
308                         if ( *relative_path == 0 ) {
309                                 base_tmp = dirname ( base_tmp );
310                         } else if ( *relative_path == '/' ) {
311                                 base_tmp = dirname ( base_tmp );
312                                 relative_path++;
313                         } else {
314                                 relative_path -= 2;
315                                 break;
316                         }
317                 } else {
318                         relative_path--;
319                         break;
320                 }
321         }
322
323         /* Create and return new path */
324         if ( asprintf ( &resolved, "%s%s%s", base_tmp,
325                         ( ( base_tmp[ strlen ( base_tmp ) - 1 ] == '/' ) ?
326                           "" : "/" ), relative_path ) < 0 )
327                 return NULL;
328
329         return resolved;
330 }
331
332 /**
333  * Resolve base+relative URI
334  *
335  * @v base_uri          Base URI
336  * @v relative_uri      Relative URI
337  * @ret resolved_uri    Resolved URI
338  *
339  * Takes a base URI (e.g. "http://etherboot.org/kernels/vmlinuz" and a
340  * relative URI (e.g. "../initrds/initrd.gz") and produces a new URI
341  * (e.g. "http://etherboot.org/initrds/initrd.gz").
342  */
343 struct uri * resolve_uri ( struct uri *base_uri,
344                            struct uri *relative_uri ) {
345         struct uri tmp_uri;
346         char *tmp_path = NULL;
347         struct uri *new_uri;
348
349         /* If relative URI is absolute, just re-use it */
350         if ( uri_is_absolute ( relative_uri ) )
351                 return uri_get ( relative_uri );
352
353         /* Mangle URI */
354         memcpy ( &tmp_uri, base_uri, sizeof ( tmp_uri ) );
355         if ( relative_uri->path ) {
356                 tmp_path = resolve_path ( ( base_uri->path ?
357                                             base_uri->path : "/" ),
358                                           relative_uri->path );
359                 tmp_uri.path = tmp_path;
360                 tmp_uri.query = relative_uri->query;
361                 tmp_uri.fragment = relative_uri->fragment;
362         } else if ( relative_uri->query ) {
363                 tmp_uri.query = relative_uri->query;
364                 tmp_uri.fragment = relative_uri->fragment;
365         } else if ( relative_uri->fragment ) {
366                 tmp_uri.fragment = relative_uri->fragment;
367         }
368
369         /* Create demangled URI */
370         new_uri = uri_dup ( &tmp_uri );
371         free ( tmp_path );
372         return new_uri;
373 }