Merge branch 'master' of /pub/scm/gpxe
[gpxe.git] / src / core / uri.c
blob8cb855a6b0729114357d815cc5464c9a41712a11
1 /*
2 * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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.
19 /** @file
21 * Uniform Resource Identifiers
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>
32 /**
33 * Dump URI for debugging
35 * @v uri URI
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 );
60 /**
61 * Parse URI
63 * @v uri_string URI as a string
64 * @ret uri URI
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().
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;
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 ) );
85 /* Zero URI struct and copy in the raw string */
86 memset ( uri, 0, sizeof ( *uri ) );
87 memcpy ( raw, uri_string, raw_len );
89 /* Start by chopping off the fragment, if it exists */
90 if ( ( tmp = strchr ( raw, '#' ) ) ) {
91 *(tmp++) = '\0';
92 uri->fragment = tmp;
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;
107 } else {
108 /* Relative URI */
109 path = raw;
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
115 if ( ! path )
116 goto done;
118 /* Chop off the query, if it exists */
119 if ( ( tmp = strchr ( path, '?' ) ) ) {
120 *(tmp++) = '\0';
121 uri->query = tmp;
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.
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';
141 } else {
142 /* Absolute/relative path */
143 uri->path = path;
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;
157 } else {
158 /* No user:password */
159 uri->host = authority;
162 /* Split host into host[:port] */
163 if ( ( tmp = strchr ( uri->host, ':' ) ) ) {
164 *(tmp++) = '\0';
165 uri->port = tmp;
168 done:
169 DBG ( "URI \"%s\" split into", uri_string );
170 dump_uri ( uri );
171 DBG ( "\n" );
173 return uri;
177 * Get port from URI
179 * @v uri URI, or NULL
180 * @v default_port Default port to use if none specified in URI
181 * @ret port Port
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 ) );
190 * Unparse URI
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
197 int unparse_uri ( char *buf, size_t size, struct uri *uri ) {
198 int used = 0;
200 DBG ( "URI unparsing" );
201 dump_uri ( uri );
202 DBG ( "\n" );
204 /* Special-case NULL URI */
205 if ( ! uri ) {
206 if ( size )
207 buf[0] = '\0';
208 return 0;
211 /* Special-case opaque URIs */
212 if ( uri->opaque ) {
213 return ssnprintf ( ( buf + used ), ( size - used ),
214 "%s:%s", uri->scheme, uri->opaque );
217 /* scheme:// */
218 if ( uri->scheme ) {
219 used += ssnprintf ( ( buf + used ), ( size - used ),
220 "%s://", uri->scheme );
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 );
233 used += ssnprintf ( ( buf + used ), ( size - used ),
234 "@" );
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 );
244 /* /path */
245 if ( uri->path ) {
246 used += ssnprintf ( ( buf + used ), ( size - used ),
247 "%s", uri->path );
250 /* ?query */
251 if ( uri->query ) {
252 used += ssnprintf ( ( buf + used ), ( size - used ),
253 "?%s", uri->query );
256 /* #fragment */
257 if ( uri->fragment ) {
258 used += ssnprintf ( ( buf + used ), ( size - used ),
259 "#%s", uri->fragment );
262 return used;
266 * Duplicate URI
268 * @v uri URI
269 * @ret uri Duplicate URI
271 * Creates a modifiable copy of a URI.
273 struct uri * uri_dup ( struct uri *uri ) {
274 size_t len = ( unparse_uri ( NULL, 0, uri ) + 1 );
275 char buf[len];
277 unparse_uri ( buf, len, uri );
278 return parse_uri ( buf );
282 * Resolve base+relative path
284 * @v base_uri Base path
285 * @v relative_uri Relative path
286 * @ret resolved_uri Resolved path
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.
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;
302 /* If relative path is absolute, just re-use it */
303 if ( relative_path[0] == '/' )
304 return strdup ( relative_path );
306 /* Create modifiable copy of path for dirname() */
307 memcpy ( base_tmp, base_path, base_len );
308 base_tmp = dirname ( base_tmp );
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;
328 } else {
329 relative_path--;
330 break;
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;
340 return resolved;
344 * Resolve base+relative URI
346 * @v base_uri Base URI, or NULL
347 * @v relative_uri Relative URI
348 * @ret resolved_uri Resolved URI
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").
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;
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 );
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;
380 /* Create demangled URI */
381 new_uri = uri_dup ( &tmp_uri );
382 free ( tmp_path );
383 return new_uri;