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