From: Michihiro NAKAJIMA Date: Wed, 9 Mar 2011 17:12:29 +0000 (-0500) Subject: Add the ability to be able to recognize mtree format without a signature "#mtree" X-Git-Tag: v3.0.0a~670 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cbc36660ea2b5b2f891b4b0cad1bf055641978a0;p=thirdparty%2Flibarchive.git Add the ability to be able to recognize mtree format without a signature "#mtree" since mtree utility still does not record the signature. SVN-Revision: 2999 --- diff --git a/Makefile.am b/Makefile.am index 49ddc5f51..6b28b5460 100644 --- a/Makefile.am +++ b/Makefile.am @@ -440,6 +440,7 @@ libarchive_test_EXTRA_DIST=\ libarchive/test/test_read_format_lha_lh7.lzh.uu \ libarchive/test/test_read_format_lha_withjunk.lzh.uu \ libarchive/test/test_read_format_mtree.mtree.uu \ + libarchive/test/test_read_format_mtree_nomagic.mtree.uu \ libarchive/test/test_read_format_raw.data.Z.uu \ libarchive/test/test_read_format_raw.data.uu \ libarchive/test/test_read_format_tar_empty_filename.tar.uu \ diff --git a/libarchive/archive_read_support_format_mtree.c b/libarchive/archive_read_support_format_mtree.c index e24aa8692..27dadface 100644 --- a/libarchive/archive_read_support_format_mtree.c +++ b/libarchive/archive_read_support_format_mtree.c @@ -1,6 +1,7 @@ /*- * Copyright (c) 2003-2007 Tim Kientzle * Copyright (c) 2008 Joerg Sonnenberger + * Copyright (c) 2011 Michihiro NAKAJIMA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -186,20 +187,377 @@ cleanup(struct archive_read *a) return (ARCHIVE_OK); } +static ssize_t +get_line_size(const char *b, ssize_t avail, ssize_t *nlsize) +{ + ssize_t len; + + len = 0; + while (len < avail) { + switch (*b) { + case '\0':/* Non-ascii character or control character. */ + if (nlsize != NULL) + *nlsize = 0; + return (-1); + case '\r': + if (avail-len > 1 && b[1] == '\n') { + if (nlsize != NULL) + *nlsize = 2; + return (len+2); + } + /* FALL THROUGH */ + case '\n': + if (nlsize != NULL) + *nlsize = 1; + return (len+1); + default: + b++; + len++; + break; + } + } + if (nlsize != NULL) + *nlsize = 0; + return (avail); +} + +static ssize_t +next_line(struct archive_read *a, + const char **b, ssize_t *avail, ssize_t *ravail, ssize_t *nl) +{ + ssize_t len; + int quit; + + quit = 0; + if (*avail == 0) { + *nl = 0; + len = 0; + } else + len = get_line_size(*b, *avail, nl); + /* + * Read bytes more while it does not reach the end of line. + */ + while (*nl == 0 && len == *avail && !quit) { + ssize_t diff = *ravail - *avail; + + *b = __archive_read_ahead(a, 160 + *ravail, avail); + if (*b == NULL) { + if (*ravail >= *avail) + return (0); + /* Reading bytes reaches the end of file. */ + *b = __archive_read_ahead(a, *avail, avail); + quit = 1; + } + *ravail = *avail; + *b += diff; + *avail -= diff; + len = get_line_size(*b, *avail, nl); + } + return (len); +} + +/* + * Compare characters with a mtree keyword. + * Returns the length of a mtree keyword if matched. + * Returns 0 if not matched. + */ +int +bid_keycmp(const char *p, const char *key, ssize_t len) +{ + int match_len = 0; + + while (len > 0 && *p && *key) { + if (*p == *key) { + --len; + ++p; + ++key; + ++match_len; + continue; + } + return (0);/* Not match */ + } + if (*key != '\0') + return (0);/* Not match */ + + /* A following character should be specified characters */ + if (p[0] == '=' || p[0] == ' ' || p[0] == '\t' || + p[0] == '\n' || p[0] == '\r' || + (p[0] == '\\' && (p[1] == '\n' || p[1] == '\r'))) + return (match_len); + return (0);/* Not match */ +} + +/* + * Test whether the characters 'p' has is mtree keyword. + * Returns the length of a detected keyword. + * Returns 0 if any keywords were not found. + */ +static ssize_t +bid_keyword(const char *p, ssize_t len) +{ + static const char *keys_c[] = { + "content", "contents", "cksum", NULL + }; + static const char *keys_df[] = { + "device", "flags", NULL + }; + static const char *keys_g[] = { + "gid", "gname", NULL + }; + static const char *keys_il[] = { + "ignore", "link", NULL + }; + static const char *keys_m[] = { + "md5", "md5digest", "mode", NULL + }; + static const char *keys_no[] = { + "nlink", "optional", NULL + }; + static const char *keys_r[] = { + "rmd160", "rmd160digest", NULL + }; + static const char *keys_s[] = { + "sha1", "sha1digest", + "sha256", "sha256digest", + "sha384", "sha384digest", + "sha512", "sha512digest", + "size", NULL + }; + static const char *keys_t[] = { + "tags", "time", "type", NULL + }; + static const char *keys_u[] = { + "uid", "uname", NULL + }; + const char **keys; + int i; + + switch (*p) { + case 'c': keys = keys_c; break; + case 'd': case 'f': keys = keys_df; break; + case 'g': keys = keys_g; break; + case 'i': case 'l': keys = keys_il; break; + case 'm': keys = keys_m; break; + case 'n': case 'o': keys = keys_no; break; + case 'r': keys = keys_r; break; + case 's': keys = keys_s; break; + case 't': keys = keys_t; break; + case 'u': keys = keys_u; break; + default: return (0);/* Unknown key */ + } + + for (i = 0; keys[i] != NULL; i++) { + int l = bid_keycmp(p, keys[i], len); + if (l > 0) + return (l); + } + return (0);/* Unkown key */ +} + +/* + * Test whether there is a set of mtree keywords. + * Returns the number of keyword. + * Returns -1 if we got incorrect sequence. + * This function expects a set of "keyword=value". + * When "unset" is specified, expects a set of "keyword". + */ +static int +bid_keyword_list(const char *p, ssize_t len, int unset) +{ + int l; + int keycnt = 0; + + while (len > 0 && *p) { + int blank = 0; + + /* Test whether there are blank characters in the line. */ + while (len >0 && (*p == ' ' || *p == '\t')) { + ++p; + --len; + blank = 1; + } + if (*p == '\n' || *p == '\r') + break; + if (p[0] == '\\' && (p[1] == '\n' || p[1] == '\r')) + break; + if (!blank) /* No blank character. */ + return (-1); + + if (unset) { + l = bid_keycmp(p, "all", len); + if (l > 0) + return (1); + } + /* Test whether there is a correct key in the line. */ + l = bid_keyword(p, len); + if (l == 0) + return (-1);/* Unknown keyword was found. */ + p += l; + len -= l; + keycnt++; + + /* Skip value */ + if (*p == '=') { + int value = 0; + ++p; + --len; + while (len > 0 && *p != ' ' && *p != '\t') { + ++p; + --len; + value = 1; + } + /* A keyword should have a its value unless + * "/unset" operation. */ + if (!unset && value == 0) + return (-1); + } + } + return (keycnt); +} + +static int +bid_entry(const char *p, ssize_t len) +{ + int f = 0; + static const unsigned char safe_char[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1F */ + /* !"$%&'()*+,-./ EXCLUSION:( )(#) */ + 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 20 - 2F */ + /* 0123456789:;<>? EXCLUSION:(=) */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, /* 30 - 3F */ + /* @ABCDEFGHIJKLMNO */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */ + /* PQRSTUVWXYZ[\]^_ */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 - 5F */ + /* `abcdefghijklmno */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60 - 6F */ + /* pqrstuvwxyz{|}~ */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* 70 - 7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */ + }; + + /* + * Skip the path-name which is quoted. + */ + while (len > 0 && *p != ' ' && *p != '\t') { + if (!safe_char[*(const unsigned char *)p]) + return (-1); + ++p; + --len; + ++f; + } + /* If a path-name was not found, returns error. */ + if (f == 0) + return (-1); + + return (bid_keyword_list(p, len, 0)); +} + +#define MAX_BID_ENTRY 3 static int mtree_bid(struct archive_read *a) { const char *signature = "#mtree"; const char *p; + ssize_t avail, ravail; + ssize_t len, nl; + int detected_bytes = 0, entry_cnt = 0, multiline = 0; /* Now let's look at the actual header and see if it matches. */ - p = __archive_read_ahead(a, strlen(signature), NULL); + p = __archive_read_ahead(a, strlen(signature), &avail); if (p == NULL) return (-1); if (strncmp(p, signature, strlen(signature)) == 0) return (8 * (int)strlen(signature)); + + /* + * There is not a mtree signature. Let's try to detect mtree format. + */ + ravail = avail; + for (;;) { + len = next_line(a, &p, &avail, &ravail, &nl); + /* The terminal character of the line should be + * a new line character, '\r\n' or '\n'. */ + if (len <= 0 || nl == 0) + break; + if (!multiline) { + /* Leading whitespace is never significant, + * ignore it. */ + while (len > 0 && (*p == ' ' || *p == '\t')) { + ++p; + --avail; + --len; + } + /* Skip comment or empty line. */ + if (p[0] == '#' || p[0] == '\n' || p[0] == '\r') { + p += len; + avail -= len; + continue; + } + } else { + /* A continuance line; the terminal + * character of previous line was '\' character. */ + if (bid_keyword_list(p, len, 0) <= 0) + break; + if (multiline == 1) + detected_bytes += len; + if (p[len-nl-1] != '\\') { + if (multiline == 1 && + ++entry_cnt >= MAX_BID_ENTRY) + break; + multiline = 0; + } + p += len; + avail -= len; + continue; + } + if (p[0] != '/') { + if (bid_entry(p, len) >= 0) { + detected_bytes += len; + if (p[len-nl-1] == '\\') + /* This line continues. */ + multiline = 1; + else { + /* We've got plenty of correct lines + * to assume that this file is a mtree + * format. */ + if (++entry_cnt >= MAX_BID_ENTRY) + break; + } + } else + break; + } else if (strncmp(p, "/set", 4) == 0) { + if (bid_keyword_list(p+4, len-4, 0) <= 0) + break; + /* This line continues. */ + if (p[len-nl-1] == '\\') + multiline = 2; + } else if (strncmp(p, "/unset", 6) == 0) { + if (bid_keyword_list(p+6, len-6, 1) <= 0) + break; + /* This line continues. */ + if (p[len-nl-1] == '\\') + multiline = 2; + } else + break; + + /* Test next line. */ + p += len; + avail -= len; + } + if (entry_cnt >= MAX_BID_ENTRY || (entry_cnt > 0 && len == 0)) + return (32); + return (0); } diff --git a/libarchive/test/test_read_format_mtree.c b/libarchive/test/test_read_format_mtree.c index 1ea45ed1e..02c4be36e 100644 --- a/libarchive/test/test_read_format_mtree.c +++ b/libarchive/test/test_read_format_mtree.c @@ -177,10 +177,91 @@ test_read_format_mtree3(void) } +static void +test_read_format_mtree4(void) +{ + const char reffile[] = "test_read_format_mtree_nomagic.mtree"; + char buff[16]; + struct archive_entry *ae; + struct archive *a; + FILE *f; + + assertMakeDir("mtree4", 0777); + assertChdir("mtree4"); + + extract_reference_file(reffile); + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_compression_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_file(a, reffile, 11)); + + /* + * Read "file", whose data is available on disk. + */ + f = fopen("file", "wb"); + assert(f != NULL); + assertEqualInt(3, fwrite("hi\n", 1, 3, f)); + fclose(f); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_MTREE); + assertEqualString(archive_entry_pathname(ae), "file"); + assertEqualInt(archive_entry_uid(ae), 18); + assertEqualInt(AE_IFREG, archive_entry_filetype(ae)); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0123); + assertEqualInt(archive_entry_size(ae), 3); + assertEqualInt(3, archive_read_data(a, buff, 3)); + assertEqualMem(buff, "hi\n", 3); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir"); + assertEqualInt(AE_IFDIR, archive_entry_filetype(ae)); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir/file with space"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "file with space"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3a"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3a/indir3a"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/fullindir2"); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0644); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/indir2"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3b"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "dir2/dir3b/indir3b"); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "notindir"); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + + assertChdir(".."); +} + DEFINE_TEST(test_read_format_mtree) { test_read_format_mtree1(); test_read_format_mtree2(); test_read_format_mtree3(); + test_read_format_mtree4(); } diff --git a/libarchive/test/test_read_format_mtree_nomagic.mtree.uu b/libarchive/test/test_read_format_mtree_nomagic.mtree.uu new file mode 100644 index 000000000..be089103d --- /dev/null +++ b/libarchive/test/test_read_format_mtree_nomagic.mtree.uu @@ -0,0 +1,11 @@ +begin 644 test_read_format_mtree_nomagic.mtree +M9FEL92!T>7!E/69I;&4@=6ED/3$X(&UO9&4],#$R,R!S:7IE/3,*9&ER('1Y +M<&4]9&ER"B!F:6QE7#`T,'=I=&A<,#0P7!E/61I<@H@(&EN9&ER,V$@='EP93UF:6QE"F1I +M7!E/69I;&4@;6]D93TP-S7!E/69I;&4*(&1I7!E/69I +M;&4*("`N+@H@+BX*;F]T:6YD:7(@='EP93UF:6QE"F1I