* src/common.h (one_top_level_option): New global. * src/extract.c (extract_archive): If one_top_level_option is set, check for whether the archive tries to create two file trees. * src/tar.c (ONE_TOP_LEVEL_OPTION): New constant. (options): New option --one-top-level. (parse_opt): Handle this option. (decode_options): Make it conflict with --absolute-names.
* NEWS: Update. * doc/tar.texi: Document new option. --- NEWS | 8 ++++++++ doc/tar.texi | 9 +++++++++ src/common.h | 3 +++ src/extract.c | 43 +++++++++++++++++++++++++++++++++++++++++++ src/tar.c | 10 ++++++++++ 5 files changed, 73 insertions(+) diff --git a/NEWS b/NEWS index 90a9022..7487930 100644 --- a/NEWS +++ b/NEWS @@ -39,6 +39,14 @@ errors. Instead it just silently skips them. An additional level of verbosity can be obtained by using the option --warning=existing-file together with this option. +* The --one-top-level option. + +This new command line option tells tar that the working directory +(or the one passed to -C) should not be populated with more than one +name directly under it. A fatal error is thrown if, after name +transformations, a second member that would be extracted to this location +is found. + * Support for POSIX ACLs, extended attributes and SELinux context. Starting with this version tar is able to store, extract and list diff --git a/doc/tar.texi b/doc/tar.texi index 9fde5a0..86d7064 100644 --- a/doc/tar.texi +++ b/doc/tar.texi @@ -3086,6 +3086,15 @@ Used when creating an archive. Prevents @command{tar} from recursing into directories that are on different file systems from the current directory. +@opsummary{one-top-level} +@item --one-top-level + +Tells @command{tar} that only one name from the archive should be placed directly +under the extraction directory (the working directory or the one specified with +@option{-C}). A fatal error is thrown if a second one is found. The top-level of +a member is determined after the transformations from @option{--strip-components} +and @option{--transform} are applied. + @opsummary{overwrite} @item --overwrite diff --git a/src/common.h b/src/common.h index e65e647..8980ed1 100644 --- a/src/common.h +++ b/src/common.h @@ -235,6 +235,9 @@ GLOBAL bool numeric_owner_option; GLOBAL bool one_file_system_option; +/* Refuse to extract more than one top-level name. */ +GLOBAL bool one_top_level_option; + /* Specified value to be put into tar file in place of stat () results, or just null and -1 if such an override should not take place. */ GLOBAL char const *owner_name_option; diff --git a/src/extract.c b/src/extract.c index 9b6b7f9..ea5be67 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1578,6 +1578,45 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) return 1; } +/* Throws an error if passed file name is the second top-level name. */ +void +depth_check (char *file_name) +{ + size_t i; + size_t start = 0; + + static char *first_top_level; + static bool first_top_level_found = false; + + /* Leading slashes have already been removed so it is safe to start at 1. */ + for (i = 1; i <= strlen (file_name); i++) + { + if (ISSLASH (file_name[i]) || file_name[i] == '\0') + { + if ((i == start) || (i - start == 1 && file_name[start] == '.')) + start = i + 1; + else + { + if (first_top_level_found) + { + /* Still reject two top-levels if one is a substring of the other. */ + if (strncmp (first_top_level, file_name + start, i - start) + || strlen (first_top_level) != i - start) + FATAL_ERROR ((0, 0, _("Found multiple top-level names, exiting"))); + } + else + { + first_top_level_found = true; + first_top_level = xmalloc (i - start + 1); + strncpy (first_top_level, file_name + start, i - start); + first_top_level[i - start] = '\0'; + } + return; + } + } + } +} + /* Extract a file from the archive. */ void extract_archive (void) @@ -1623,6 +1662,10 @@ extract_archive (void) return; } + /* Check for names where the depth is too high. */ + if (one_top_level_option) + depth_check (current_stat_info.file_name); + /* Extract the archive entry according to its type. */ /* KLUDGE */ typeflag = sparse_member_p (¤t_stat_info) ? diff --git a/src/tar.c b/src/tar.c index 6c327f7..a248bb8 100644 --- a/src/tar.c +++ b/src/tar.c @@ -319,6 +319,7 @@ enum OCCURRENCE_OPTION, OLD_ARCHIVE_OPTION, ONE_FILE_SYSTEM_OPTION, + ONE_TOP_LEVEL_OPTION, OVERWRITE_DIR_OPTION, OVERWRITE_OPTION, OWNER_OPTION, @@ -489,6 +490,8 @@ static struct argp_option options[] = { {"keep-directory-symlink", KEEP_DIRECTORY_SYMLINK_OPTION, 0, 0, N_("preserve existing symlinks to directories when extracting"), GRID+1 }, + {"one-top-level", ONE_TOP_LEVEL_OPTION, 0, 0, + N_("allow at most one top-level name when extracting"), GRID+1 }, #undef GRID #define GRID 40 @@ -1437,6 +1440,10 @@ parse_opt (int key, char *arg, struct argp_state *state) one_file_system_option = true; break; + case ONE_TOP_LEVEL_OPTION: + one_top_level_option = true; + break; + case 'l': check_links_option = 1; break; @@ -2389,6 +2396,9 @@ decode_options (int argc, char **argv) subcommand_string (subcommand_option))); } + if (one_top_level_option && absolute_names_option) + USAGE_ERROR ((0, 0, _("--one-top-level cannot be used with --absolute-names"))); + if (archive_names == 0) { /* If no archive file name given, try TAPE from the environment, or -- 1.8.4