/*-
- * Copyright (c) 2003-2007 Tim Kientzle
+ * Copyright (c) 2003-2023 Tim Kientzle
* Copyright (c) 2011-2012 Michihiro NAKAJIMA
* Copyright (c) 2016 Martin Matuska
* All rights reserved.
};
struct tar {
- struct archive_string acl_text;
struct archive_string entry_pathname;
/* For "GNU.sparse.name" and other similar path extensions. */
struct archive_string entry_pathname_override;
- struct archive_string entry_linkpath;
struct archive_string entry_uname;
struct archive_string entry_gname;
- struct archive_string longlink;
+ struct archive_string entry_linkpath;
struct archive_string longname;
- struct archive_string pax_header;
struct archive_string pax_global;
struct archive_string line;
- int pax_hdrcharset_binary;
- int header_recursion_depth;
+ int pax_hdrcharset_utf8;
int64_t entry_bytes_remaining;
int64_t entry_offset;
int64_t entry_padding;
int64_t entry_bytes_unconsumed;
int64_t realsize;
- int sparse_allowed;
struct sparse_block *sparse_list;
struct sparse_block *sparse_last;
int64_t sparse_offset;
int64_t sparse_numbytes;
int sparse_gnu_major;
int sparse_gnu_minor;
- char sparse_gnu_pending;
+ char sparse_gnu_attributes_seen;
+ char filetype;
struct archive_string localname;
struct archive_string_conv *opt_sconv;
static int gnu_sparse_old_parse(struct archive_read *, struct tar *,
const struct gnu_sparse *sparse, int length);
static int gnu_sparse_01_parse(struct archive_read *, struct tar *,
- const char *);
+ const char *, size_t);
static ssize_t gnu_sparse_10_read(struct archive_read *, struct tar *,
- size_t *);
+ size_t *);
static int header_Solaris_ACL(struct archive_read *, struct tar *,
struct archive_entry *, const void *, size_t *);
static int header_common(struct archive_read *, struct tar *,
struct archive_entry *, const void *);
static int header_old_tar(struct archive_read *, struct tar *,
struct archive_entry *, const void *);
-static int header_pax_extensions(struct archive_read *, struct tar *,
+static int header_pax_extension(struct archive_read *, struct tar *,
struct archive_entry *, const void *, size_t *);
static int header_pax_global(struct archive_read *, struct tar *,
struct archive_entry *, const void *h, size_t *);
-static int header_longlink(struct archive_read *, struct tar *,
- struct archive_entry *, const void *h, size_t *);
-static int header_longname(struct archive_read *, struct tar *,
+static int header_gnu_longlink(struct archive_read *, struct tar *,
struct archive_entry *, const void *h, size_t *);
-static int read_mac_metadata_blob(struct archive_read *, struct tar *,
+static int header_gnu_longname(struct archive_read *, struct tar *,
struct archive_entry *, const void *h, size_t *);
+static int is_mac_metadata_entry(struct archive_entry *entry);
+static int read_mac_metadata_blob(struct archive_read *,
+ struct archive_entry *, size_t *);
static int header_volume(struct archive_read *, struct tar *,
struct archive_entry *, const void *h, size_t *);
static int header_ustar(struct archive_read *, struct tar *,
struct archive_entry *);
static int checksum(struct archive_read *, const void *);
static int pax_attribute(struct archive_read *, struct tar *,
- struct archive_entry *, const char *key, const char *value,
- size_t value_length);
-static int pax_attribute_acl(struct archive_read *, struct tar *,
- struct archive_entry *, const char *, int);
-static int pax_attribute_xattr(struct archive_entry *, const char *,
- const char *);
-static int pax_header(struct archive_read *, struct tar *,
- struct archive_entry *, struct archive_string *);
-static void pax_time(const char *, int64_t *sec, long *nanos);
+ struct archive_entry *, const char *key, size_t key_length,
+ size_t value_length, size_t *unconsumed);
+static int pax_attribute_LIBARCHIVE_xattr(struct archive_entry *,
+ const char *, size_t, const char *, size_t);
+static int pax_attribute_SCHILY_acl(struct archive_read *, struct tar *,
+ struct archive_entry *, size_t, int);
+static int pax_attribute_SUN_holesdata(struct archive_read *, struct tar *,
+ struct archive_entry *, const char *, size_t);
+static void pax_time(const char *, size_t, int64_t *sec, long *nanos);
static ssize_t readline(struct archive_read *, struct tar *, const char **,
ssize_t limit, size_t *);
static int read_body_to_string(struct archive_read *, struct tar *,
struct archive_string *, const void *h, size_t *);
-static int solaris_sparse_parse(struct archive_read *, struct tar *,
- struct archive_entry *, const char *);
+static int read_bytes_to_string(struct archive_read *,
+ struct archive_string *, size_t, size_t *);
static int64_t tar_atol(const char *, size_t);
static int64_t tar_atol10(const char *, size_t);
static int64_t tar_atol256(const char *, size_t);
static int tar_read_header(struct archive_read *, struct tar *,
struct archive_entry *, size_t *);
static int tohex(int c);
-static char *url_decode(const char *);
+static char *url_decode(const char *, size_t);
static void tar_flush_unconsumed(struct archive_read *, size_t *);
+/* Sanity limits: These numbers should be low enough to
+ * prevent a maliciously-crafted archive from forcing us to
+ * allocate extreme amounts of memory. But of course, they
+ * need to be high enough for any correct value. These
+ * will likely need some adjustment as we get more experience. */
+static const size_t guname_limit = 65536; /* Longest uname or gname: 64kiB */
+static const size_t pathname_limit = 1048576; /* Longest path name: 1MiB */
+static const size_t sparse_map_limit = 8 * 1048576; /* Longest sparse map: 8MiB */
+static const size_t xattr_limit = 16 * 1048576; /* Longest xattr: 16MiB */
+static const size_t fflags_limit = 512; /* Longest fflags */
+static const size_t acl_limit = 131072; /* Longest textual ACL: 128kiB */
int
archive_read_support_format_gnutar(struct archive *a)
tar = (struct tar *)(a->format->data);
gnu_clear_sparse_list(tar);
- archive_string_free(&tar->acl_text);
archive_string_free(&tar->entry_pathname);
archive_string_free(&tar->entry_pathname_override);
- archive_string_free(&tar->entry_linkpath);
archive_string_free(&tar->entry_uname);
archive_string_free(&tar->entry_gname);
archive_string_free(&tar->line);
archive_string_free(&tar->pax_global);
- archive_string_free(&tar->pax_header);
archive_string_free(&tar->longname);
- archive_string_free(&tar->longlink);
archive_string_free(&tar->localname);
free(tar);
(a->format->data) = NULL;
* probably not worthwhile just to support the relatively
* obscure tar->cpio conversion case.
*/
+ /* TODO: Move this into `struct tar` to avoid conflicts
+ * when reading multiple archives */
static int default_inode;
static int default_dev;
struct tar *tar;
return (ARCHIVE_FATAL);
if (*buff == NULL) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Truncated tar archive");
+ "Truncated tar archive"
+ " detected while reading data");
return (ARCHIVE_FATAL);
}
if (bytes_read > tar->entry_bytes_remaining)
}
/*
- * This function recursively interprets all of the headers associated
+ * This function reads and interprets all of the headers associated
* with a single entry.
*/
static int
struct archive_entry *entry, size_t *unconsumed)
{
ssize_t bytes;
- int err, eof_vol_header;
+ int err = ARCHIVE_OK, err2;
+ int eof_fatal = 0; /* EOF is okay at some points... */
const char *h;
const struct archive_entry_header_ustar *header;
const struct archive_entry_header_gnutar *gnuheader;
- eof_vol_header = 0;
-
- /* Loop until we find a workable header record. */
- for (;;) {
- tar_flush_unconsumed(a, unconsumed);
+ /* Bitmask of what header types we've seen. */
+ int32_t seen_headers = 0;
+ static const int32_t seen_A_header = 1;
+ static const int32_t seen_g_header = 2;
+ static const int32_t seen_K_header = 4;
+ static const int32_t seen_L_header = 8;
+ static const int32_t seen_V_header = 16;
+ static const int32_t seen_x_header = 32; /* Also X */
+ static const int32_t seen_mac_metadata = 512;
+
+ tar->pax_hdrcharset_utf8 = 1;
+ tar->sparse_gnu_attributes_seen = 0;
+ archive_string_empty(&(tar->entry_gname));
+ archive_string_empty(&(tar->entry_pathname));
+ archive_string_empty(&(tar->entry_pathname_override));
+ archive_string_empty(&(tar->entry_uname));
- /* Read 512-byte header record */
- h = __archive_read_ahead(a, 512, &bytes);
- if (bytes < 0)
- return ((int)bytes);
- if (bytes == 0) { /* EOF at a block boundary. */
- /* Some writers do omit the block of nulls. <sigh> */
- return (ARCHIVE_EOF);
- }
- if (bytes < 512) { /* Short block at EOF; this is bad. */
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_FILE_FORMAT,
- "Truncated tar archive");
- return (ARCHIVE_FATAL);
- }
- *unconsumed = 512;
+ /* Ensure format is set. */
+ if (a->archive.archive_format_name == NULL) {
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR;
+ a->archive.archive_format_name = "tar";
+ }
- /* Header is workable if it's not an end-of-archive mark. */
- if (h[0] != 0 || !archive_block_is_null(h))
- break;
+ /*
+ * TODO: Write global/default pax options into
+ * 'entry' struct here before overwriting with
+ * file-specific options.
+ */
- /* Ensure format is set for archives with only null blocks. */
- if (a->archive.archive_format_name == NULL) {
- a->archive.archive_format = ARCHIVE_FORMAT_TAR;
- a->archive.archive_format_name = "tar";
- }
+ /* Loop over all the headers needed for the next entry */
+ for (;;) {
- if (!tar->read_concatenated_archives) {
- /* Try to consume a second all-null record, as well. */
+ /* Find the next valid header record. */
+ while (1) {
tar_flush_unconsumed(a, unconsumed);
- h = __archive_read_ahead(a, 512, NULL);
- if (h != NULL && h[0] == 0 && archive_block_is_null(h))
- __archive_read_consume(a, 512);
- archive_clear_error(&a->archive);
- return (ARCHIVE_EOF);
- }
- /*
- * We're reading concatenated archives, ignore this block and
- * loop to get the next.
- */
- }
+ /* Read 512-byte header record */
+ h = __archive_read_ahead(a, 512, &bytes);
+ if (bytes < 0)
+ return ((int)bytes);
+ if (bytes == 0) { /* EOF at a block boundary. */
+ if (eof_fatal) {
+ /* We've read a special header already;
+ * if there's no regular header, then this is
+ * a premature EOF. */
+ archive_set_error(&a->archive, EINVAL,
+ "Damaged tar archive");
+ return (ARCHIVE_FATAL);
+ } else {
+ return (ARCHIVE_EOF);
+ }
+ }
+ if (bytes < 512) { /* Short block at EOF; this is bad. */
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Truncated tar archive"
+ " detected while reading next heaader");
+ return (ARCHIVE_FATAL);
+ }
+ *unconsumed += 512;
- /*
- * Note: If the checksum fails and we return ARCHIVE_RETRY,
- * then the client is likely to just retry. This is a very
- * crude way to search for the next valid header!
- *
- * TODO: Improve this by implementing a real header scan.
- */
- if (!checksum(a, h)) {
- tar_flush_unconsumed(a, unconsumed);
- archive_set_error(&a->archive, EINVAL, "Damaged tar archive");
- return (ARCHIVE_RETRY); /* Retryable: Invalid header */
- }
+ if (h[0] == 0 && archive_block_is_null(h)) {
+ /* We found a NULL block which indicates end-of-archive */
- if (++tar->header_recursion_depth > 32) {
- tar_flush_unconsumed(a, unconsumed);
- archive_set_error(&a->archive, EINVAL, "Too many special headers");
- return (ARCHIVE_WARN);
- }
+ if (tar->read_concatenated_archives) {
+ /* We're ignoring NULL blocks, so keep going. */
+ continue;
+ }
- /* Determine the format variant. */
- header = (const struct archive_entry_header_ustar *)h;
+ /* Try to consume a second all-null record, as well. */
+ /* If we can't, that's okay. */
+ tar_flush_unconsumed(a, unconsumed);
+ h = __archive_read_ahead(a, 512, NULL);
+ if (h != NULL && h[0] == 0 && archive_block_is_null(h))
+ __archive_read_consume(a, 512);
- switch(header->typeflag[0]) {
- case 'A': /* Solaris tar ACL */
- a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
- a->archive.archive_format_name = "Solaris tar";
- err = header_Solaris_ACL(a, tar, entry, h, unconsumed);
- break;
- case 'g': /* POSIX-standard 'g' header. */
- a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
- a->archive.archive_format_name = "POSIX pax interchange format";
- err = header_pax_global(a, tar, entry, h, unconsumed);
- if (err == ARCHIVE_EOF)
- return (err);
- break;
- case 'K': /* Long link name (GNU tar, others) */
- err = header_longlink(a, tar, entry, h, unconsumed);
- break;
- case 'L': /* Long filename (GNU tar, others) */
- err = header_longname(a, tar, entry, h, unconsumed);
- break;
- case 'V': /* GNU volume header */
- err = header_volume(a, tar, entry, h, unconsumed);
- if (err == ARCHIVE_EOF)
- eof_vol_header = 1;
- break;
- case 'X': /* Used by SUN tar; same as 'x'. */
- a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
- a->archive.archive_format_name =
- "POSIX pax interchange format (Sun variant)";
- err = header_pax_extensions(a, tar, entry, h, unconsumed);
- break;
- case 'x': /* POSIX-standard 'x' header. */
- a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
- a->archive.archive_format_name = "POSIX pax interchange format";
- err = header_pax_extensions(a, tar, entry, h, unconsumed);
- break;
- default:
- gnuheader = (const struct archive_entry_header_gnutar *)h;
- if (memcmp(gnuheader->magic, "ustar \0", 8) == 0) {
- a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR;
- a->archive.archive_format_name = "GNU tar format";
- err = header_gnutar(a, tar, entry, h, unconsumed);
- } else if (memcmp(header->magic, "ustar", 5) == 0) {
- if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) {
- a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR;
- a->archive.archive_format_name = "POSIX ustar format";
+ archive_clear_error(&a->archive);
+ return (ARCHIVE_EOF);
}
- err = header_ustar(a, tar, entry, h);
- } else {
- a->archive.archive_format = ARCHIVE_FORMAT_TAR;
- a->archive.archive_format_name = "tar (non-POSIX)";
- err = header_old_tar(a, tar, entry, h);
- }
- }
- if (err == ARCHIVE_FATAL)
- return (err);
-
- tar_flush_unconsumed(a, unconsumed);
- h = NULL;
- header = NULL;
+ /* This is NOT a null block, so it must be a valid header. */
+ if (!checksum(a, h)) {
+ tar_flush_unconsumed(a, unconsumed);
+ archive_set_error(&a->archive, EINVAL, "Damaged tar archive");
+ /* If we've read some critical information (pax headers, etc)
+ * and _then_ see a bad header, we can't really recover. */
+ if (eof_fatal) {
+ return (ARCHIVE_FATAL);
+ } else {
+ return (ARCHIVE_RETRY);
+ }
+ }
+ break;
+ }
- --tar->header_recursion_depth;
- /* Yuck. Apple's design here ends up storing long pathname
- * extensions for both the AppleDouble extension entry and the
- * regular entry.
- */
- if ((err == ARCHIVE_WARN || err == ARCHIVE_OK) &&
- tar->header_recursion_depth == 0 &&
- tar->process_mac_extensions) {
- int err2 = read_mac_metadata_blob(a, tar, entry, h, unconsumed);
- if (err2 < err)
- err = err2;
- }
-
- /* We return warnings or success as-is. Anything else is fatal. */
- if (err == ARCHIVE_WARN || err == ARCHIVE_OK) {
- if (tar->sparse_gnu_pending) {
- if (tar->sparse_gnu_major == 1 &&
- tar->sparse_gnu_minor == 0) {
- ssize_t bytes_read;
-
- tar->sparse_gnu_pending = 0;
- /* Read initial sparse map. */
- bytes_read = gnu_sparse_10_read(a, tar, unconsumed);
- if (bytes_read < 0)
- return ((int)bytes_read);
- tar->entry_bytes_remaining -= bytes_read;
+ /* Determine the format variant. */
+ header = (const struct archive_entry_header_ustar *)h;
+ switch(header->typeflag[0]) {
+ case 'A': /* Solaris tar ACL */
+ if (seen_headers & seen_A_header) {
+ return (ARCHIVE_FATAL);
+ }
+ seen_headers |= seen_A_header;
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
+ a->archive.archive_format_name = "Solaris tar";
+ err2 = header_Solaris_ACL(a, tar, entry, h, unconsumed);
+ break;
+ case 'g': /* POSIX-standard 'g' header. */
+ if (seen_headers & seen_g_header) {
+ return (ARCHIVE_FATAL);
+ }
+ seen_headers |= seen_g_header;
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
+ a->archive.archive_format_name = "POSIX pax interchange format";
+ err2 = header_pax_global(a, tar, entry, h, unconsumed);
+ break;
+ case 'K': /* Long link name (GNU tar, others) */
+ if (seen_headers & seen_K_header) {
+ return (ARCHIVE_FATAL);
+ }
+ seen_headers |= seen_K_header;
+ err2 = header_gnu_longlink(a, tar, entry, h, unconsumed);
+ break;
+ case 'L': /* Long filename (GNU tar, others) */
+ if (seen_headers & seen_L_header) {
+ return (ARCHIVE_FATAL);
+ }
+ seen_headers |= seen_L_header;
+ err2 = header_gnu_longname(a, tar, entry, h, unconsumed);
+ break;
+ case 'V': /* GNU volume header */
+ if (seen_headers & seen_V_header) {
+ return (ARCHIVE_FATAL);
+ }
+ seen_headers |= seen_V_header;
+ err2 = header_volume(a, tar, entry, h, unconsumed);
+ break;
+ case 'X': /* Used by SUN tar; same as 'x'. */
+ if (seen_headers & seen_x_header) {
+ return (ARCHIVE_FATAL);
+ }
+ seen_headers |= seen_x_header;
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
+ a->archive.archive_format_name =
+ "POSIX pax interchange format (Sun variant)";
+ err2 = header_pax_extension(a, tar, entry, h, unconsumed);
+ break;
+ case 'x': /* POSIX-standard 'x' header. */
+ if (seen_headers & seen_x_header) {
+ return (ARCHIVE_FATAL);
+ }
+ seen_headers |= seen_x_header;
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE;
+ a->archive.archive_format_name = "POSIX pax interchange format";
+ err2 = header_pax_extension(a, tar, entry, h, unconsumed);
+ break;
+ default: /* Regular header: Legacy tar, GNU tar, or ustar */
+ gnuheader = (const struct archive_entry_header_gnutar *)h;
+ if (memcmp(gnuheader->magic, "ustar \0", 8) == 0) {
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR;
+ a->archive.archive_format_name = "GNU tar format";
+ err2 = header_gnutar(a, tar, entry, h, unconsumed);
+ } else if (memcmp(header->magic, "ustar", 5) == 0) {
+ if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) {
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR;
+ a->archive.archive_format_name = "POSIX ustar format";
+ }
+ err2 = header_ustar(a, tar, entry, h);
} else {
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_MISC,
- "Unrecognized GNU sparse file format");
- return (ARCHIVE_WARN);
+ a->archive.archive_format = ARCHIVE_FORMAT_TAR;
+ a->archive.archive_format_name = "tar (non-POSIX)";
+ err2 = header_old_tar(a, tar, entry, h);
+ }
+ err = err_combine(err, err2);
+ /* We return warnings or success as-is. Anything else is fatal. */
+ if (err < ARCHIVE_WARN) {
+ return (ARCHIVE_FATAL);
+ }
+ /* Filename of the form `._filename` is an AppleDouble
+ * extension entry. The body is the macOS metadata blob;
+ * this is followed by another entry with the actual
+ * regular file data.
+ * This design has two drawbacks:
+ * = it's brittle; you might just have a file with such a name
+ * = it duplicates any long pathname extensions
+ *
+ * TODO: This probably shouldn't be here at all. Consider
+ * just returning the contents as a regular entry here and
+ * then dealing with it when we write data to disk.
+ */
+ if (tar->process_mac_extensions
+ && ((seen_headers & seen_mac_metadata) == 0)
+ && is_mac_metadata_entry(entry)) {
+ err2 = read_mac_metadata_blob(a, entry, unconsumed);
+ if (err2 < ARCHIVE_WARN) {
+ return (ARCHIVE_FATAL);
+ }
+ err = err_combine(err, err2);
+ /* Note: Other headers can appear again. */
+ seen_headers = seen_mac_metadata;
+ break;
}
- tar->sparse_gnu_pending = 0;
+
+ /* Reconcile GNU sparse attributes */
+ if (tar->sparse_gnu_attributes_seen) {
+ /* Only 'S' (GNU sparse) and ustar '0' regular files can be sparse */
+ if (tar->filetype != 'S' && tar->filetype != '0') {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Non-regular file cannot be sparse");
+ return (ARCHIVE_WARN);
+ } else if (tar->sparse_gnu_major == 0 &&
+ tar->sparse_gnu_minor == 0) {
+ /* Sparse map already parsed from 'x' header */
+ } else if (tar->sparse_gnu_major == 0 &&
+ tar->sparse_gnu_minor == 1) {
+ /* Sparse map already parsed from 'x' header */
+ } else if (tar->sparse_gnu_major == 1 &&
+ tar->sparse_gnu_minor == 0) {
+ /* Sparse map is prepended to file contents */
+ ssize_t bytes_read;
+ bytes_read = gnu_sparse_10_read(a, tar, unconsumed);
+ if (bytes_read < 0)
+ return ((int)bytes_read);
+ tar->entry_bytes_remaining -= bytes_read;
+ } else {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Unrecognized GNU sparse file format");
+ return (ARCHIVE_WARN);
+ }
+ }
+ return (err);
}
- return (err);
- }
- if (err == ARCHIVE_EOF) {
- if (!eof_vol_header) {
- /* EOF when recursively reading a header is bad. */
- archive_set_error(&a->archive, EINVAL,
- "Damaged tar archive");
- } else {
- /* If we encounter just a GNU volume header treat
- * this situation as an empty archive */
- return (ARCHIVE_EOF);
+
+ /* We're between headers ... */
+ err = err_combine(err, err2);
+ if (err == ARCHIVE_FATAL)
+ return (err);
+
+ /* The GNU volume header and the pax `g` global header
+ * are both allowed to be the only header in an
+ * archive. If we've seen any other header, a
+ * following EOF is fatal. */
+ if ((seen_headers & ~seen_V_header & ~seen_g_header) != 0) {
+ eof_fatal = 1;
}
}
- return (ARCHIVE_FATAL);
}
/*
struct archive_entry *entry, const void *h, size_t *unconsumed)
{
const struct archive_entry_header_ustar *header;
+ struct archive_string acl_text;
size_t size;
int err, acl_type;
int64_t type;
*/
header = (const struct archive_entry_header_ustar *)h;
size = (size_t)tar_atol(header->size, sizeof(header->size));
- err = read_body_to_string(a, tar, &(tar->acl_text), h, unconsumed);
+ archive_string_init(&acl_text);
+ err = read_body_to_string(a, tar, &acl_text, h, unconsumed);
if (err != ARCHIVE_OK)
return (err);
- /* Recursively read next header */
- err = tar_read_header(a, tar, entry, unconsumed);
- if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN))
- return (err);
-
/* TODO: Examine the first characters to see if this
* is an AIX ACL descriptor. We'll likely never support
* them, but it would be polite to recognize and warn when
* we do see them. */
/* Leading octal number indicates ACL type and number of entries. */
- p = acl = tar->acl_text.s;
+ p = acl = acl_text.s;
type = 0;
while (*p != '\0' && p < acl + size) {
if (*p < '0' || *p > '7') {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"Malformed Solaris ACL attribute (invalid digit)");
+ archive_string_free(&acl_text);
return(ARCHIVE_WARN);
}
type <<= 3;
if (type > 077777777) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"Malformed Solaris ACL attribute (count too large)");
+ archive_string_free(&acl_text);
return (ARCHIVE_WARN);
}
p++;
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"Malformed Solaris ACL attribute (unsupported type %o)",
(int)type);
+ archive_string_free(&acl_text);
return (ARCHIVE_WARN);
}
p++;
if (p >= acl + size) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"Malformed Solaris ACL attribute (body overflow)");
+ archive_string_free(&acl_text);
return(ARCHIVE_WARN);
}
if (tar->sconv_acl == NULL) {
tar->sconv_acl = archive_string_conversion_from_charset(
&(a->archive), "UTF-8", 1);
- if (tar->sconv_acl == NULL)
+ if (tar->sconv_acl == NULL) {
+ archive_string_free(&acl_text);
return (ARCHIVE_FATAL);
+ }
}
archive_strncpy(&(tar->localname), acl, p - acl);
err = archive_acl_from_text_l(archive_entry_acl(entry),
tar->localname.s, acl_type, tar->sconv_acl);
+ /* Workaround: Force perm_is_set() to be correct */
+ /* If this bit were stored in the ACL, this wouldn't be needed */
+ archive_entry_set_perm(entry, archive_entry_perm(entry));
if (err != ARCHIVE_OK) {
if (errno == ENOMEM) {
archive_set_error(&a->archive, ENOMEM,
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
"Malformed Solaris ACL attribute (unparsable)");
}
+ archive_string_free(&acl_text);
return (err);
}
* Interpret 'K' long linkname header.
*/
static int
-header_longlink(struct archive_read *a, struct tar *tar,
+header_gnu_longlink(struct archive_read *a, struct tar *tar,
struct archive_entry *entry, const void *h, size_t *unconsumed)
{
int err;
- err = read_body_to_string(a, tar, &(tar->longlink), h, unconsumed);
- if (err != ARCHIVE_OK)
- return (err);
- err = tar_read_header(a, tar, entry, unconsumed);
- if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN))
- return (err);
- /* Set symlink if symlink already set, else hardlink. */
- archive_entry_copy_link(entry, tar->longlink.s);
- return (ARCHIVE_OK);
+ struct archive_string linkpath;
+ archive_string_init(&linkpath);
+ err = read_body_to_string(a, tar, &linkpath, h, unconsumed);
+ archive_entry_set_link(entry, linkpath.s);
+ return (err);
}
static int
* Interpret 'L' long filename header.
*/
static int
-header_longname(struct archive_read *a, struct tar *tar,
+header_gnu_longname(struct archive_read *a, struct tar *tar,
struct archive_entry *entry, const void *h, size_t *unconsumed)
{
int err;
err = read_body_to_string(a, tar, &(tar->longname), h, unconsumed);
if (err != ARCHIVE_OK)
return (err);
- /* Read and parse "real" header, then override name. */
- err = tar_read_header(a, tar, entry, unconsumed);
- if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN))
- return (err);
if (archive_entry_copy_pathname_l(entry, tar->longname.s,
archive_strlen(&(tar->longname)), tar->sconv) != 0)
err = set_conversion_failed_error(a, tar->sconv, "Pathname");
return (err);
}
-
/*
* Interpret 'V' GNU tar volume header.
*/
header_volume(struct archive_read *a, struct tar *tar,
struct archive_entry *entry, const void *h, size_t *unconsumed)
{
- (void)h;
+ const struct archive_entry_header_ustar *header;
+ int64_t size, to_consume;
+
+ (void)a; /* UNUSED */
+ (void)tar; /* UNUSED */
+ (void)entry; /* UNUSED */
- /* Just skip this and read the next header. */
- return (tar_read_header(a, tar, entry, unconsumed));
+ header = (const struct archive_entry_header_ustar *)h;
+ size = tar_atol(header->size, sizeof(header->size));
+ to_consume = ((size + 511) & ~511);
+ *unconsumed += to_consume;
+ return (ARCHIVE_OK);
}
/*
- * Read body of an archive entry into an archive_string object.
+ * Read the next `size` bytes into the provided string.
+ * Null-terminate the string.
*/
static int
-read_body_to_string(struct archive_read *a, struct tar *tar,
- struct archive_string *as, const void *h, size_t *unconsumed)
-{
- int64_t size;
- const struct archive_entry_header_ustar *header;
+read_bytes_to_string(struct archive_read *a,
+ struct archive_string *as, size_t size,
+ size_t *unconsumed) {
const void *src;
- (void)tar; /* UNUSED */
- header = (const struct archive_entry_header_ustar *)h;
- size = tar_atol(header->size, sizeof(header->size));
- if ((size > 1048576) || (size < 0)) {
- archive_set_error(&a->archive, EINVAL,
- "Special header too large");
- return (ARCHIVE_FATAL);
- }
-
/* Fail if we can't make our buffer big enough. */
if (archive_string_ensure(as, (size_t)size+1) == NULL) {
archive_set_error(&a->archive, ENOMEM,
tar_flush_unconsumed(a, unconsumed);
/* Read the body into the string. */
- *unconsumed = (size_t)((size + 511) & ~ 511);
- src = __archive_read_ahead(a, *unconsumed, NULL);
+ src = __archive_read_ahead(a, size, NULL);
if (src == NULL) {
+ archive_set_error(&a->archive, EINVAL,
+ "Truncated archive"
+ " detected while reading metadata");
*unconsumed = 0;
return (ARCHIVE_FATAL);
}
memcpy(as->s, src, (size_t)size);
as->s[size] = '\0';
as->length = (size_t)size;
+ *unconsumed += size;
return (ARCHIVE_OK);
}
+/*
+ * Read body of an archive entry into an archive_string object.
+ */
+static int
+read_body_to_string(struct archive_read *a, struct tar *tar,
+ struct archive_string *as, const void *h, size_t *unconsumed)
+{
+ int64_t size;
+ const struct archive_entry_header_ustar *header;
+ int r;
+
+ (void)tar; /* UNUSED */
+ header = (const struct archive_entry_header_ustar *)h;
+ size = tar_atol(header->size, sizeof(header->size));
+ if ((size > 1048576) || (size < 0)) {
+ archive_string_empty(as);
+ int64_t to_consume = ((size + 511) & ~511);
+ if (to_consume != __archive_read_consume(a, to_consume)) {
+ return (ARCHIVE_FATAL);
+ }
+ archive_set_error(&a->archive, EINVAL,
+ "Special header too large: %d > 1MiB",
+ (int)size);
+ return (ARCHIVE_WARN);
+ }
+ r = read_bytes_to_string(a, as, size, unconsumed);
+ *unconsumed += 0x1ff & (-size);
+ return(r);
+}
+
/*
* Parse out common header elements.
*
struct archive_entry *entry, const void *h)
{
const struct archive_entry_header_ustar *header;
- char tartype;
+ const char *existing_linkpath;
int err = ARCHIVE_OK;
header = (const struct archive_entry_header_ustar *)h;
- if (header->linkname[0])
- archive_strncpy(&(tar->entry_linkpath),
- header->linkname, sizeof(header->linkname));
- else
- archive_string_empty(&(tar->entry_linkpath));
/* Parse out the numeric fields (all are octal) */
- archive_entry_set_mode(entry,
- (mode_t)tar_atol(header->mode, sizeof(header->mode)));
- archive_entry_set_uid(entry, tar_atol(header->uid, sizeof(header->uid)));
- archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid)));
+
+ /* Split mode handling: Set filetype always, perm only if not already set */
+ archive_entry_set_filetype(entry,
+ (mode_t)tar_atol(header->mode, sizeof(header->mode)));
+ if (!archive_entry_perm_is_set(entry)) {
+ archive_entry_set_perm(entry,
+ (mode_t)tar_atol(header->mode, sizeof(header->mode)));
+ }
+ if (!archive_entry_uid_is_set(entry)) {
+ archive_entry_set_uid(entry, tar_atol(header->uid, sizeof(header->uid)));
+ }
+ if (!archive_entry_gid_is_set(entry)) {
+ archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid)));
+ }
+
tar->entry_bytes_remaining = tar_atol(header->size, sizeof(header->size));
if (tar->entry_bytes_remaining < 0) {
tar->entry_bytes_remaining = 0;
"Tar entry size overflow");
return (ARCHIVE_FATAL);
}
- tar->realsize = tar->entry_bytes_remaining;
- archive_entry_set_size(entry, tar->entry_bytes_remaining);
- archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0);
+ if (!tar->realsize_override) {
+ tar->realsize = tar->entry_bytes_remaining;
+ }
+ archive_entry_set_size(entry, tar->realsize);
+
+ if (!archive_entry_mtime_is_set(entry)) {
+ archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0);
+ }
/* Handle the tar type flag appropriately. */
- tartype = header->typeflag[0];
+ tar->filetype = header->typeflag[0];
- switch (tartype) {
+ /*
+ * TODO: If the linkpath came from Pax extension header, then
+ * we should obey the hdrcharset_utf8 flag when converting these.
+ */
+ switch (tar->filetype) {
case '1': /* Hard link */
- if (archive_entry_copy_hardlink_l(entry, tar->entry_linkpath.s,
- archive_strlen(&(tar->entry_linkpath)), tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv,
- "Linkname");
- if (err == ARCHIVE_FATAL)
- return (err);
+ archive_entry_set_link_to_hardlink(entry);
+ existing_linkpath = archive_entry_hardlink(entry);
+ if (existing_linkpath == NULL || existing_linkpath[0] == '\0') {
+ struct archive_string linkpath;
+ archive_string_init(&linkpath);
+ archive_strncpy(&linkpath,
+ header->linkname, sizeof(header->linkname));
+ if (archive_entry_copy_hardlink_l(entry, linkpath.s,
+ archive_strlen(&linkpath), tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv,
+ "Linkname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
}
/*
* The following may seem odd, but: Technically, tar
*/
break;
case '2': /* Symlink */
+ archive_entry_set_link_to_symlink(entry);
+ existing_linkpath = archive_entry_symlink(entry);
+ if (existing_linkpath == NULL || existing_linkpath[0] == '\0') {
+ struct archive_string linkpath;
+ archive_string_init(&linkpath);
+ archive_strncpy(&linkpath,
+ header->linkname, sizeof(header->linkname));
+ if (archive_entry_copy_symlink_l(entry, linkpath.s,
+ archive_strlen(&linkpath), tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv,
+ "Linkname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
+ }
archive_entry_set_filetype(entry, AE_IFLNK);
archive_entry_set_size(entry, 0);
tar->entry_bytes_remaining = 0;
- if (archive_entry_copy_symlink_l(entry, tar->entry_linkpath.s,
- archive_strlen(&(tar->entry_linkpath)), tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv,
- "Linkname");
- if (err == ARCHIVE_FATAL)
- return (err);
- }
break;
case '3': /* Character device */
archive_entry_set_filetype(entry, AE_IFCHR);
* sparse information in the extended area.
*/
/* FALLTHROUGH */
- case '0':
- /*
- * Enable sparse file "read" support only for regular
- * files and explicit GNU sparse files. However, we
- * don't allow non-standard file types to be sparse.
- */
- tar->sparse_allowed = 1;
+ case '0': /* ustar "regular" file */
/* FALLTHROUGH */
- default: /* Regular file and non-standard types */
+ default: /* Non-standard file types */
/*
* Per POSIX: non-recognized types should always be
* treated as regular files.
}
/*
- * Read a Mac AppleDouble-encoded blob of file metadata,
- * if there is one.
+ * Is this likely an AppleDouble extension?
*/
static int
-read_mac_metadata_blob(struct archive_read *a, struct tar *tar,
- struct archive_entry *entry, const void *h, size_t *unconsumed)
-{
- int64_t size;
- size_t msize;
- const void *data;
+is_mac_metadata_entry(struct archive_entry *entry) {
const char *p, *name;
const wchar_t *wp, *wname;
- (void)h; /* UNUSED */
-
wname = wp = archive_entry_pathname_w(entry);
if (wp != NULL) {
/* Find the last path element. */
* If last path element starts with "._", then
* this is a Mac extension.
*/
- if (wname[0] != L'.' || wname[1] != L'_' || wname[2] == L'\0')
- return ARCHIVE_OK;
+ if (wname[0] == L'.' && wname[1] == L'_' && wname[2] != L'\0')
+ return 1;
} else {
/* Find the last path element. */
name = p = archive_entry_pathname(entry);
* If last path element starts with "._", then
* this is a Mac extension.
*/
- if (name[0] != '.' || name[1] != '_' || name[2] == '\0')
- return ARCHIVE_OK;
+ if (name[0] == '.' && name[1] == '_' && name[2] != '\0')
+ return 1;
}
+ /* Not a mac extension */
+ return 0;
+}
+
+/*
+ * Read a Mac AppleDouble-encoded blob of file metadata,
+ * if there is one.
+ *
+ * TODO: In Libarchive 4, we should consider ripping this
+ * out -- instead, return a file starting with `._` as
+ * a regular file and let the client (or archive_write logic)
+ * handle it.
+ */
+static int
+read_mac_metadata_blob(struct archive_read *a,
+ struct archive_entry *entry, size_t *unconsumed)
+{
+ int64_t size;
+ size_t msize;
+ const void *data;
/* Read the body as a Mac OS metadata blob. */
size = archive_entry_size(entry);
return (ARCHIVE_FATAL);
}
+ /* TODO: Should this merely skip the overlarge entry and
+ * WARN? Or is xattr_limit sufficiently large that we can
+ * safely assume anything larger is malicious? */
+ if (size > (int64_t)xattr_limit) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Oversized AppleDouble extension has size %llu > %llu",
+ (unsigned long long)size,
+ (unsigned long long)xattr_limit);
+ return (ARCHIVE_FATAL);
+ }
+
/*
* TODO: Look beyond the body here to peek at the next header.
* If it's a regular header (not an extension header)
* Q: Is the above idea really possible? Even
* when there are GNU or pax extension entries?
*/
+ tar_flush_unconsumed(a, unconsumed);
data = __archive_read_ahead(a, msize, NULL);
if (data == NULL) {
*unconsumed = 0;
return (ARCHIVE_FATAL);
}
+ archive_entry_clear(entry);
archive_entry_copy_mac_metadata(entry, data, msize);
*unconsumed = (msize + 511) & ~ 511;
- tar_flush_unconsumed(a, unconsumed);
- return (tar_read_header(a, tar, entry, unconsumed));
+ return (ARCHIVE_OK);
}
/*
header_pax_global(struct archive_read *a, struct tar *tar,
struct archive_entry *entry, const void *h, size_t *unconsumed)
{
- int err;
-
- err = read_body_to_string(a, tar, &(tar->pax_global), h, unconsumed);
- if (err != ARCHIVE_OK)
- return (err);
- err = tar_read_header(a, tar, entry, unconsumed);
- return (err);
-}
-
-static int
-header_pax_extensions(struct archive_read *a, struct tar *tar,
- struct archive_entry *entry, const void *h, size_t *unconsumed)
-{
- int err, err2;
-
- err = read_body_to_string(a, tar, &(tar->pax_header), h, unconsumed);
- if (err != ARCHIVE_OK)
- return (err);
+ const struct archive_entry_header_ustar *header;
+ int64_t size, to_consume;
- /* Parse the next header. */
- err = tar_read_header(a, tar, entry, unconsumed);
- if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN))
- return (err);
+ (void)a; /* UNUSED */
+ (void)tar; /* UNUSED */
+ (void)entry; /* UNUSED */
- /*
- * TODO: Parse global/default options into 'entry' struct here
- * before handling file-specific options.
- *
- * This design (parse standard header, then overwrite with pax
- * extended attribute data) usually works well, but isn't ideal;
- * it would be better to parse the pax extended attributes first
- * and then skip any fields in the standard header that were
- * defined in the pax header.
- */
- err2 = pax_header(a, tar, entry, &tar->pax_header);
- err = err_combine(err, err2);
- tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining);
- return (err);
+ header = (const struct archive_entry_header_ustar *)h;
+ size = tar_atol(header->size, sizeof(header->size));
+ to_consume = ((size + 511) & ~511);
+ *unconsumed += to_consume;
+ return (ARCHIVE_OK);
}
-
/*
* Parse a file header for a Posix "ustar" archive entry. This also
* handles "pax" or "extended ustar" entries.
+ *
+ * In order to correctly handle pax attributes (which precede this),
+ * we have to skip parsing any field for which the entry already has
+ * contents.
*/
static int
header_ustar(struct archive_read *a, struct tar *tar,
struct archive_entry *entry, const void *h)
{
const struct archive_entry_header_ustar *header;
- struct archive_string *as;
+ struct archive_string as;
int err = ARCHIVE_OK, r;
header = (const struct archive_entry_header_ustar *)h;
/* Copy name into an internal buffer to ensure null-termination. */
- as = &(tar->entry_pathname);
- if (header->prefix[0]) {
- archive_strncpy(as, header->prefix, sizeof(header->prefix));
- if (as->s[archive_strlen(as) - 1] != '/')
- archive_strappend_char(as, '/');
- archive_strncat(as, header->name, sizeof(header->name));
- } else {
- archive_strncpy(as, header->name, sizeof(header->name));
- }
- if (archive_entry_copy_pathname_l(entry, as->s, archive_strlen(as),
- tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv, "Pathname");
- if (err == ARCHIVE_FATAL)
- return (err);
+ const char *existing_pathname = archive_entry_pathname(entry);
+ if (existing_pathname == NULL || existing_pathname[0] == '\0') {
+ archive_string_init(&as);
+ if (header->prefix[0]) {
+ archive_strncpy(&as, header->prefix, sizeof(header->prefix));
+ if (as.s[archive_strlen(&as) - 1] != '/')
+ archive_strappend_char(&as, '/');
+ archive_strncat(&as, header->name, sizeof(header->name));
+ } else {
+ archive_strncpy(&as, header->name, sizeof(header->name));
+ }
+ if (archive_entry_copy_pathname_l(entry, as.s, archive_strlen(&as),
+ tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv, "Pathname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
+ archive_string_free(&as);
}
/* Handle rest of common fields. */
err = r;
/* Handle POSIX ustar fields. */
- if (archive_entry_copy_uname_l(entry,
- header->uname, sizeof(header->uname), tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv, "Uname");
- if (err == ARCHIVE_FATAL)
- return (err);
- }
-
- if (archive_entry_copy_gname_l(entry,
- header->gname, sizeof(header->gname), tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv, "Gname");
- if (err == ARCHIVE_FATAL)
- return (err);
+ const char *existing_uname = archive_entry_uname(entry);
+ if (existing_uname == NULL || existing_uname[0] == '\0') {
+ if (archive_entry_copy_uname_l(entry,
+ header->uname, sizeof(header->uname), tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv, "Uname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
+ }
+
+ const char *existing_gname = archive_entry_gname(entry);
+ if (existing_gname == NULL || existing_gname[0] == '\0') {
+ if (archive_entry_copy_gname_l(entry,
+ header->gname, sizeof(header->gname), tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv, "Gname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
}
/* Parse out device numbers only for char and block specials. */
if (header->typeflag[0] == '3' || header->typeflag[0] == '4') {
- archive_entry_set_rdevmajor(entry, (dev_t)
- tar_atol(header->rdevmajor, sizeof(header->rdevmajor)));
- archive_entry_set_rdevminor(entry, (dev_t)
- tar_atol(header->rdevminor, sizeof(header->rdevminor)));
+ if (!archive_entry_rdev_is_set(entry)) {
+ archive_entry_set_rdevmajor(entry, (dev_t)
+ tar_atol(header->rdevmajor, sizeof(header->rdevmajor)));
+ archive_entry_set_rdevminor(entry, (dev_t)
+ tar_atol(header->rdevminor, sizeof(header->rdevminor)));
+ }
+ } else {
+ archive_entry_set_rdev(entry, 0);
}
tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining);
return (err);
}
-
-/*
- * Parse the pax extended attributes record.
- *
- * Returns non-zero if there's an error in the data.
- */
static int
-pax_header(struct archive_read *a, struct tar *tar,
- struct archive_entry *entry, struct archive_string *in_as)
+header_pax_extension(struct archive_read *a, struct tar *tar,
+ struct archive_entry *entry, const void *h, size_t *unconsumed)
{
- size_t attr_length, l, line_length, value_length;
- char *p;
- char *key, *value;
- struct archive_string *as;
+ /* Sanity checks: The largest `x` body I've ever heard of was
+ * a little over 4MB. So I doubt there has ever been a
+ * well-formed archive with an `x` body over 1GiB. Similarly,
+ * it seems plausible that no single attribute has ever been
+ * larger than 100MB. So if we see a larger value here, it's
+ * almost certainly a sign of a corrupted/malicious archive. */
+
+ /* Maximum sane size for extension body: 1 GiB */
+ /* This cannot be raised to larger than 8GiB without
+ * exceeding the maximum size for a standard ustar
+ * entry. */
+ const int64_t ext_size_limit = 1024 * 1024 * (int64_t)1024;
+ /* Maximum size for a single line/attr: 100 million characters */
+ /* This cannot be raised to more than 2GiB without exceeding
+ * a `size_t` on 32-bit platforms. */
+ const size_t max_parsed_line_length = 99999999ULL;
+ /* Largest attribute prolog: size + name. */
+ const size_t max_size_name = 512;
+
+ /* Size and padding of the full extension body */
+ int64_t ext_size, ext_padding;
+ size_t line_length, value_length, name_length;
+ ssize_t to_read, did_read;
+ const struct archive_entry_header_ustar *header;
+ const char *p, *attr_start, *name_start;
struct archive_string_conv *sconv;
- int err, err2;
- char *attr = in_as->s;
+ struct archive_string *pas = NULL;
+ struct archive_string attr_name;
+ int err = ARCHIVE_OK, r;
- attr_length = in_as->length;
- tar->pax_hdrcharset_binary = 0;
- archive_string_empty(&(tar->entry_gname));
- archive_string_empty(&(tar->entry_linkpath));
- archive_string_empty(&(tar->entry_pathname));
- archive_string_empty(&(tar->entry_pathname_override));
- archive_string_empty(&(tar->entry_uname));
- err = ARCHIVE_OK;
- while (attr_length > 0) {
- /* Parse decimal length field at start of line. */
+ header = (const struct archive_entry_header_ustar *)h;
+ ext_size = tar_atol(header->size, sizeof(header->size));
+ if (ext_size < 0) {
+ archive_set_error(&a->archive, EINVAL,
+ "pax extension header has invalid size: %lld",
+ (long long)ext_size);
+ return (ARCHIVE_FATAL);
+ }
+
+ ext_padding = 0x1ff & (-ext_size);
+ if (ext_size > ext_size_limit) {
+ /* Consume the pax extension body and return an error */
+ if (ext_size + ext_padding != __archive_read_consume(a, ext_size + ext_padding)) {
+ return (ARCHIVE_FATAL);
+ }
+ archive_set_error(&a->archive, EINVAL,
+ "Ignoring oversized pax extensions: %d > %d",
+ (int)ext_size, (int)ext_size_limit);
+ return (ARCHIVE_WARN);
+ }
+ tar_flush_unconsumed(a, unconsumed);
+
+ /* Parse the size/name of each pax attribute in the body */
+ archive_string_init(&attr_name);
+ while (ext_size > 0) {
+ /* Read enough bytes to parse the size/name of the next attribute */
+ to_read = max_size_name;
+ if (to_read > ext_size) {
+ to_read = ext_size;
+ }
+ p = __archive_read_ahead(a, to_read, &did_read);
+ if (did_read < 0) {
+ return ((int)did_read);
+ }
+ if (did_read == 0) { /* EOF */
+ archive_set_error(&a->archive, EINVAL,
+ "Truncated tar archive"
+ " detected while reading pax attribute name");
+ return (ARCHIVE_FATAL);
+ }
+ if (did_read > ext_size) {
+ did_read = ext_size;
+ }
+
+ /* Parse size of attribute */
line_length = 0;
- l = attr_length;
- p = attr; /* Record start of line. */
- while (l>0) {
+ attr_start = p;
+ while (1) {
+ if (p >= attr_start + did_read) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Ignoring malformed pax attributes: overlarge attribute size field");
+ *unconsumed += ext_size + ext_padding;
+ return (ARCHIVE_WARN);
+ }
if (*p == ' ') {
p++;
- l--;
break;
}
if (*p < '0' || *p > '9') {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Ignoring malformed pax extended attributes");
+ "Ignoring malformed pax attributes: malformed attribute size field");
+ *unconsumed += ext_size + ext_padding;
return (ARCHIVE_WARN);
}
line_length *= 10;
line_length += *p - '0';
- if (line_length > 999999) {
+ if (line_length > max_parsed_line_length) {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Rejecting pax extended attribute > 1MB");
+ "Ignoring malformed pax attribute: size > %lld",
+ (long long)max_parsed_line_length);
+ *unconsumed += ext_size + ext_padding;
return (ARCHIVE_WARN);
}
p++;
- l--;
}
- /*
- * Parsed length must be no bigger than available data,
- * at least 1, and the last character of the line must
- * be '\n'.
- */
- if (line_length > attr_length
- || line_length < 1
- || attr[line_length - 1] != '\n')
- {
- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Ignoring malformed pax extended attribute");
- return (ARCHIVE_WARN);
+ if ((int64_t)line_length > ext_size) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Ignoring malformed pax attribute: %lld > %lld",
+ (long long)line_length, (long long)ext_size);
+ *unconsumed += ext_size + ext_padding;
+ return (ARCHIVE_WARN);
}
- /* Null-terminate the line. */
- attr[line_length - 1] = '\0';
-
- /* Find end of key and null terminate it. */
- key = p;
- if (key[0] == '=')
- return (-1);
- while (*p && *p != '=')
- ++p;
- if (*p == '\0') {
+ /* Parse name of attribute */
+ if (p >= attr_start + did_read
+ || p >= attr_start + line_length
+ || *p == '=') {
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Invalid pax extended attributes");
+ "Ignoring malformed pax attributes: empty name found");
+ *unconsumed += ext_size + ext_padding;
return (ARCHIVE_WARN);
}
- *p = '\0';
+ name_start = p;
+ while (1) {
+ if (p >= attr_start + did_read || p >= attr_start + line_length) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Ignoring malformed pax attributes: overlarge attribute name");
+ *unconsumed += ext_size + ext_padding;
+ return (ARCHIVE_WARN);
+ }
+ if (*p == '=') {
+ break;
+ }
+ p++;
+ }
+ name_length = p - name_start;
+ p++; // Skip '='
- value = p + 1;
+ archive_strncpy(&attr_name, name_start, name_length);
- /* Some values may be binary data */
- value_length = attr + line_length - 1 - value;
+ ext_size -= p - attr_start;
+ value_length = line_length - (p - attr_start);
- /* Identify this attribute and set it in the entry. */
- err2 = pax_attribute(a, tar, entry, key, value, value_length);
- if (err2 == ARCHIVE_FATAL)
- return (err2);
- err = err_combine(err, err2);
+ /* Consume size, name, and `=` */
+ *unconsumed += p - attr_start;
+ tar_flush_unconsumed(a, unconsumed);
+
+ /* pax_attribute will consume value_length - 1 */
+ r = pax_attribute(a, tar, entry, attr_name.s, archive_strlen(&attr_name), value_length - 1, unconsumed);
+ ext_size -= value_length - 1;
+
+ if (r < ARCHIVE_WARN) {
+ *unconsumed += ext_size + ext_padding;
+ return (r);
+ }
+ err = err_combine(err, r);
- /* Skip to next line */
- attr += line_length;
- attr_length -= line_length;
+ /* Consume the `\n` that follows the pax attribute value. */
+ tar_flush_unconsumed(a, unconsumed);
+ p = __archive_read_ahead(a, 1, &did_read);
+ if (did_read < 0) {
+ return ((int)did_read);
+ }
+ if (did_read == 0) {
+ archive_set_error(&a->archive, EINVAL,
+ "Truncated tar archive"
+ " detected while completing pax attribute");
+ return (ARCHIVE_FATAL);
+ }
+ if (p[0] != '\n') {
+ archive_set_error(&a->archive, EINVAL,
+ "Malformed pax attributes");
+ *unconsumed += ext_size + ext_padding;
+ return (ARCHIVE_WARN);
+ }
+ ext_size -= 1;
+ *unconsumed += 1;
+ tar_flush_unconsumed(a, unconsumed);
}
+ *unconsumed += ext_size + ext_padding;
/*
- * PAX format uses UTF-8 as default charset for its metadata
- * unless hdrcharset=BINARY is present in its header.
- * We apply the charset specified by the hdrcharset option only
- * when the hdrcharset attribute(in PAX header) is BINARY because
- * we respect the charset described in PAX header and BINARY also
- * means that metadata(filename,uname and gname) character-set
- * is unknown.
+ * Some PAX values -- pathname, linkpath, uname, gname --
+ * can't be copied into the entry until we know the character
+ * set to use:
*/
- if (tar->pax_hdrcharset_binary)
+ if (!tar->pax_hdrcharset_utf8)
+ /* PAX specified "BINARY", so use the default charset */
sconv = tar->opt_sconv;
else {
+ /* PAX default UTF-8 */
sconv = archive_string_conversion_from_charset(
&(a->archive), "UTF-8", 1);
if (sconv == NULL)
SCONV_SET_OPT_UTF8_LIBARCHIVE2X);
}
+ /* Pathname */
+ pas = NULL;
+ if (archive_strlen(&(tar->entry_pathname_override)) > 0) {
+ /* Prefer GNU.sparse.name attribute if present */
+ /* GNU sparse files store a fake name under the standard
+ * "pathname" key. */
+ pas = &(tar->entry_pathname_override);
+ } else if (archive_strlen(&(tar->entry_pathname)) > 0) {
+ /* Use standard "pathname" PAX extension */
+ pas = &(tar->entry_pathname);
+ }
+ if (pas != NULL) {
+ if (archive_entry_copy_pathname_l(entry, pas->s,
+ archive_strlen(pas), sconv) != 0) {
+ err = set_conversion_failed_error(a, sconv, "Pathname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ /* Use raw name without conversion */
+ archive_entry_copy_pathname(entry, pas->s);
+ }
+ }
+ /* Uname */
+ if (archive_strlen(&(tar->entry_uname)) > 0) {
+ if (archive_entry_copy_uname_l(entry, tar->entry_uname.s,
+ archive_strlen(&(tar->entry_uname)), sconv) != 0) {
+ err = set_conversion_failed_error(a, sconv, "Uname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ /* Use raw name without conversion */
+ archive_entry_copy_uname(entry, tar->entry_uname.s);
+ }
+ }
+ /* Gname */
if (archive_strlen(&(tar->entry_gname)) > 0) {
if (archive_entry_copy_gname_l(entry, tar->entry_gname.s,
archive_strlen(&(tar->entry_gname)), sconv) != 0) {
err = set_conversion_failed_error(a, sconv, "Gname");
if (err == ARCHIVE_FATAL)
return (err);
- /* Use a converted an original name. */
+ /* Use raw name without conversion */
archive_entry_copy_gname(entry, tar->entry_gname.s);
}
}
+ /* Linkpath */
if (archive_strlen(&(tar->entry_linkpath)) > 0) {
if (archive_entry_copy_link_l(entry, tar->entry_linkpath.s,
archive_strlen(&(tar->entry_linkpath)), sconv) != 0) {
- err = set_conversion_failed_error(a, sconv, "Linkname");
+ err = set_conversion_failed_error(a, sconv, "Linkpath");
if (err == ARCHIVE_FATAL)
return (err);
- /* Use a converted an original name. */
+ /* Use raw name without conversion */
archive_entry_copy_link(entry, tar->entry_linkpath.s);
}
}
- /*
- * Some extensions (such as the GNU sparse file extensions)
- * deliberately store a synthetic name under the regular 'path'
- * attribute and the real file name under a different attribute.
- * Since we're supposed to not care about the order, we
- * have no choice but to store all of the various filenames
- * we find and figure it all out afterwards. This is the
- * figuring out part.
- */
- as = NULL;
- if (archive_strlen(&(tar->entry_pathname_override)) > 0)
- as = &(tar->entry_pathname_override);
- else if (archive_strlen(&(tar->entry_pathname)) > 0)
- as = &(tar->entry_pathname);
- if (as != NULL) {
- if (archive_entry_copy_pathname_l(entry, as->s,
- archive_strlen(as), sconv) != 0) {
- err = set_conversion_failed_error(a, sconv, "Pathname");
- if (err == ARCHIVE_FATAL)
- return (err);
- /* Use a converted an original name. */
- archive_entry_copy_pathname(entry, as->s);
- }
- }
- if (archive_strlen(&(tar->entry_uname)) > 0) {
- if (archive_entry_copy_uname_l(entry, tar->entry_uname.s,
- archive_strlen(&(tar->entry_uname)), sconv) != 0) {
- err = set_conversion_failed_error(a, sconv, "Uname");
- if (err == ARCHIVE_FATAL)
- return (err);
- /* Use a converted an original name. */
- archive_entry_copy_uname(entry, tar->entry_uname.s);
- }
- }
+
+ /* Extension may have given us a corrected `entry_bytes_remaining` for
+ * the main entry; update the padding appropriately. */
+ tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining);
return (err);
}
static int
-pax_attribute_xattr(struct archive_entry *entry,
- const char *name, const char *value)
+pax_attribute_LIBARCHIVE_xattr(struct archive_entry *entry,
+ const char *name, size_t name_length, const char *value, size_t value_length)
{
char *name_decoded;
void *value_decoded;
size_t value_len;
- if (strlen(name) < 18 || (memcmp(name, "LIBARCHIVE.xattr.", 17)) != 0)
+ if (name_length < 1)
return 3;
- name += 17;
-
/* URL-decode name */
- name_decoded = url_decode(name);
+ name_decoded = url_decode(name, name_length);
if (name_decoded == NULL)
return 2;
/* Base-64 decode value */
- value_decoded = base64_decode(value, strlen(value), &value_len);
+ value_decoded = base64_decode(value, value_length, &value_len);
if (value_decoded == NULL) {
free(name_decoded);
return 1;
}
static int
-pax_attribute_schily_xattr(struct archive_entry *entry,
- const char *name, const char *value, size_t value_length)
+pax_attribute_SCHILY_xattr(struct archive_entry *entry,
+ const char *name, size_t name_length, const char *value, size_t value_length)
{
- if (strlen(name) < 14 || (memcmp(name, "SCHILY.xattr.", 13)) != 0)
+ if (name_length < 1 || name_length > 128) {
return 1;
+ }
- name += 13;
-
- archive_entry_xattr_add_entry(entry, name, value, value_length);
+ char * null_terminated_name = malloc(name_length + 1);
+ if (null_terminated_name != NULL) {
+ memcpy(null_terminated_name, name, name_length);
+ null_terminated_name[name_length] = '\0';
+ archive_entry_xattr_add_entry(entry, null_terminated_name, value, value_length);
+ free(null_terminated_name);
+ }
return 0;
}
static int
-pax_attribute_rht_security_selinux(struct archive_entry *entry,
+pax_attribute_RHT_security_selinux(struct archive_entry *entry,
const char *value, size_t value_length)
{
archive_entry_xattr_add_entry(entry, "security.selinux",
}
static int
-pax_attribute_acl(struct archive_read *a, struct tar *tar,
- struct archive_entry *entry, const char *value, int type)
+pax_attribute_SCHILY_acl(struct archive_read *a, struct tar *tar,
+ struct archive_entry *entry, size_t value_length, int type)
{
int r;
+ const char *p;
const char* errstr;
switch (type) {
return (ARCHIVE_FATAL);
}
- r = archive_acl_from_text_l(archive_entry_acl(entry), value, type,
- tar->sconv_acl);
+ if (value_length > acl_limit) {
+ __archive_read_consume(a, value_length);
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Unreasonably large ACL: %d > %d",
+ (int)value_length, (int)acl_limit);
+ return (ARCHIVE_WARN);
+ }
+
+ p = __archive_read_ahead(a, value_length, NULL);
+ if (p == NULL) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+ "Truncated tar archive "
+ "detected while reading ACL data");
+ return (ARCHIVE_FATAL);
+ }
+
+ r = archive_acl_from_text_nl(archive_entry_acl(entry), p, value_length,
+ type, tar->sconv_acl);
+ __archive_read_consume(a, value_length);
+ /* Workaround: Force perm_is_set() to be correct */
+ /* If this bit were stored in the ACL, this wouldn't be needed */
+ archive_entry_set_perm(entry, archive_entry_perm(entry));
if (r != ARCHIVE_OK) {
if (r == ARCHIVE_FATAL) {
archive_set_error(&a->archive, ENOMEM,
return (r);
}
+static int
+pax_attribute_read_time(struct archive_read *a, size_t value_length, int64_t *ps, long *pn, size_t *unconsumed) {
+ struct archive_string as;
+ int r;
+
+ if (value_length > 128) {
+ __archive_read_consume(a, value_length);
+ *ps = 0;
+ *pn = 0;
+ return (ARCHIVE_FATAL);
+ }
+
+ archive_string_init(&as);
+ r = read_bytes_to_string(a, &as, value_length, unconsumed);
+ if (r < ARCHIVE_OK) {
+ return (r);
+ }
+
+ pax_time(as.s, archive_strlen(&as), ps, pn);
+ if (*ps < 0 || *ps == INT64_MAX) {
+ return (ARCHIVE_WARN);
+ }
+ return (ARCHIVE_OK);
+}
+
+static int
+pax_attribute_read_number(struct archive_read *a, size_t value_length, int64_t *result) {
+ struct archive_string as;
+ size_t unconsumed = 0;
+ int r;
+
+ if (value_length > 64) {
+ __archive_read_consume(a, value_length);
+ *result = 0;
+ return (ARCHIVE_FATAL);
+ }
+
+ archive_string_init(&as);
+ r = read_bytes_to_string(a, &as, value_length, &unconsumed);
+ tar_flush_unconsumed(a, &unconsumed);
+ if (r < ARCHIVE_OK) {
+ return (r);
+ }
+
+ *result = tar_atol10(as.s, archive_strlen(&as));
+ if (*result < 0 || *result == INT64_MAX) {
+ *result = INT64_MAX;
+ return (ARCHIVE_WARN);
+ }
+ return (ARCHIVE_OK);
+}
+
/*
- * Parse a single key=value attribute. key/value pointers are
- * assumed to point into reasonably long-lived storage.
+ * Parse a single key=value attribute.
*
- * Note that POSIX reserves all-lowercase keywords. Vendor-specific
- * extensions should always have keywords of the form "VENDOR.attribute"
- * In particular, it's quite feasible to support many different
- * vendor extensions here. I'm using "LIBARCHIVE" for extensions
- * unique to this library.
+ * POSIX reserves all-lowercase keywords. Vendor-specific extensions
+ * should always have keywords of the form "VENDOR.attribute" In
+ * particular, it's quite feasible to support many different vendor
+ * extensions here. I'm using "LIBARCHIVE" for extensions unique to
+ * this library.
*
- * Investigate other vendor-specific extensions and see if
+ * TODO: Investigate other vendor-specific extensions and see if
* any of them look useful.
*/
static int
-pax_attribute(struct archive_read *a, struct tar *tar,
- struct archive_entry *entry, const char *key, const char *value, size_t value_length)
+pax_attribute(struct archive_read *a, struct tar *tar, struct archive_entry *entry,
+ const char *key, size_t key_length, size_t value_length, size_t *unconsumed)
{
- int64_t s;
+ int64_t t;
long n;
- int err = ARCHIVE_OK, r;
+ const char *p;
+ ssize_t bytes_read;
+ int err = ARCHIVE_OK;
- if (value == NULL)
- value = ""; /* Disable compiler warning; do not pass
- * NULL pointer to strlen(). */
switch (key[0]) {
case 'G':
- /* Reject GNU.sparse.* headers on non-regular files. */
- if (strncmp(key, "GNU.sparse", 10) == 0 &&
- !tar->sparse_allowed) {
- archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
- "Non-regular file cannot be sparse");
- return (ARCHIVE_FATAL);
- }
-
- /* GNU "0.0" sparse pax format. */
- if (strcmp(key, "GNU.sparse.numblocks") == 0) {
- tar->sparse_offset = -1;
- tar->sparse_numbytes = -1;
- tar->sparse_gnu_major = 0;
- tar->sparse_gnu_minor = 0;
- }
- if (strcmp(key, "GNU.sparse.offset") == 0) {
- tar->sparse_offset = tar_atol10(value, strlen(value));
- if (tar->sparse_numbytes != -1) {
- if (gnu_add_sparse_entry(a, tar,
- tar->sparse_offset, tar->sparse_numbytes)
- != ARCHIVE_OK)
- return (ARCHIVE_FATAL);
- tar->sparse_offset = -1;
- tar->sparse_numbytes = -1;
- }
- }
- if (strcmp(key, "GNU.sparse.numbytes") == 0) {
- tar->sparse_numbytes = tar_atol10(value, strlen(value));
- if (tar->sparse_offset != -1) {
- if (gnu_add_sparse_entry(a, tar,
- tar->sparse_offset, tar->sparse_numbytes)
- != ARCHIVE_OK)
- return (ARCHIVE_FATAL);
- tar->sparse_offset = -1;
- tar->sparse_numbytes = -1;
+ /* GNU.* extensions */
+ if (key_length > 4 && memcmp(key, "GNU.", 4) == 0) {
+ key += 4;
+ key_length -= 4;
+
+ /* GNU.sparse marks the existence of GNU sparse information */
+ if (key_length == 6 && memcmp(key, "sparse", 6) == 0) {
+ tar->sparse_gnu_attributes_seen = 1;
}
- }
- if (strcmp(key, "GNU.sparse.size") == 0) {
- tar->realsize = tar_atol10(value, strlen(value));
- archive_entry_set_size(entry, tar->realsize);
- tar->realsize_override = 1;
- }
-
- /* GNU "0.1" sparse pax format. */
- if (strcmp(key, "GNU.sparse.map") == 0) {
- tar->sparse_gnu_major = 0;
- tar->sparse_gnu_minor = 1;
- if (gnu_sparse_01_parse(a, tar, value) != ARCHIVE_OK)
- return (ARCHIVE_WARN);
- }
- /* GNU "1.0" sparse pax format */
- if (strcmp(key, "GNU.sparse.major") == 0) {
- tar->sparse_gnu_major = (int)tar_atol10(value, strlen(value));
- tar->sparse_gnu_pending = 1;
- }
- if (strcmp(key, "GNU.sparse.minor") == 0) {
- tar->sparse_gnu_minor = (int)tar_atol10(value, strlen(value));
- tar->sparse_gnu_pending = 1;
- }
- if (strcmp(key, "GNU.sparse.name") == 0) {
- /*
- * The real filename; when storing sparse
- * files, GNU tar puts a synthesized name into
- * the regular 'path' attribute in an attempt
- * to limit confusion. ;-)
- */
- archive_strcpy(&(tar->entry_pathname_override), value);
- }
- if (strcmp(key, "GNU.sparse.realsize") == 0) {
- tar->realsize = tar_atol10(value, strlen(value));
- archive_entry_set_size(entry, tar->realsize);
- tar->realsize_override = 1;
+ /* GNU.sparse.* extensions */
+ else if (key_length > 7 && memcmp(key, "sparse.", 7) == 0) {
+ tar->sparse_gnu_attributes_seen = 1;
+ key += 7;
+ key_length -= 7;
+
+ /* GNU "0.0" sparse pax format. */
+ if (key_length == 9 && memcmp(key, "numblocks", 9) == 0) {
+ /* GNU.sparse.numblocks */
+ tar->sparse_offset = -1;
+ tar->sparse_numbytes = -1;
+ tar->sparse_gnu_major = 0;
+ tar->sparse_gnu_minor = 0;
+ }
+ else if (key_length == 6 && memcmp(key, "offset", 6) == 0) {
+ /* GNU.sparse.offset */
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ tar->sparse_offset = t;
+ if (tar->sparse_numbytes != -1) {
+ if (gnu_add_sparse_entry(a, tar,
+ tar->sparse_offset, tar->sparse_numbytes)
+ != ARCHIVE_OK)
+ return (ARCHIVE_FATAL);
+ tar->sparse_offset = -1;
+ tar->sparse_numbytes = -1;
+ }
+ }
+ return (err);
+ }
+ else if (key_length == 8 && memcmp(key, "numbytes", 8) == 0) {
+ /* GNU.sparse.numbytes */
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ tar->sparse_numbytes = t;
+ if (tar->sparse_offset != -1) {
+ if (gnu_add_sparse_entry(a, tar,
+ tar->sparse_offset, tar->sparse_numbytes)
+ != ARCHIVE_OK)
+ return (ARCHIVE_FATAL);
+ tar->sparse_offset = -1;
+ tar->sparse_numbytes = -1;
+ }
+ }
+ return (err);
+ }
+ else if (key_length == 4 && memcmp(key, "size", 4) == 0) {
+ /* GNU.sparse.size */
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ tar->realsize = t;
+ archive_entry_set_size(entry, tar->realsize);
+ tar->realsize_override = 1;
+ }
+ return (err);
+ }
+
+ /* GNU "0.1" sparse pax format. */
+ else if (key_length == 3 && memcmp(key, "map", 3) == 0) {
+ /* GNU.sparse.map */
+ tar->sparse_gnu_major = 0;
+ tar->sparse_gnu_minor = 1;
+ if (value_length > sparse_map_limit) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Unreasonably large sparse map: %d > %d",
+ (int)value_length, (int)sparse_map_limit);
+ err = ARCHIVE_FAILED;
+ } else {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p != NULL) {
+ if (gnu_sparse_01_parse(a, tar, p, value_length) != ARCHIVE_OK) {
+ err = ARCHIVE_WARN;
+ }
+ } else {
+ return (ARCHIVE_FATAL);
+ }
+ }
+ __archive_read_consume(a, value_length);
+ return (err);
+ }
+
+ /* GNU "1.0" sparse pax format */
+ else if (key_length == 5 && memcmp(key, "major", 5) == 0) {
+ /* GNU.sparse.major */
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK
+ && t >= 0
+ && t <= 10) {
+ tar->sparse_gnu_major = (int)t;
+ }
+ return (err);
+ }
+ else if (key_length == 5 && memcmp(key, "minor", 5) == 0) {
+ /* GNU.sparse.minor */
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK
+ && t >= 0
+ && t <= 10) {
+ tar->sparse_gnu_minor = (int)t;
+ }
+ return (err);
+ }
+ else if (key_length == 4 && memcmp(key, "name", 4) == 0) {
+ /* GNU.sparse.name */
+ /*
+ * The real filename; when storing sparse
+ * files, GNU tar puts a synthesized name into
+ * the regular 'path' attribute in an attempt
+ * to limit confusion. ;-)
+ */
+ if (value_length > pathname_limit) {
+ *unconsumed += value_length;
+ err = ARCHIVE_WARN;
+ } else {
+ err = read_bytes_to_string(a, &(tar->entry_pathname_override),
+ value_length, unconsumed);
+ }
+ return (err);
+ }
+ else if (key_length == 8 && memcmp(key, "realsize", 8) == 0) {
+ /* GNU.sparse.realsize */
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ tar->realsize = t;
+ archive_entry_set_size(entry, tar->realsize);
+ tar->realsize_override = 1;
+ }
+ return (err);
+ }
+ }
}
break;
case 'L':
- /* Our extensions */
-/* TODO: Handle arbitrary extended attributes... */
-/*
- if (strcmp(key, "LIBARCHIVE.xxxxxxx") == 0)
- archive_entry_set_xxxxxx(entry, value);
-*/
- if (strcmp(key, "LIBARCHIVE.creationtime") == 0) {
- pax_time(value, &s, &n);
- archive_entry_set_birthtime(entry, s, n);
- }
- if (strcmp(key, "LIBARCHIVE.symlinktype") == 0) {
- if (strcmp(value, "file") == 0) {
- archive_entry_set_symlink_type(entry,
- AE_SYMLINK_TYPE_FILE);
- } else if (strcmp(value, "dir") == 0) {
- archive_entry_set_symlink_type(entry,
- AE_SYMLINK_TYPE_DIRECTORY);
+ /* LIBARCHIVE extensions */
+ if (key_length > 11 && memcmp(key, "LIBARCHIVE.", 11) == 0) {
+ key_length -= 11;
+ key += 11;
+
+ /* TODO: Handle arbitrary extended attributes... */
+ /*
+ if (strcmp(key, "LIBARCHIVE.xxxxxxx") == 0)
+ archive_entry_set_xxxxxx(entry, value);
+ */
+ if (key_length == 12 && memcmp(key, "creationtime", 12) == 0) {
+ /* LIBARCHIVE.creationtime */
+ if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_birthtime(entry, t, n);
+ }
+ return (err);
+ }
+ else if (key_length == 11 && memcmp(key, "symlinktype", 11) == 0) {
+ /* LIBARCHIVE.symlinktype */
+ if (value_length < 16) {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p != NULL) {
+ if (value_length == 4 && memcmp(p, "file", 4) == 0) {
+ archive_entry_set_symlink_type(entry,
+ AE_SYMLINK_TYPE_FILE);
+ } else if (value_length == 3 && memcmp(p, "dir", 3) == 0) {
+ archive_entry_set_symlink_type(entry,
+ AE_SYMLINK_TYPE_DIRECTORY);
+ } else {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Unrecognized symlink type");
+ err = ARCHIVE_WARN;
+ }
+ } else {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+ "Truncated tar archive "
+ "detected while reading `symlinktype` attribute");
+ return (ARCHIVE_FATAL);
+ }
+ } else {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "symlink type is very long"
+ "(longest recognized value is 4 bytes, this is %d)",
+ (int)value_length);
+ err = ARCHIVE_WARN;
+ }
+ __archive_read_consume(a, value_length);
+ return (err);
+ }
+ else if (key_length > 6 && memcmp(key, "xattr.", 6) == 0) {
+ key_length -= 6;
+ key += 6;
+ if (value_length > xattr_limit) {
+ err = ARCHIVE_WARN;
+ } else {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p == NULL
+ || pax_attribute_LIBARCHIVE_xattr(entry, key, key_length, p, value_length)) {
+ /* TODO: Unable to parse xattr */
+ err = ARCHIVE_WARN;
+ }
+ }
+ __archive_read_consume(a, value_length);
+ return (err);
}
}
- if (memcmp(key, "LIBARCHIVE.xattr.", 17) == 0)
- pax_attribute_xattr(entry, key, value);
break;
case 'R':
/* GNU tar uses RHT.security header to store SELinux xattrs
* SCHILY.xattr.security.selinux == RHT.security.selinux */
- if (strcmp(key, "RHT.security.selinux") == 0) {
- pax_attribute_rht_security_selinux(entry, value,
- value_length);
+ if (key_length == 20 && memcmp(key, "RHT.security.selinux", 20) == 0) {
+ if (value_length > xattr_limit) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Ignoring unreasonably large security.selinux attribute:"
+ " %d > %d",
+ (int)value_length, (int)xattr_limit);
+ /* TODO: Should this be FAILED instead? */
+ err = ARCHIVE_WARN;
+ } else {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p == NULL
+ || pax_attribute_RHT_security_selinux(entry, p, value_length)) {
+ /* TODO: Unable to parse xattr */
+ err = ARCHIVE_WARN;
+ }
}
+ __archive_read_consume(a, value_length);
+ return (err);
+ }
break;
case 'S':
- /* We support some keys used by the "star" archiver */
- if (strcmp(key, "SCHILY.acl.access") == 0) {
- r = pax_attribute_acl(a, tar, entry, value,
- ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
- if (r == ARCHIVE_FATAL)
- return (r);
- } else if (strcmp(key, "SCHILY.acl.default") == 0) {
- r = pax_attribute_acl(a, tar, entry, value,
- ARCHIVE_ENTRY_ACL_TYPE_DEFAULT);
- if (r == ARCHIVE_FATAL)
- return (r);
- } else if (strcmp(key, "SCHILY.acl.ace") == 0) {
- r = pax_attribute_acl(a, tar, entry, value,
- ARCHIVE_ENTRY_ACL_TYPE_NFS4);
- if (r == ARCHIVE_FATAL)
- return (r);
- } else if (strcmp(key, "SCHILY.devmajor") == 0) {
- archive_entry_set_rdevmajor(entry,
- (dev_t)tar_atol10(value, strlen(value)));
- } else if (strcmp(key, "SCHILY.devminor") == 0) {
- archive_entry_set_rdevminor(entry,
- (dev_t)tar_atol10(value, strlen(value)));
- } else if (strcmp(key, "SCHILY.fflags") == 0) {
- archive_entry_copy_fflags_text(entry, value);
- } else if (strcmp(key, "SCHILY.dev") == 0) {
- archive_entry_set_dev(entry,
- (dev_t)tar_atol10(value, strlen(value)));
- } else if (strcmp(key, "SCHILY.ino") == 0) {
- archive_entry_set_ino(entry,
- tar_atol10(value, strlen(value)));
- } else if (strcmp(key, "SCHILY.nlink") == 0) {
- archive_entry_set_nlink(entry, (unsigned)
- tar_atol10(value, strlen(value)));
- } else if (strcmp(key, "SCHILY.realsize") == 0) {
- tar->realsize = tar_atol10(value, strlen(value));
- tar->realsize_override = 1;
- archive_entry_set_size(entry, tar->realsize);
- } else if (strncmp(key, "SCHILY.xattr.", 13) == 0) {
- pax_attribute_schily_xattr(entry, key, value,
- value_length);
- } else if (strcmp(key, "SUN.holesdata") == 0) {
- /* A Solaris extension for sparse. */
- r = solaris_sparse_parse(a, tar, entry, value);
- if (r < err) {
- if (r == ARCHIVE_FATAL)
- return (r);
- err = r;
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_MISC,
- "Parse error: SUN.holesdata");
+ /* SCHILY.* extensions used by "star" archiver */
+ if (key_length > 7 && memcmp(key, "SCHILY.", 7) == 0) {
+ key_length -= 7;
+ key += 7;
+
+ if (key_length == 10 && memcmp(key, "acl.access", 10) == 0) {
+ err = pax_attribute_SCHILY_acl(a, tar, entry, value_length,
+ ARCHIVE_ENTRY_ACL_TYPE_ACCESS);
+ // TODO: Mark mode as set
+ return (err);
+ }
+ else if (key_length == 11 && memcmp(key, "acl.default", 11) == 0) {
+ err = pax_attribute_SCHILY_acl(a, tar, entry, value_length,
+ ARCHIVE_ENTRY_ACL_TYPE_DEFAULT);
+ return (err);
+ }
+ else if (key_length == 7 && memcmp(key, "acl.ace", 7) == 0) {
+ err = pax_attribute_SCHILY_acl(a, tar, entry, value_length,
+ ARCHIVE_ENTRY_ACL_TYPE_NFS4);
+ // TODO: Mark mode as set
+ return (err);
+ }
+ else if (key_length == 8 && memcmp(key, "devmajor", 8) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ archive_entry_set_rdevmajor(entry, t);
+ }
+ return (err);
+ }
+ else if (key_length == 8 && memcmp(key, "devminor", 8) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ archive_entry_set_rdevminor(entry, t);
+ }
+ return (err);
+ }
+ else if (key_length == 6 && memcmp(key, "fflags", 6) == 0) {
+ if (value_length < fflags_limit) {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p != NULL) {
+ archive_entry_copy_fflags_text_len(entry, p, value_length);
+ err = ARCHIVE_OK;
+ } else {
+ /* Truncated archive */
+ err = ARCHIVE_FATAL;
+ }
+ } else {
+ /* Overlong fflags field */
+ err = ARCHIVE_WARN;
+ }
+ __archive_read_consume(a, value_length);
+ return (err);
+ }
+ else if (key_length == 3 && memcmp(key, "dev", 3) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ archive_entry_set_dev(entry, t);
+ }
+ return (err);
+ }
+ else if (key_length == 3 && memcmp(key, "ino", 3) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ archive_entry_set_ino(entry, t);
+ }
+ return (err);
+ }
+ else if (key_length == 5 && memcmp(key, "nlink", 5) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ archive_entry_set_nlink(entry, t);
+ }
+ return (err);
+ }
+ else if (key_length == 8 && memcmp(key, "realsize", 8) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ tar->realsize = t;
+ tar->realsize_override = 1;
+ archive_entry_set_size(entry, tar->realsize);
+ }
+ return (err);
+ }
+ else if (key_length > 6 && memcmp(key, "xattr.", 6) == 0) {
+ key_length -= 6;
+ key += 6;
+ if (value_length < xattr_limit) {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p == NULL
+ || pax_attribute_SCHILY_xattr(entry, key, key_length, p, value_length)) {
+ /* TODO: Unable to parse xattr */
+ err = ARCHIVE_WARN;
+ }
+ } else {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Unreasonably large xattr: %d > %d",
+ (int)value_length, (int)xattr_limit);
+ err = ARCHIVE_WARN;
+ }
+ __archive_read_consume(a, value_length);
+ return (err);
+ }
+ }
+ /* SUN.* extensions from Solaris tar */
+ if (key_length > 4 && memcmp(key, "SUN.", 4) == 0) {
+ key_length -= 4;
+ key += 4;
+
+ if (key_length == 9 && memcmp(key, "holesdata", 9) == 0) {
+ /* SUN.holesdata */
+ if (value_length < sparse_map_limit) {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p != NULL) {
+ err = pax_attribute_SUN_holesdata(a, tar, entry, p, value_length);
+ if (err < ARCHIVE_OK) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_MISC,
+ "Parse error: SUN.holesdata");
+ }
+ } else {
+ return (ARCHIVE_FATAL);
+ }
+ } else {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
+ "Unreasonably large sparse map: %d > %d",
+ (int)value_length, (int)sparse_map_limit);
+ err = ARCHIVE_FAILED;
+ }
+ __archive_read_consume(a, value_length);
+ return (err);
}
}
break;
case 'a':
- if (strcmp(key, "atime") == 0) {
- pax_time(value, &s, &n);
- archive_entry_set_atime(entry, s, n);
+ if (key_length == 5 && memcmp(key, "atime", 5) == 0) {
+ if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_atime(entry, t, n);
+ }
+ return (err);
}
break;
case 'c':
- if (strcmp(key, "ctime") == 0) {
- pax_time(value, &s, &n);
- archive_entry_set_ctime(entry, s, n);
- } else if (strcmp(key, "charset") == 0) {
+ if (key_length == 5 && memcmp(key, "ctime", 5) == 0) {
+ if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_ctime(entry, t, n);
+ }
+ return (err);
+ } else if (key_length == 7 && memcmp(key, "charset", 7) == 0) {
/* TODO: Publish charset information in entry. */
- } else if (strcmp(key, "comment") == 0) {
+ } else if (key_length == 7 && memcmp(key, "comment", 7) == 0) {
/* TODO: Publish comment in entry. */
}
break;
case 'g':
- if (strcmp(key, "gid") == 0) {
- archive_entry_set_gid(entry,
- tar_atol10(value, strlen(value)));
- } else if (strcmp(key, "gname") == 0) {
- archive_strcpy(&(tar->entry_gname), value);
+ if (key_length == 3 && memcmp(key, "gid", 3) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ archive_entry_set_gid(entry, t);
+ }
+ return (err);
+ } else if (key_length == 5 && memcmp(key, "gname", 5) == 0) {
+ if (value_length > guname_limit) {
+ *unconsumed += value_length;
+ err = ARCHIVE_WARN;
+ } else {
+ err = read_bytes_to_string(a, &(tar->entry_gname), value_length, unconsumed);
+ }
+ return (err);
}
break;
case 'h':
- if (strcmp(key, "hdrcharset") == 0) {
- if (strcmp(value, "BINARY") == 0)
- /* Binary mode. */
- tar->pax_hdrcharset_binary = 1;
- else if (strcmp(value, "ISO-IR 10646 2000 UTF-8") == 0)
- tar->pax_hdrcharset_binary = 0;
+ if (key_length == 10 && memcmp(key, "hdrcharset", 10) == 0) {
+ if (value_length < 64) {
+ p = __archive_read_ahead(a, value_length, &bytes_read);
+ if (p != NULL) {
+ if (value_length == 6
+ && memcmp(p, "BINARY", 6) == 0) {
+ /* Binary mode. */
+ tar->pax_hdrcharset_utf8 = 0;
+ err = ARCHIVE_OK;
+ } else if (value_length == 23
+ && memcmp(p, "ISO-IR 10646 2000 UTF-8", 23) == 0) {
+ tar->pax_hdrcharset_utf8 = 1;
+ err = ARCHIVE_OK;
+ } else {
+ /* TODO: Unrecognized character set */
+ err = ARCHIVE_WARN;
+ }
+ } else {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+ "Truncated tar archive "
+ "detected while reading hdrcharset attribute");
+ return (ARCHIVE_FATAL);
+ }
+ } else {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+ "hdrcharset attribute is unreasonably large (%d bytes)",
+ (int)value_length);
+ err = ARCHIVE_WARN;
+ }
+ __archive_read_consume(a, value_length);
+ return (err);
}
break;
case 'l':
/* pax interchange doesn't distinguish hardlink vs. symlink. */
- if (strcmp(key, "linkpath") == 0) {
- archive_strcpy(&(tar->entry_linkpath), value);
+ if (key_length == 8 && memcmp(key, "linkpath", 8) == 0) {
+ if (value_length > pathname_limit) {
+ *unconsumed += value_length;
+ err = ARCHIVE_WARN;
+ } else {
+ err = read_bytes_to_string(a, &tar->entry_linkpath, value_length, unconsumed);
+ }
+ return (err);
}
break;
case 'm':
- if (strcmp(key, "mtime") == 0) {
- pax_time(value, &s, &n);
- archive_entry_set_mtime(entry, s, n);
+ if (key_length == 5 && memcmp(key, "mtime", 5) == 0) {
+ if ((err = pax_attribute_read_time(a, value_length, &t, &n, unconsumed)) == ARCHIVE_OK) {
+ archive_entry_set_mtime(entry, t, n);
+ }
+ return (err);
}
break;
case 'p':
- if (strcmp(key, "path") == 0) {
- archive_strcpy(&(tar->entry_pathname), value);
+ if (key_length == 4 && memcmp(key, "path", 4) == 0) {
+ if (value_length > pathname_limit) {
+ *unconsumed += value_length;
+ err = ARCHIVE_WARN;
+ } else {
+ err = read_bytes_to_string(a, &(tar->entry_pathname), value_length, unconsumed);
+ }
+ return (err);
}
break;
case 'r':
case 's':
/* POSIX has reserved 'security.*' */
/* Someday: if (strcmp(key, "security.acl") == 0) { ... } */
- if (strcmp(key, "size") == 0) {
+ if (key_length == 4 && memcmp(key, "size", 4) == 0) {
/* "size" is the size of the data in the entry. */
- tar->entry_bytes_remaining
- = tar_atol10(value, strlen(value));
- if (tar->entry_bytes_remaining < 0) {
- tar->entry_bytes_remaining = 0;
- archive_set_error(&a->archive,
- ARCHIVE_ERRNO_MISC,
- "Tar size attribute is negative");
- return (ARCHIVE_FATAL);
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ tar->entry_bytes_remaining = t;
+ /*
+ * The "size" pax header keyword always overrides the
+ * "size" field in the tar header.
+ * GNU.sparse.realsize, GNU.sparse.size and
+ * SCHILY.realsize override this value.
+ */
+ if (!tar->realsize_override) {
+ archive_entry_set_size(entry,
+ tar->entry_bytes_remaining);
+ tar->realsize
+ = tar->entry_bytes_remaining;
+ }
}
- if (tar->entry_bytes_remaining == INT64_MAX) {
- /* Note: tar_atol returns INT64_MAX on overflow */
+ else if (t == INT64_MAX) {
+ /* Note: pax_attr_read_number returns INT64_MAX on overflow or < 0 */
tar->entry_bytes_remaining = 0;
archive_set_error(&a->archive,
ARCHIVE_ERRNO_MISC,
"Tar size attribute overflow");
return (ARCHIVE_FATAL);
}
- /*
- * The "size" pax header keyword always overrides the
- * "size" field in the tar header.
- * GNU.sparse.realsize, GNU.sparse.size and
- * SCHILY.realsize override this value.
- */
- if (!tar->realsize_override) {
- archive_entry_set_size(entry,
- tar->entry_bytes_remaining);
- tar->realsize
- = tar->entry_bytes_remaining;
- }
+ return (err);
}
break;
case 'u':
- if (strcmp(key, "uid") == 0) {
- archive_entry_set_uid(entry,
- tar_atol10(value, strlen(value)));
- } else if (strcmp(key, "uname") == 0) {
- archive_strcpy(&(tar->entry_uname), value);
+ if (key_length == 3 && memcmp(key, "uid", 3) == 0) {
+ if ((err = pax_attribute_read_number(a, value_length, &t)) == ARCHIVE_OK) {
+ archive_entry_set_uid(entry, t);
+ }
+ return (err);
+ } else if (key_length == 5 && memcmp(key, "uname", 5) == 0) {
+ if (value_length > guname_limit) {
+ *unconsumed += value_length;
+ err = ARCHIVE_WARN;
+ } else {
+ err = read_bytes_to_string(a, &(tar->entry_uname), value_length, unconsumed);
+ }
+ return (err);
}
break;
}
+
+ /* Unrecognized key, just skip the entire value. */
+ __archive_read_consume(a, value_length);
return (err);
}
* parse a decimal time value, which may include a fractional portion
*/
static void
-pax_time(const char *p, int64_t *ps, long *pn)
+pax_time(const char *p, size_t length, int64_t *ps, long *pn)
{
char digit;
int64_t s;
limit = INT64_MAX / 10;
last_digit_limit = INT64_MAX % 10;
+ if (length <= 0) {
+ *ps = 0;
+ return;
+ }
s = 0;
sign = 1;
if (*p == '-') {
sign = -1;
p++;
+ length--;
}
- while (*p >= '0' && *p <= '9') {
+ while (length > 0 && *p >= '0' && *p <= '9') {
digit = *p - '0';
if (s > limit ||
(s == limit && digit > last_digit_limit)) {
}
s = (s * 10) + digit;
++p;
+ --length;
}
*ps = s * sign;
/* Calculate nanoseconds. */
*pn = 0;
- if (*p != '.')
+ if (length <= 0 || *p != '.')
return;
l = 100000000UL;
do {
++p;
- if (*p >= '0' && *p <= '9')
+ --length;
+ if (length > 0 && *p >= '0' && *p <= '9')
*pn += (*p - '0') * l;
else
break;
/* Copy filename over (to ensure null termination). */
header = (const struct archive_entry_header_gnutar *)h;
- if (archive_entry_copy_pathname_l(entry,
- header->name, sizeof(header->name), tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv, "Pathname");
- if (err == ARCHIVE_FATAL)
- return (err);
+ const char *existing_pathname = archive_entry_pathname(entry);
+ if (existing_pathname == NULL || existing_pathname[0] == '\0') {
+ if (archive_entry_copy_pathname_l(entry,
+ header->name, sizeof(header->name), tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv, "Pathname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
}
/* Fields common to ustar and GNU */
/* XXX Can the following be factored out since it's common
* to ustar and gnu tar? Is it okay to move it down into
* header_common, perhaps? */
- if (archive_entry_copy_uname_l(entry,
- header->uname, sizeof(header->uname), tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv, "Uname");
- if (err == ARCHIVE_FATAL)
- return (err);
+ const char *existing_uname = archive_entry_uname(entry);
+ if (existing_uname == NULL || existing_uname[0] == '\0') {
+ if (archive_entry_copy_uname_l(entry,
+ header->uname, sizeof(header->uname), tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv, "Uname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
}
- if (archive_entry_copy_gname_l(entry,
- header->gname, sizeof(header->gname), tar->sconv) != 0) {
- err = set_conversion_failed_error(a, tar->sconv, "Gname");
- if (err == ARCHIVE_FATAL)
- return (err);
+ const char *existing_gname = archive_entry_gname(entry);
+ if (existing_gname == NULL || existing_gname[0] == '\0') {
+ if (archive_entry_copy_gname_l(entry,
+ header->gname, sizeof(header->gname), tar->sconv) != 0) {
+ err = set_conversion_failed_error(a, tar->sconv, "Gname");
+ if (err == ARCHIVE_FATAL)
+ return (err);
+ }
}
/* Parse out device numbers only for char and block specials */
if (header->typeflag[0] == '3' || header->typeflag[0] == '4') {
- archive_entry_set_rdevmajor(entry, (dev_t)
- tar_atol(header->rdevmajor, sizeof(header->rdevmajor)));
- archive_entry_set_rdevminor(entry, (dev_t)
- tar_atol(header->rdevminor, sizeof(header->rdevminor)));
- } else
+ if (!archive_entry_rdev_is_set(entry)) {
+ archive_entry_set_rdevmajor(entry, (dev_t)
+ tar_atol(header->rdevmajor, sizeof(header->rdevmajor)));
+ archive_entry_set_rdevminor(entry, (dev_t)
+ tar_atol(header->rdevminor, sizeof(header->rdevminor)));
+ }
+ } else {
archive_entry_set_rdev(entry, 0);
+ }
tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining);
/* Grab GNU-specific fields. */
- t = tar_atol(header->atime, sizeof(header->atime));
- if (t > 0)
- archive_entry_set_atime(entry, t, 0);
- t = tar_atol(header->ctime, sizeof(header->ctime));
- if (t > 0)
- archive_entry_set_ctime(entry, t, 0);
+ if (!archive_entry_atime_is_set(entry)) {
+ t = tar_atol(header->atime, sizeof(header->atime));
+ if (t > 0)
+ archive_entry_set_atime(entry, t, 0);
+ }
+ if (!archive_entry_ctime_is_set(entry)) {
+ t = tar_atol(header->ctime, sizeof(header->ctime));
+ if (t > 0)
+ archive_entry_set_ctime(entry, t, 0);
+ }
if (header->realsize[0] != 0) {
tar->realsize
* importantly, the sparse data was lost when extracted by archivers
* that didn't recognize this extension.
*/
-
static int
-gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p)
+gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p, size_t length)
{
const char *e;
int64_t offset = -1, size = -1;
for (;;) {
e = p;
- while (*e != '\0' && *e != ',') {
+ while (length > 0 && *e != ',') {
if (*e < '0' || *e > '9')
return (ARCHIVE_WARN);
e++;
+ length--;
}
if (offset < 0) {
offset = tar_atol10(p, e - p);
return (ARCHIVE_FATAL);
offset = -1;
}
- if (*e == '\0')
+ if (length == 0)
return (ARCHIVE_OK);
p = e + 1;
+ length--;
}
}
* consist of both data and hole.
*/
static int
-solaris_sparse_parse(struct archive_read *a, struct tar *tar,
- struct archive_entry *entry, const char *p)
+pax_attribute_SUN_holesdata(struct archive_read *a, struct tar *tar,
+ struct archive_entry *entry, const char *p, size_t length)
{
const char *e;
int64_t start, end;
(void)entry; /* UNUSED */
end = 0;
- if (*p == ' ')
+ if (length <= 0)
+ return (ARCHIVE_WARN);
+ if (*p == ' ') {
p++;
- else
+ length--;
+ } else {
return (ARCHIVE_WARN);
+ }
for (;;) {
e = p;
- while (*e != '\0' && *e != ' ') {
+ while (length > 0 && *e != ' ') {
if (*e < '0' || *e > '9')
return (ARCHIVE_WARN);
e++;
+ length--;
}
start = end;
end = tar_atol10(p, e - p);
return (ARCHIVE_FATAL);
tar->sparse_last->hole = hole;
}
- if (*e == '\0')
- return (ARCHIVE_OK);
+ if (length == 0 || *e == '\n') {
+ if (length == 0 && *e == '\n') {
+ return (ARCHIVE_OK);
+ } else {
+ return (ARCHIVE_WARN);
+ }
+ }
p = e + 1;
+ length--;
hole = hole == 0;
}
}
}
static char *
-url_decode(const char *in)
+url_decode(const char *in, size_t length)
{
char *out, *d;
const char *s;
- out = (char *)malloc(strlen(in) + 1);
+ out = (char *)malloc(length + 1);
if (out == NULL)
return (NULL);
- for (s = in, d = out; *s != '\0'; ) {
- if (s[0] == '%' && s[1] != '\0' && s[2] != '\0') {
+ for (s = in, d = out; length > 0 && *s != '\0'; ) {
+ if (s[0] == '%' && length > 2) {
/* Try to convert % escape */
int digit1 = tohex(s[1]);
int digit2 = tohex(s[2]);
if (digit1 >= 0 && digit2 >= 0) {
/* Looks good, consume three chars */
s += 3;
+ length -= 3;
/* Convert output */
*d++ = ((digit1 << 4) | digit2);
continue;
/* Else fall through and treat '%' as normal char */
}
*d++ = *s++;
+ --length;
}
*d = '\0';
return (out);