iPXE
mime.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2026 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 *
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
22 */
23
24FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26#include <stdint.h>
27#include <string.h>
28#include <strings.h>
29#include <ctype.h>
30#include <errno.h>
31#include <ipxe/image.h>
32#include <ipxe/base64.h>
33#include <ipxe/mime.h>
34
35/** @file
36 *
37 * MIME image format
38 *
39 * The MIME format is defined in RFC 2045 and RFC 2046 (with reference
40 * to RFC 822). We treat it firstly as a simple single-member archive
41 * format where the content is extracted using the specified encoding.
42 *
43 * We additionally support multipart MIME as an archive format from
44 * which we attempt to extract the first body part that has the MIME
45 * type "text/x-ipxe". This makes it possible to store both
46 * cloud-init configuration and an iPXE boot script in the same user
47 * metadata blob for a cloud instance.
48 *
49 * Due to its historical origins, MIME is an irritatingly flexible
50 * format. We do not attempt to support all possible MIME files: only
51 * those that we might reasonably expect to encounter as cloud
52 * instance metadata.
53 */
54
55static const struct mime_type * mime_type ( const char *name );
56
57/**
58 * Parse MIME attribute
59 *
60 * @v image MIME image
61 * @v value Header value
62 * @v eol End of header line
63 * @v name Attribute name (including terminating equals sign)
64 * @v attr MIME attribute to update
65 */
66static void mime_parse_attribute ( struct image *image, const char *value,
67 const char *eol, const char *name,
68 struct mime_attribute *attr ) {
69 const char *match;
70
71 /* Locate attribute */
72 match = strcasestr ( value, name );
73 if ( ( ! match ) || ( match >= eol ) )
74 return;
75 attr->value = ( match + strlen ( name ) );
76 DBGC2 ( image, "MIME %s found %s\"", image->name, name );
77
78 /* Determine attribute length */
79 if ( attr->value[0] == '"' )
80 attr->value++;
81 attr->len = 0;
82 while ( attr->value[attr->len] && ( attr->value[attr->len] != '"' ) &&
83 ( ! isspace ( attr->value[attr->len] ) ) ) {
84 DBGC2 ( image, "%c", attr->value[attr->len] );
85 attr->len++;
86 }
87 DBGC2 ( image, "\"\n" );
88 assert ( &attr->value[attr->len] <= eol );
89}
90
91/**
92 * Parse Content-Type header
93 *
94 * @v image MIME image
95 * @v value Header value
96 * @v eol End of header line
97 * @v headers MIME headers to update
98 */
99static void mime_parse_type ( struct image *image, const char *value,
100 const char *eol, struct mime_headers *headers ) {
101
102 /* Record content type */
103 headers->type = value;
104 DBGC2 ( image, "MIME %s found content type\n", image->name );
105
106 /* Check for boundary separator */
107 mime_parse_attribute ( image, value, eol, "boundary=",
108 &headers->boundary );
109}
110
111/**
112 * Parse Content-Transfer-Encoding header
113 *
114 * @v image MIME image
115 * @v value Header value
116 * @v eol End of header line
117 * @v headers MIME headers to update
118 */
119static void mime_parse_encoding ( struct image *image, const char *value,
120 const char *eol __unused,
121 struct mime_headers *headers ) {
122
123 /* Record content transfer encoding */
124 headers->encoding = value;
125 DBGC2 ( image, "MIME %s found content transfer encoding\n",
126 image->name );
127}
128
129/** Recognised MIME headers */
130const struct mime_header mime_headers[] = {
131 {
132 .name = "Content-Type:",
133 .parse = mime_parse_type,
134
135 },
136 {
137 .name = "Content-Transfer-Encoding:",
138 .parse = mime_parse_encoding,
139 },
140};
141
142/**
143 * Identify MIME header
144 *
145 * @v line Header line
146 * @ret header MIME header, or NULL if not recognised
147 */
148static const struct mime_header * mime_header ( const char *line ) {
149 const struct mime_header *header;
150 unsigned int i;
151
152 /* Identify MIME header */
153 for ( i = 0 ; i < ( sizeof ( mime_headers ) /
154 sizeof ( mime_headers[0] ) ) ; i++ ) {
155 header = &mime_headers[i];
156 if ( strncasecmp ( line, header->name,
157 strlen ( header->name ) ) == 0 ) {
158 return header;
159 }
160 }
161
162 return NULL;
163}
164
165/**
166 * Parse MIME headers
167 *
168 * @v image MIME image
169 * @v text Start of MIME headers within image
170 * @v headers MIME headers to fill in
171 * @ret rc Return status code
172 */
173static int mime_parse ( struct image *image, const char *text,
174 struct mime_headers *headers ) {
175 const struct mime_header *header;
176 const char *value;
177 const char *line;
178 const char *next;
179 const char *eol;
180
181 /* Initialise headers */
182 memset ( headers, 0, sizeof ( *headers ) );
183
184 /* Parse headers until reaching empty-line separator */
185 for ( line = text ; ; line = next ) {
186
187 /* Locate end of line */
188 eol = strchr ( line, '\n' );
189 if ( ! eol ) {
190 DBGC ( image, "MIME %s premature end of file\n",
191 image->name );
192 return -EINVAL;
193 }
194 next = ( eol + 1 );
195
196 /* Check for empty line */
197 if ( eol == line )
198 break;
199 if ( eol[-1] == '\r' )
200 eol--;
201 if ( eol == line )
202 break;
203
204 /* Identify header (if recognised) */
205 header = mime_header ( line );
206 if ( ! header )
207 continue;
208
209 /* Locate start of value */
210 value = ( line + strlen ( header->name ) );
211 while ( ( *value != '\n' ) && isspace ( *value ) )
212 value++;
213
214 /* Parse header */
215 header->parse ( image, value, eol, headers );
216 }
217
218 /* Check for mandatory headers */
219 if ( ! headers->type ) {
220 DBGC ( image, "MIME %s missing content type\n", image->name );
221 return -EINVAL;
222 }
223
224 /* Record length of headers */
225 headers->len = ( next - text );
226
227 return 0;
228}
229
230/**
231 * Decode MIME entity with identity encoding
232 *
233 * @v image MIME image
234 * @v headers MIME headers
235 * @v decoded Decoded image
236 * @ret rc Return status code
237 */
238static int mime_decode_identity ( struct image *image,
239 const struct mime_headers *headers,
240 struct image *decoded ) {
241 size_t len;
242 int rc;
243
244 /* Allocate space */
245 len = ( image->len - headers->len );
246 if ( ( rc = image_set_len ( decoded, len ) ) != 0 )
247 return rc;
248
249 /* Decode data */
250 memcpy ( decoded->rwdata, ( image->data + headers->len ), len );
251
252 return 0;
253}
254
255/**
256 * Decode MIME entity with Base64 encoding
257 *
258 * @v image MIME image
259 * @v headers MIME headers
260 * @v decoded Decoded image
261 * @ret rc Return status code
262 */
263static int mime_decode_base64 ( struct image *image,
264 const struct mime_headers *headers,
265 struct image *decoded ) {
266 const char *encoded;
267 size_t max_len;
268 int len;
269 int rc;
270
271 /* Allocate space for decoded data */
272 encoded = ( image->text + headers->len );
273 max_len = base64_decoded_max_len ( encoded );
274 if ( ( rc = image_set_len ( decoded, max_len ) ) != 0 )
275 return rc;
276
277 /* Decode data */
278 len = base64_decode ( encoded, decoded->rwdata, decoded->len );
279 if ( len < 0 ) {
280 rc = len;
281 DBGC ( image, "MIME %s could not decode base64: %s\n",
282 image->name, strerror ( rc ) );
283 return rc;
284 }
285 assert ( ( ( size_t ) len ) <= max_len );
286
287 /* Set decoded length */
288 if ( ( rc = image_set_len ( decoded, len ) ) != 0 )
289 return rc;
290
291 return 0;
292}
293
294/** MIME encodings */
295static const struct mime_encoding mime_encodings[] = {
296 {
297 /* Default encoding */
298 .name = "7bit",
299 .decode = mime_decode_identity,
300 },
301 {
302 .name = "8bit",
303 .decode = mime_decode_identity,
304 },
305 {
306 .name = "binary",
307 .decode = mime_decode_identity,
308 },
309 {
310 .name = "base64",
311 .decode = mime_decode_base64,
312 },
313};
314
315/**
316 * Identify MIME encoding
317 *
318 * @v name Encoding name, or NULL to use default
319 * @ret encoding MIME encoding, or NULL if not recognised
320 */
321static const struct mime_encoding * mime_encoding ( const char *name ) {
322 const struct mime_encoding *encoding;
323 size_t len;
324 unsigned int i;
325
326 /* Use default encoding if no name specified */
327 if ( ! name )
328 return &mime_encodings[0];
329
330 /* Identify MIME encoding */
331 for ( i = 0 ; i < ( sizeof ( mime_encodings ) /
332 sizeof ( mime_encodings[0] ) ) ; i++ ) {
333 encoding = &mime_encodings[i];
334 len = strlen ( encoding->name );
335 if ( ( strncasecmp ( name, encoding->name, len ) == 0 ) &&
336 ( ( name[len] == ';' ) || isspace ( name[len] ) ) ) {
337 return encoding;
338 }
339 }
340
341 return NULL;
342}
343
344/**
345 * Extract single MIME entity
346 *
347 * @v image MIME image
348 * @v headers MIME headers
349 * @v extracted Extracted image
350 * @ret rc Return status code
351 */
352static int mime_extract_entity ( struct image *image,
353 const struct mime_headers *headers,
354 struct image *extracted ) {
355 const struct mime_encoding *encoding;
356 int rc;
357
358 /* Identify encoding */
359 encoding = mime_encoding ( headers->encoding );
360 if ( ! encoding ) {
361 DBGC ( image, "MIME %s has unrecognised encoding\n",
362 image->name );
363 return -ENOTSUP;
364 }
365 DBGC ( image, "MIME %s has encoding %s\n",
366 image->name, encoding->name );
367
368 /* Decode via applicable encoding */
369 if ( ( rc = encoding->decode ( image, headers, extracted ) ) != 0 )
370 return rc;
371
372 return 0;
373}
374
375/**
376 * Find end of current part in multipart MIME image
377 *
378 * @v image MIME image
379 * @v boundary Boundary separator
380 * @v pos Current position within image
381 * @ret end End of current part, or NULL if not found
382 */
383static const char * mime_part_end ( struct image *image,
384 const struct mime_attribute *boundary,
385 const char *pos ) {
386
387 /* Locate boundary marker */
388 while ( 1 ) {
389 pos = strstr ( pos, "\n--" );
390 if ( ! pos )
391 break;
392 pos += ( 1 /* newline */ );
393 if ( strncmp ( ( pos + 2 /* -- */ ), boundary->value,
394 boundary->len ) != 0 ) {
395 continue;
396 }
397 return pos;
398 }
399
400 DBGC ( image, "MIME %s missing boundary marker\n", image->name );
401 return NULL;
402}
403
404/**
405 * Find start of next part in multipart MIME image
406 *
407 * @v image MIME image
408 * @v boundary Boundary separator
409 * @v pos End of current part within image
410 * @v next Start of next part, or NULL if not found
411 */
412static const char * mime_part_next ( struct image *image,
413 const struct mime_attribute *boundary,
414 const char *pos ) {
415
416 /* Skip boundary marker */
417 pos += ( 2 /* -- */ + boundary->len );
418
419 /* Check for end boundary */
420 if ( strcmp ( pos, "--" ) == 0 )
421 return NULL;
422
423 /* Skip trailing whitespace */
424 while ( ( *pos != '\n' ) && isspace ( *pos ) )
425 pos++;
426
427 /* Check for and skip terminating newline */
428 if ( *pos != '\n' ) {
429 DBGC ( image, "MIME %s malformed boundary marker\n",
430 image->name );
431 return NULL;
432 }
433 pos++;
434
435 return pos;
436}
437
438/**
439 * Extract part from multipart MIME image
440 *
441 * @v image MIME image
442 * @v headers MIME headers
443 * @v extracted Extracted image
444 * @ret rc Return status code
445 *
446 * Extraction will produce a MIME image containing only the selected
447 * part. (We rely on the recursive behaviour of image_extract_exec()
448 * to extract the content within the extracted part.)
449 */
450static int mime_extract_part ( struct image *image,
451 const struct mime_headers *headers,
452 struct image *extracted ) {
453 const struct mime_attribute *boundary = &headers->boundary;
454 const struct mime_type *type;
455 struct mime_headers subheaders;
456 const char *part;
457 const char *end;
458 size_t len;
459 int rc;
460
461 /* Find end of preamble */
463 if ( ! end )
464 return -ENOENT;
465
466 /* Look for a usable part */
467 while ( ( part = mime_part_next ( image, boundary, end ) ) &&
468 ( end = mime_part_end ( image, boundary, part ) ) ) {
469
470 /* Parse part headers */
471 DBGC ( image, "MIME %s found part at [%#zx,%#zx)\n",
472 image->name, ( part - image->text ),
473 ( end - image->text ) );
474 if ( ( rc = mime_parse ( image, part, &subheaders ) ) != 0 )
475 return rc;
476
477 /* Check for a recognised MIME type */
478 type = mime_type ( subheaders.type );
479 if ( ! type )
480 continue;
481 DBGC ( image, "MIME %s found part with type %s\n",
482 image->name, type->name );
483
484 /* Extract part */
485 len = ( end - part );
486 if ( ( rc = image_set_len ( extracted, len ) ) != 0 )
487 return rc;
488 memcpy ( extracted->rwdata, part, len );
489 return 0;
490 }
491
492 DBGC ( image, "MIME %s found no usable part\n", image->name );
493 return -ENOENT;
494}
495
496/**
497 * Probe MIME image
498 *
499 * @v image Compressed kernel image
500 * @ret rc Return status code
501 */
502static int mime_probe ( struct image *image ) {
503 struct mime_headers headers;
504 int rc;
505
506 /* Check for MIME headers */
507 if ( ( rc = mime_parse ( image, image->text, &headers ) ) != 0 )
508 return rc;
509
510 return 0;
511}
512
513/** MIME types */
514static const struct mime_type mime_types[] = {
515 {
516 .name = "multipart/mixed",
517 .extract = mime_extract_part,
518 },
519 {
520 .name = "text/x-ipxe",
521 .extract = mime_extract_entity,
522 },
523};
524
525/**
526 * Identify MIME type
527 *
528 * @v name Encoding
529 * @ret type MIME type, or NULL if not recognised
530 */
531static const struct mime_type * mime_type ( const char *name ) {
532 const struct mime_type *type;
533 size_t len;
534 unsigned int i;
535
536 /* Identify MIME type */
537 for ( i = 0 ; i < ( sizeof ( mime_types ) /
538 sizeof ( mime_types[0] ) ) ; i++ ) {
539 type = &mime_types[i];
540 len = strlen ( type->name );
541 if ( ( strncasecmp ( name, type->name, len ) == 0 ) &&
542 ( ( name[len] == ';' ) || isspace ( name[len] ) ) ) {
543 return type;
544 }
545 }
546
547 return NULL;
548}
549
550/**
551 * Extract MIME image
552 *
553 * @v image Compressed kernel image
554 * @v extracted Extracted image
555 * @ret rc Return status code
556 */
557static int mime_extract ( struct image *image, struct image *extracted ) {
558 const struct mime_type *type;
559 struct mime_headers headers;
560 int rc;
561
562 /* Parse headers */
563 if ( ( rc = mime_parse ( image, image->text, &headers ) ) != 0 )
564 return rc;
565
566 /* Identify MIME type */
567 type = mime_type ( headers.type );
568 if ( ! type ) {
569 DBGC ( image, "MIME %s has no type\n", image->name );
570 return -ENOTSUP;
571 }
572 DBGC ( image, "MIME %s has type %s\n", image->name, type->name );
573
574 /* Extract image */
575 if ( ( rc = type->extract ( image, &headers, extracted ) ) != 0 )
576 return rc;
577
578 return 0;
579}
580
581/** MIME image type */
582struct image_type mime_image_type __image_type ( PROBE_NORMAL ) = {
583 .name = "MIME",
584 .probe = mime_probe,
585 .extract = mime_extract,
586 .exec = image_extract_exec,
587};
#define NULL
NULL pointer (VOID *).
Definition Base.h:321
struct arbelprm_rc_send_wqe rc
Definition arbel.h:3
pseudo_bit_t value[0x00020]
Definition arbel.h:2
uint8_t headers[IB_MAX_HEADER_SIZE]
Definition arbel.h:3
int image_extract_exec(struct image *image)
Extract and execute image.
Definition archive.c:108
#define assert(condition)
Assert a condition at run-time.
Definition assert.h:50
const char * name
Definition ath9k_hw.c:1986
int base64_decode(const char *encoded, void *data, size_t len)
Base64-decode string.
Definition base64.c:92
Base64 encoding.
static size_t base64_decoded_max_len(const char *encoded)
Calculate maximum length of base64-decoded string.
Definition base64.h:35
int isspace(int character)
Check to see if character is a space.
Definition ctype.c:42
Character types.
uint32_t next
Next descriptor address.
Definition dwmac.h:11
ring len
Length.
Definition dwmac.h:226
uint32_t type
Operating system type.
Definition ena.h:1
struct ena_llq_option header
Header locations.
Definition ena.h:5
Error codes.
#define __unused
Declare a variable or data structure as unused.
Definition compiler.h:598
#define DBGC2(...)
Definition compiler.h:547
#define DBGC(...)
Definition compiler.h:530
#define FILE_LICENCE(_licence)
Declare a particular licence as applying to a file.
Definition compiler.h:921
#define ENOENT
No such file or directory.
Definition errno.h:515
#define EINVAL
Invalid argument.
Definition errno.h:429
#define ENOTSUP
Operation not supported.
Definition errno.h:590
int image_set_len(struct image *image, size_t len)
Set image length.
Definition image.c:249
Executable images.
#define PROBE_NORMAL
Normal image probe priority.
Definition image.h:163
#define __image_type(probe_order)
An executable image type.
Definition image.h:177
String functions.
void * memcpy(void *dest, const void *src, size_t len) __nonnull
void * memset(void *dest, int character, size_t len) __nonnull
String functions.
uint8_t attr
Type and attributes.
Definition librm.h:7
static int mime_extract(struct image *image, struct image *extracted)
Extract MIME image.
Definition mime.c:557
static void mime_parse_encoding(struct image *image, const char *value, const char *eol __unused, struct mime_headers *headers)
Parse Content-Transfer-Encoding header.
Definition mime.c:119
static const struct mime_type mime_types[]
MIME types.
Definition mime.c:514
static const struct mime_encoding mime_encodings[]
MIME encodings.
Definition mime.c:295
static void mime_parse_attribute(struct image *image, const char *value, const char *eol, const char *name, struct mime_attribute *attr)
Parse MIME attribute.
Definition mime.c:66
static int mime_probe(struct image *image)
Probe MIME image.
Definition mime.c:502
static void mime_parse_type(struct image *image, const char *value, const char *eol, struct mime_headers *headers)
Parse Content-Type header.
Definition mime.c:99
static int mime_decode_identity(struct image *image, const struct mime_headers *headers, struct image *decoded)
Decode MIME entity with identity encoding.
Definition mime.c:238
static const char * mime_part_end(struct image *image, const struct mime_attribute *boundary, const char *pos)
Find end of current part in multipart MIME image.
Definition mime.c:383
static const struct mime_type * mime_type(const char *name)
Identify MIME type.
Definition mime.c:531
static int mime_decode_base64(struct image *image, const struct mime_headers *headers, struct image *decoded)
Decode MIME entity with Base64 encoding.
Definition mime.c:263
static int mime_parse(struct image *image, const char *text, struct mime_headers *headers)
Parse MIME headers.
Definition mime.c:173
static const char * mime_part_next(struct image *image, const struct mime_attribute *boundary, const char *pos)
Find start of next part in multipart MIME image.
Definition mime.c:412
static const struct mime_header * mime_header(const char *line)
Identify MIME header.
Definition mime.c:148
static int mime_extract_entity(struct image *image, const struct mime_headers *headers, struct image *extracted)
Extract single MIME entity.
Definition mime.c:352
static int mime_extract_part(struct image *image, const struct mime_headers *headers, struct image *extracted)
Extract part from multipart MIME image.
Definition mime.c:450
static const struct mime_encoding * mime_encoding(const char *name)
Identify MIME encoding.
Definition mime.c:321
MIME format.
uint32_t end
Ending offset.
Definition netvsc.h:7
char * strerror(int errno)
Retrieve string representation of error number.
Definition strerror.c:79
int strcmp(const char *first, const char *second)
Compare strings.
Definition string.c:174
char * strchr(const char *src, int character)
Find character within a string.
Definition string.c:288
int strncasecmp(const char *first, const char *second, size_t max)
Compare case-insensitive strings.
Definition string.c:222
int strncmp(const char *first, const char *second, size_t max)
Compare strings.
Definition string.c:187
char * strstr(const char *haystack, const char *needle)
Find substring.
Definition string.c:324
char * strcasestr(const char *haystack, const char *needle)
Find case-insensitive substring.
Definition string.c:341
size_t strlen(const char *src)
Get length of string.
Definition string.c:244
An executable image type.
Definition image.h:102
An executable image.
Definition image.h:24
const void * data
Read-only data.
Definition image.h:51
char * name
Name.
Definition image.h:38
size_t len
Length of raw file image.
Definition image.h:63
const char * text
Read-only text (guaranteed to be NUL terminated).
Definition image.h:60
void * rwdata
Writable data.
Definition image.h:53
A MIME attribute.
Definition mime.h:16
size_t len
Length of value.
Definition mime.h:20
const char * value
Attribute value (not NUL-terminated).
Definition mime.h:18
A MIME content transfer encoding.
Definition mime.h:52
int(* decode)(struct image *image, const struct mime_headers *headers, struct image *decoded)
Decode entity.
Definition mime.h:63
const char * name
Name.
Definition mime.h:54
A recognised MIME header.
Definition mime.h:36
MIME headers.
Definition mime.h:24
const char * type
Content type (not NUL-terminated).
Definition mime.h:28
struct mime_attribute boundary
Boundary separator.
Definition mime.h:30
A MIME type.
Definition mime.h:69