+#include <ustar.h>
+#include <limits.h>
+#include <packed.h>
+#include <stdio.h>
+#include <string.h>
+
+/* Header for ustar-format tar archive. See the documentation of
+ the "pax" utility in [SUSv3] for the the "ustar" format
+ specification. */
+struct ustar_header
+ {
+ char name[100]; /* File name. Null-terminated if room. */
+ char mode[8]; /* Permissions as octal string. */
+ char uid[8]; /* User ID as octal string. */
+ char gid[8]; /* Group ID as octal string. */
+ char size[12]; /* File size in bytes as octal string. */
+ char mtime[12]; /* Modification time in seconds
+ from Jan 1, 1970, as octal string. */
+ char chksum[8]; /* Sum of octets in header as octal string. */
+ char typeflag; /* An enum ustar_type value. */
+ char linkname[100]; /* Name of link target.
+ Null-terminated if room. */
+ char magic[6]; /* "ustar\0" */
+ char version[2]; /* "00" */
+ char uname[32]; /* User name, always null-terminated. */
+ char gname[32]; /* Group name, always null-terminated. */
+ char devmajor[8]; /* Device major number as octal string. */
+ char devminor[8]; /* Device minor number as octal string. */
+ char prefix[155]; /* Prefix to file name.
+ Null-terminated if room. */
+ char padding[12]; /* Pad to 512 bytes. */
+ }
+PACKED;
+
+/* Returns the checksum for the given ustar format HEADER. */
+static unsigned int
+calculate_chksum (const struct ustar_header *h)
+{
+ const uint8_t *header = (const uint8_t *) h;
+ unsigned int chksum;
+ size_t i;
+
+ chksum = 0;
+ for (i = 0; i < USTAR_HEADER_SIZE; i++)
+ {
+ /* The ustar checksum is calculated as if the chksum field
+ were all spaces. */
+ const size_t chksum_start = offsetof (struct ustar_header, chksum);
+ const size_t chksum_end = chksum_start + sizeof h->chksum;
+ bool in_chksum_field = i >= chksum_start && i < chksum_end;
+ chksum += in_chksum_field ? ' ' : header[i];
+ }
+ return chksum;
+}
+
+/* Drop possibly dangerous prefixes from FILE_NAME and return the
+ stripped name. An archive with file names that start with "/"
+ or "../" could cause a naive tar extractor to write to
+ arbitrary parts of the file system, not just the destination
+ directory. We don't want to create such archives or be such a
+ naive extractor.
+
+ The return value can be a suffix of FILE_NAME or a string
+ literal. */
+static const char *
+strip_antisocial_prefixes (const char *file_name)
+{
+ while (*file_name == '/'
+ || !memcmp (file_name, "./", 2)
+ || !memcmp (file_name, "../", 3))
+ file_name = strchr (file_name, '/') + 1;
+ return *file_name == '\0' || !strcmp (file_name, "..") ? "." : file_name;
+}
+
+/* Composes HEADER as a USTAR_HEADER_SIZE (512)-byte archive
+ header in ustar format for a SIZE-byte file named FILE_NAME of
+ the given TYPE. The caller is responsible for writing the
+ header to a file or device.
+
+ If successful, returns true. On failure (due to an
+ excessively long file name), returns false. */
+bool
+ustar_make_header (const char *file_name, enum ustar_type type,
+ int size, char header[USTAR_HEADER_SIZE])
+{
+ struct ustar_header *h = (struct ustar_header *) header;
+
+ ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE);
+ ASSERT (type == USTAR_REGULAR || type == USTAR_DIRECTORY);
+
+ /* Check file name. */
+ file_name = strip_antisocial_prefixes (file_name);
+ if (strlen (file_name) > 99)
+ {
+ printf ("%s: file name too long\n", file_name);
+ return false;
+ }
+
+ /* Fill in header except for final checksum. */
+ memset (h, 0, sizeof *h);
+ strlcpy (h->name, file_name, sizeof h->name);
+ snprintf (h->mode, sizeof h->mode, "%07o",
+ type == USTAR_REGULAR ? 0644 : 0755);
+ strlcpy (h->uid, "0000000", sizeof h->uid);
+ strlcpy (h->gid, "0000000", sizeof h->gid);
+ snprintf (h->size, sizeof h->size, "%011o", size);
+ snprintf (h->mtime, sizeof h->size, "%011o", 1136102400);
+ h->typeflag = type;
+ strlcpy (h->magic, "ustar", sizeof h->magic);
+ h->version[0] = h->version[1] = '0';
+ strlcpy (h->gname, "root", sizeof h->gname);
+ strlcpy (h->uname, "root", sizeof h->uname);
+
+ /* Compute and fill in final checksum. */
+ snprintf (h->chksum, sizeof h->chksum, "%07o", calculate_chksum (h));
+
+ return true;
+}
+
+/* Parses a SIZE-byte octal field in S in the format used by
+ ustar format. If successful, stores the field's value in
+ *VALUE and returns true; on failure, returns false.
+
+ ustar octal fields consist of a sequence of octal digits
+ terminated by a space or a null byte. The ustar specification
+ seems ambiguous as to whether these fields must be padded on
+ the left with '0's, so we accept any field that fits in the
+ available space, regardless of whether it fills the space. */
+static bool
+parse_octal_field (const char *s, size_t size, unsigned long int *value)
+{
+ size_t ofs;
+
+ *value = 0;
+ for (ofs = 0; ofs < size; ofs++)
+ {
+ char c = s[ofs];
+ if (c >= '0' && c <= '7')
+ {
+ if (*value > ULONG_MAX / 8)
+ {
+ /* Overflow. */
+ return false;
+ }
+ *value = c - '0' + *value * 8;
+ }
+ else if (c == ' ' || c == '\0')
+ {
+ /* End of field, but disallow completely empty
+ fields. */
+ return ofs > 0;
+ }
+ else
+ {
+ /* Bad character. */
+ return false;
+ }
+ }
+
+ /* Field did not end in space or null byte. */
+ return false;
+}
+
+/* Returns true if the CNT bytes starting at BLOCK are all zero,
+ false otherwise. */
+static bool
+is_all_zeros (const char *block, size_t cnt)
+{
+ while (cnt-- > 0)
+ if (*block++ != 0)
+ return false;
+ return true;
+}
+
+/* Parses HEADER as a ustar-format archive header for a regular
+ file or directory. If successful, stores the archived file's
+ name in *FILE_NAME (as a pointer into HEADER or a string
+ literal), its type in *TYPE, and its size in bytes in *SIZE,
+ and returns a null pointer. On failure, returns a
+ human-readable error message. */
+const char *
+ustar_parse_header (const char header[USTAR_HEADER_SIZE],
+ const char **file_name, enum ustar_type *type, int *size)
+{
+ const struct ustar_header *h = (const struct ustar_header *) header;
+ unsigned long int chksum, size_ul;
+
+ ASSERT (sizeof (struct ustar_header) == USTAR_HEADER_SIZE);
+
+ /* Detect end of archive. */
+ if (is_all_zeros (header, USTAR_HEADER_SIZE))
+ {
+ *file_name = NULL;
+ *type = USTAR_EOF;
+ *size = 0;
+ return NULL;
+ }
+
+ /* Validate ustar header. */
+ if (memcmp (h->magic, "ustar", 6))
+ return "not a ustar archive";
+ else if (h->version[0] != '0' || h->version[1] != '0')
+ return "invalid ustar version";
+ else if (!parse_octal_field (h->chksum, sizeof h->chksum, &chksum))
+ return "corrupt chksum field";
+ else if (chksum != calculate_chksum (h))
+ return "checksum mismatch";
+ else if (h->name[sizeof h->name - 1] != '\0' || h->prefix[0] != '\0')
+ return "file name too long";
+ else if (h->typeflag != USTAR_REGULAR && h->typeflag != USTAR_DIRECTORY)
+ return "unimplemented file type";
+ if (h->typeflag == USTAR_REGULAR)
+ {
+ if (!parse_octal_field (h->size, sizeof h->size, &size_ul))
+ return "corrupt file size field";
+ else if (size_ul > INT_MAX)
+ return "file too large";
+ }
+ else
+ size_ul = 0;
+
+ /* Success. */
+ *file_name = strip_antisocial_prefixes (h->name);
+ *type = h->typeflag;
+ *size = size_ul;
+ return NULL;
+}
+