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