[uri] Add support for "data:" URIs

Add support for "data:" URIs as defined in RFC 2397.  These can be
used to construct image content under control of an iPXE script.  For
example:

  # Inject the message "Hello from iPXE" as /etc/motd
  initrd -n motd data:,Hello%20from%20iPXE%0A /etc/motd

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2026-07-16 13:05:09 +01:00
parent 0d3d223a87
commit c208006f78
7 changed files with 338 additions and 0 deletions
+3
View File
@@ -127,6 +127,9 @@ REQUIRE_OBJECT ( nfs_open );
#ifdef DOWNLOAD_PROTO_SLAM
REQUIRE_OBJECT ( slam );
#endif
#ifdef DOWNLOAD_PROTO_DATA
REQUIRE_OBJECT ( datauri );
#endif
/*
* Drag in all requested SAN boot protocols
+1
View File
@@ -46,6 +46,7 @@ FILE_SECBOOT ( PERMITTED );
//#define DOWNLOAD_PROTO_FTP /* File Transfer Protocol */
//#define DOWNLOAD_PROTO_SLAM /* Scalable Local Area Multicast */
//#define DOWNLOAD_PROTO_NFS /* Network File System Protocol */
#define DOWNLOAD_PROTO_DATA /* Inline Data */
/* Protocols supported only on platforms with filesystem abstractions */
#if defined ( PLATFORM_efi )
+152
View File
@@ -0,0 +1,152 @@
/*
* Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <ipxe/base64.h>
#include <ipxe/blob.h>
#include <ipxe/open.h>
#include <ipxe/datauri.h>
/** @file
*
* Data URIs
*
* The "data" URI scheme is defined in RFC 2397. In the interest of
* reducing code size, we support only a practical subset of the
* specification. Media types will be ignored (and must not contain a
* literal comma character). Base64 encoding is supported.
*
* URI encoding will already have been stripped by the URI parser.
* Special characters may therefore be used in the URI string
* (e.g. "hello%20world"). However, URI-encoded NUL bytes (%00) will
* not work as expected, since they will be interpreted as terminating
* the URI string. If NUL bytes are required, then Base64 encoding
* must be used instead.
*/
/**
* Parse data URI
*
* @v uri Data URI
* @v buf Buffer to fill in
* @ret len Length of data, or negative error
*
* The buffer length must be at least the size reported by
* datauri_max_len().
*/
int datauri_parse ( struct uri *uri, void *buf ) {
static const char b64marker[7] = ";base64";
const char *comma;
const char *encoded;
size_t prefix_len;
int len;
int rc;
/* Sanity check */
if ( ! uri->opaque ) {
DBGC ( uri, "DATA %p has no opaque part\n", uri );
return -EINVAL;
}
/* Locate encoded string */
comma = strchr ( uri->opaque, ',' );
if ( ! comma ) {
DBGC ( uri, "DATA %p has no comma separator\n", uri );
return -EINVAL;
}
prefix_len = ( comma - uri->opaque );
encoded = ( comma + 1 );
len = strlen ( encoded );
/* Decode string */
if ( ( prefix_len >= sizeof ( b64marker ) ) &&
( strncasecmp ( ( comma - sizeof ( b64marker ) ), b64marker,
sizeof ( b64marker ) ) == 0 ) ) {
/* Decode Base64 string */
len = base64_decode ( encoded, buf, len );
if ( len < 0 ) {
rc = len;
DBGC ( uri, "DATA %p could not decode Base64: %s\n",
uri, strerror ( rc ) );
return rc;
}
} else {
/* Copy raw string */
strcpy ( buf, encoded );
}
DBGC ( uri, "DATA %p decoded \"%s\":\n", uri, uri->opaque );
DBGC_HDA ( uri, 0, buf, len );
return len;
}
/**
* Open data URI
*
* @v xfer Data transfer interface
* @v uri URI
* @ret rc Return status code
*/
static int datauri_open ( struct interface *xfer, struct uri *uri ) {
void *data;
int len;
int rc;
/* Allocate space for parsed data */
data = malloc ( datauri_max_len ( uri ) );
if ( ! data ) {
rc = -ENOMEM;
goto err_alloc;
}
/* Parse data */
len = datauri_parse ( uri, data );
if ( len < 0 ) {
rc = len;
goto err_parse;
}
/* Open downloadable blob */
if ( ( rc = blob_open ( xfer, data, len ) ) != 0 )
goto err_open;
err_open:
err_parse:
free ( data );
err_alloc:
return rc;
}
/** Data URI opener */
struct uri_opener data_uri_opener __uri_opener = {
.scheme = "data",
.open = datauri_open,
};
+31
View File
@@ -0,0 +1,31 @@
#ifndef _IPXE_DATAURI_H
#define _IPXE_DATAURI_H
/** @file
*
* Data URIs
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );
#include <stdint.h>
#include <string.h>
#include <ipxe/uri.h>
/**
* Get maximum length of parsed data URI
*
* @v uri Data URI
* @ret max_len Maximum length
*/
static inline size_t datauri_max_len ( struct uri *uri ) {
/* Neither URI decoding nor base64 decoding can ever expand data */
return ( uri->opaque ? strlen ( uri->opaque ) : 0 );
}
extern int datauri_parse ( struct uri *uri, void *buf );
#endif /* _IPXE_DATAURI_H */
+1
View File
@@ -92,6 +92,7 @@ FILE_SECBOOT ( PERMITTED );
#define ERRFILE_spcr ( ERRFILE_CORE | 0x00330000 )
#define ERRFILE_disklog ( ERRFILE_CORE | 0x00340000 )
#define ERRFILE_efi_disklog ( ERRFILE_CORE | 0x00350000 )
#define ERRFILE_datauri ( ERRFILE_CORE | 0x00360000 )
#define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 )
#define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 )
+149
View File
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** @file
*
* Data URI tests
*
*/
/* Forcibly enable assertions */
#undef NDEBUG
#include <string.h>
#include <ipxe/uri.h>
#include <ipxe/datauri.h>
#include <ipxe/test.h>
/** Define inline data */
#define DATA(...) { __VA_ARGS__ }
/**
* Report result of data URI test
*
* @v uristring URI string
* @v expected Expected parsed data
* @v expected_len Length of expected parsed data
* @v file Test code file
* @v line Test code line
*/
static void datauri_okx ( const char *uristring, const void *expected,
size_t expected_len, const char *file,
unsigned int line ) {
uint8_t actual[ strlen ( uristring ) /* more than enough */ ];
struct uri *uri;
int len;
/* Parse URI */
uri = parse_uri ( uristring );
okx ( uri != NULL, file, line );
okx ( uri->scheme != NULL, file, line );
okx ( uri->opaque != NULL, file, line );
okx ( strcmp ( uri->scheme, "data" ) == 0, file, line );
/* Sanity check */
okx ( datauri_max_len ( uri ) <= sizeof ( actual ), file, line );
okx ( datauri_max_len ( uri ) >= expected_len, file, line );
/* Parse data URI */
len = datauri_parse ( uri, actual );
okx ( len >= 0, file, line );
okx ( ( ( size_t ) len ) == expected_len, file, line );
okx ( memcmp ( actual, expected, expected_len ) == 0, file, line );
/* Drop reference */
uri_put ( uri );
}
#define datauri_ok( uristring, EXPECTED ) do { \
static const uint8_t expected[] = EXPECTED; \
datauri_okx ( uristring, expected, sizeof ( expected ), \
__FILE__, __LINE__ ); \
} while ( 0 )
/**
* Report result of data URI failure test
*
* @v uristring URI string
* @v file Test code file
* @v line Test code line
*/
static void datauri_fail_okx ( const char *uristring, const char *file,
unsigned int line ) {
uint8_t actual[ strlen ( uristring ) /* more than enough */ ];
struct uri *uri;
int len;
/* Parse URI */
uri = parse_uri ( uristring );
okx ( uri != NULL, file, line );
okx ( uri->scheme != NULL, file, line );
okx ( uri->opaque != NULL, file, line );
okx ( strcmp ( uri->scheme, "data" ) == 0, file, line );
/* Sanity check */
okx ( datauri_max_len ( uri ) <= sizeof ( actual ), file, line );
/* Parse data URI */
len = datauri_parse ( uri, actual );
okx ( len < 0, file, line );
/* Drop reference */
uri_put ( uri );
}
#define datauri_fail_ok( uristring ) \
datauri_fail_okx ( uristring, __FILE__, __LINE__ )
/**
* Perform data URI self-test
*
*/
static void datauri_test_exec ( void ) {
/* Empty data */
datauri_ok ( "data:,", DATA() );
datauri_ok ( "data:;base64,", DATA() );
datauri_ok ( "data:text/plain,", DATA() );
/* Simple values */
datauri_ok ( "data:,hello%20world%21",
DATA ( 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l',
'd', '!' ) );
datauri_ok ( "data:text/plain,simple",
DATA ( 's', 'i', 'm', 'p', 'l', 'e' ) );
datauri_ok ( "data:text/plain;charset=US-ASCII;base64,ZW5jb2RlZA==",
DATA ( 'e', 'n', 'c', 'o', 'd', 'e', 'd' ) );
datauri_ok ( "data:application/octet-string;base64,d2l0aABudWw=",
DATA ( 'w', 'i', 't', 'h', 0x00, 'n', 'u', 'l' ) );
/* Invalid values */
datauri_fail_ok ( "data:" );
datauri_fail_ok ( "data:;base64,INVALID===" );
}
/** Data URI self-test */
struct self_test datauri_test __self_test = {
.name = "datauri",
.exec = datauri_test_exec,
};
+1
View File
@@ -94,3 +94,4 @@ REQUIRE_OBJECT ( ecdsa_test );
REQUIRE_OBJECT ( hkdf_test );
REQUIRE_OBJECT ( ffdhe_test );
REQUIRE_OBJECT ( mime_test );
REQUIRE_OBJECT ( datauri_test );