/
githubmirror
/
ipxe
Обзор
Документация
Войти
/
githubmirror
/
ipxe
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/image/mime.c
587 строк
14 KB
Michael Brown
[mime] Fix the use of MIME images without an explicit encoding
27 июл 2026, 17:29
27 июл 2026, 17:29
3ca799e
Код
Авторство
О чём код?
/* * 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 ); #include <stdint.h> #include <string.h> #include <strings.h> #include <ctype.h> #include <errno.h> #include <ipxe/image.h> #include <ipxe/base64.h> #include <ipxe/mime.h> /** @file * * MIME image format * * The MIME format is defined in RFC 2045 and RFC 2046 (with reference * to RFC 822). We treat it firstly as a simple single-member archive * format where the content is extracted using the specified encoding. * * We additionally support multipart MIME as an archive format from * which we attempt to extract the first body part that has the MIME * type "text/x-ipxe". This makes it possible to store both * cloud-init configuration and an iPXE boot script in the same user * metadata blob for a cloud instance. * * Due to its historical origins, MIME is an irritatingly flexible * format. We do not attempt to support all possible MIME files: only * those that we might reasonably expect to encounter as cloud * instance metadata. */ static const struct mime_type * mime_type ( const char *name ); /** * Parse MIME attribute * * @v image MIME image * @v value Header value * @v eol End of header line * @v name Attribute name (including terminating equals sign) * @v attr MIME attribute to update */ static void mime_parse_attribute ( struct image *image, const char *value, const char *eol, const char *name, struct mime_attribute *attr ) { const char *match; /* Locate attribute */ match = strcasestr ( value, name ); if ( ( ! match ) || ( match >= eol ) ) return; attr->value = ( match + strlen ( name ) ); DBGC2 ( image, "MIME %s found %s\"", image->name, name ); /* Determine attribute length */ if ( attr->value[0] == '"' ) attr->value++; attr->len = 0; while ( attr->value[attr->len] && ( attr->value[attr->len] != '"' ) && ( ! isspace ( attr->value[attr->len] ) ) ) { DBGC2 ( image, "%c", attr->value[attr->len] ); attr->len++; } DBGC2 ( image, "\"\n" ); assert ( &attr->value[attr->len] <= eol ); } /** * Parse Content-Type header * * @v image MIME image * @v value Header value * @v eol End of header line * @v headers MIME headers to update */ static void mime_parse_type ( struct image *image, const char *value, const char *eol, struct mime_headers *headers ) { /* Record content type */ headers->type = value; DBGC2 ( image, "MIME %s found content type\n", image->name ); /* Check for boundary separator */ mime_parse_attribute ( image, value, eol, "boundary=", &headers->boundary ); } /** * Parse Content-Transfer-Encoding header * * @v image MIME image * @v value Header value * @v eol End of header line * @v headers MIME headers to update */ static void mime_parse_encoding ( struct image *image, const char *value, const char *eol __unused, struct mime_headers *headers ) { /* Record content transfer encoding */ headers->encoding = value; DBGC2 ( image, "MIME %s found content transfer encoding\n", image->name ); } /** Recognised MIME headers */ const struct mime_header mime_headers[] = { { .name = "Content-Type:", .parse = mime_parse_type, }, { .name = "Content-Transfer-Encoding:", .parse = mime_parse_encoding, }, }; /** * Identify MIME header * * @v line Header line * @ret header MIME header, or NULL if not recognised */ static const struct mime_header * mime_header ( const char *line ) { const struct mime_header *header; unsigned int i; /* Identify MIME header */ for ( i = 0 ; i < ( sizeof ( mime_headers ) / sizeof ( mime_headers[0] ) ) ; i++ ) { header = &mime_headers[i]; if ( strncasecmp ( line, header->name, strlen ( header->name ) ) == 0 ) { return header; } } return NULL; } /** * Parse MIME headers * * @v image MIME image * @v text Start of MIME headers within image * @v headers MIME headers to fill in * @ret rc Return status code */ static int mime_parse ( struct image *image, const char *text, struct mime_headers *headers ) { const struct mime_header *header; const char *value; const char *line; const char *next; const char *eol; /* Initialise headers */ memset ( headers, 0, sizeof ( *headers ) ); /* Parse headers until reaching empty-line separator */ for ( line = text ; ; line = next ) { /* Locate end of line */ eol = strchr ( line, '\n' ); if ( ! eol ) { DBGC ( image, "MIME %s premature end of file\n", image->name ); return -EINVAL; } next = ( eol + 1 ); /* Check for empty line */ if ( eol == line ) break; if ( eol[-1] == '\r' ) eol--; if ( eol == line ) break; /* Identify header (if recognised) */ header = mime_header ( line ); if ( ! header ) continue; /* Locate start of value */ value = ( line + strlen ( header->name ) ); while ( ( *value != '\n' ) && isspace ( *value ) ) value++; /* Parse header */ header->parse ( image, value, eol, headers ); } /* Check for mandatory headers */ if ( ! headers->type ) { DBGC ( image, "MIME %s missing content type\n", image->name ); return -EINVAL; } /* Record length of headers */ headers->len = ( next - text ); return 0; } /** * Decode MIME entity with identity encoding * * @v image MIME image * @v headers MIME headers * @v decoded Decoded image * @ret rc Return status code */ static int mime_decode_identity ( struct image *image, const struct mime_headers *headers, struct image *decoded ) { size_t len; int rc; /* Allocate space */ len = ( image->len - headers->len ); if ( ( rc = image_set_len ( decoded, len ) ) != 0 ) return rc; /* Decode data */ memcpy ( decoded->rwdata, ( image->data + headers->len ), len ); return 0; } /** * Decode MIME entity with Base64 encoding * * @v image MIME image * @v headers MIME headers * @v decoded Decoded image * @ret rc Return status code */ static int mime_decode_base64 ( struct image *image, const struct mime_headers *headers, struct image *decoded ) { const char *encoded; size_t max_len; int len; int rc; /* Allocate space for decoded data */ encoded = ( image->text + headers->len ); max_len = base64_decoded_max_len ( encoded ); if ( ( rc = image_set_len ( decoded, max_len ) ) != 0 ) return rc; /* Decode data */ len = base64_decode ( encoded, decoded->rwdata, decoded->len ); if ( len < 0 ) { rc = len; DBGC ( image, "MIME %s could not decode base64: %s\n", image->name, strerror ( rc ) ); return rc; } assert ( ( ( size_t ) len ) <= max_len ); /* Set decoded length */ if ( ( rc = image_set_len ( decoded, len ) ) != 0 ) return rc; return 0; } /** MIME encodings */ static const struct mime_encoding mime_encodings[] = { { /* Default encoding */ .name = "7bit", .decode = mime_decode_identity, }, { .name = "8bit", .decode = mime_decode_identity, }, { .name = "binary", .decode = mime_decode_identity, }, { .name = "base64", .decode = mime_decode_base64, }, }; /** * Identify MIME encoding * * @v name Encoding name, or NULL to use default * @ret encoding MIME encoding, or NULL if not recognised */ static const struct mime_encoding * mime_encoding ( const char *name ) { const struct mime_encoding *encoding; size_t len; unsigned int i; /* Use default encoding if no name specified */ if ( ! name ) return &mime_encodings[0]; /* Identify MIME encoding */ for ( i = 0 ; i < ( sizeof ( mime_encodings ) / sizeof ( mime_encodings[0] ) ) ; i++ ) { encoding = &mime_encodings[i]; len = strlen ( encoding->name ); if ( ( strncasecmp ( name, encoding->name, len ) == 0 ) && ( ( name[len] == ';' ) || isspace ( name[len] ) ) ) { return encoding; } } return NULL; } /** * Extract single MIME entity * * @v image MIME image * @v headers MIME headers * @v extracted Extracted image * @ret rc Return status code */ static int mime_extract_entity ( struct image *image, const struct mime_headers *headers, struct image *extracted ) { const struct mime_encoding *encoding; int rc; /* Identify encoding */ encoding = mime_encoding ( headers->encoding ); if ( ! encoding ) { DBGC ( image, "MIME %s has unrecognised encoding\n", image->name ); return -ENOTSUP; } DBGC ( image, "MIME %s has encoding %s\n", image->name, encoding->name ); /* Decode via applicable encoding */ if ( ( rc = encoding->decode ( image, headers, extracted ) ) != 0 ) return rc; return 0; } /** * Find end of current part in multipart MIME image * * @v image MIME image * @v boundary Boundary separator * @v pos Current position within image * @ret end End of current part, or NULL if not found */ static const char * mime_part_end ( struct image *image, const struct mime_attribute *boundary, const char *pos ) { /* Locate boundary marker */ while ( 1 ) { pos = strstr ( pos, "\n--" ); if ( ! pos ) break; pos += ( 1 /* newline */ ); if ( strncmp ( ( pos + 2 /* -- */ ), boundary->value, boundary->len ) != 0 ) { continue; } return pos; } DBGC ( image, "MIME %s missing boundary marker\n", image->name ); return NULL; } /** * Find start of next part in multipart MIME image * * @v image MIME image * @v boundary Boundary separator * @v pos End of current part within image * @v next Start of next part, or NULL if not found */ static const char * mime_part_next ( struct image *image, const struct mime_attribute *boundary, const char *pos ) { /* Skip boundary marker */ pos += ( 2 /* -- */ + boundary->len ); /* Check for end boundary */ if ( strcmp ( pos, "--" ) == 0 ) return NULL; /* Skip trailing whitespace */ while ( ( *pos != '\n' ) && isspace ( *pos ) ) pos++; /* Check for and skip terminating newline */ if ( *pos != '\n' ) { DBGC ( image, "MIME %s malformed boundary marker\n", image->name ); return NULL; } pos++; return pos; } /** * Extract part from multipart MIME image * * @v image MIME image * @v headers MIME headers * @v extracted Extracted image * @ret rc Return status code * * Extraction will produce a MIME image containing only the selected * part. (We rely on the recursive behaviour of image_extract_exec() * to extract the content within the extracted part.) */ static int mime_extract_part ( struct image *image, const struct mime_headers *headers, struct image *extracted ) { const struct mime_attribute *boundary = &headers->boundary; const struct mime_type *type; struct mime_headers subheaders; const char *part; const char *end; size_t len; int rc; /* Find end of preamble */ end = mime_part_end ( image, boundary, image->text ); if ( ! end ) return -ENOENT; /* Look for a usable part */ while ( ( part = mime_part_next ( image, boundary, end ) ) && ( end = mime_part_end ( image, boundary, part ) ) ) { /* Parse part headers */ DBGC ( image, "MIME %s found part at [%#zx,%#zx)\n", image->name, ( part - image->text ), ( end - image->text ) ); if ( ( rc = mime_parse ( image, part, &subheaders ) ) != 0 ) return rc; /* Check for a recognised MIME type */ type = mime_type ( subheaders.type ); if ( ! type ) continue; DBGC ( image, "MIME %s found part with type %s\n", image->name, type->name ); /* Extract part */ len = ( end - part ); if ( ( rc = image_set_len ( extracted, len ) ) != 0 ) return rc; memcpy ( extracted->rwdata, part, len ); return 0; } DBGC ( image, "MIME %s found no usable part\n", image->name ); return -ENOENT; } /** * Probe MIME image * * @v image Compressed kernel image * @ret rc Return status code */ static int mime_probe ( struct image *image ) { struct mime_headers headers; int rc; /* Check for MIME headers */ if ( ( rc = mime_parse ( image, image->text, &headers ) ) != 0 ) return rc; return 0; } /** MIME types */ static const struct mime_type mime_types[] = { { .name = "multipart/mixed", .extract = mime_extract_part, }, { .name = "text/x-ipxe", .extract = mime_extract_entity, }, }; /** * Identify MIME type * * @v name Encoding * @ret type MIME type, or NULL if not recognised */ static const struct mime_type * mime_type ( const char *name ) { const struct mime_type *type; size_t len; unsigned int i; /* Identify MIME type */ for ( i = 0 ; i < ( sizeof ( mime_types ) / sizeof ( mime_types[0] ) ) ; i++ ) { type = &mime_types[i]; len = strlen ( type->name ); if ( ( strncasecmp ( name, type->name, len ) == 0 ) && ( ( name[len] == ';' ) || isspace ( name[len] ) ) ) { return type; } } return NULL; } /** * Extract MIME image * * @v image Compressed kernel image * @v extracted Extracted image * @ret rc Return status code */ static int mime_extract ( struct image *image, struct image *extracted ) { const struct mime_type *type; struct mime_headers headers; int rc; /* Parse headers */ if ( ( rc = mime_parse ( image, image->text, &headers ) ) != 0 ) return rc; /* Identify MIME type */ type = mime_type ( headers.type ); if ( ! type ) { DBGC ( image, "MIME %s has no type\n", image->name ); return -ENOTSUP; } DBGC ( image, "MIME %s has type %s\n", image->name, type->name ); /* Extract image */ if ( ( rc = type->extract ( image, &headers, extracted ) ) != 0 ) return rc; return 0; } /** MIME image type */ struct image_type mime_image_type __image_type ( PROBE_NORMAL ) = { .name = "MIME", .probe = mime_probe, .extract = mime_extract, .exec = image_extract_exec, };