273 lines
6.1 KiB
C
273 lines
6.1 KiB
C
|
#include "mupdf/fitz.h"
|
||
|
#include "fitz-imp.h"
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <limits.h>
|
||
|
|
||
|
typedef struct tar_entry_s tar_entry;
|
||
|
typedef struct fz_tar_archive_s fz_tar_archive;
|
||
|
|
||
|
struct tar_entry_s
|
||
|
{
|
||
|
char *name;
|
||
|
int64_t offset;
|
||
|
int size;
|
||
|
};
|
||
|
|
||
|
struct fz_tar_archive_s
|
||
|
{
|
||
|
fz_archive super;
|
||
|
|
||
|
int count;
|
||
|
tar_entry *entries;
|
||
|
};
|
||
|
|
||
|
static inline int isoctdigit(char c)
|
||
|
{
|
||
|
return c >= '0' && c <= '7';
|
||
|
}
|
||
|
|
||
|
static inline int64_t otoi(const char *s)
|
||
|
{
|
||
|
int64_t value = 0;
|
||
|
|
||
|
while (*s && isoctdigit(*s))
|
||
|
{
|
||
|
value *= 8;
|
||
|
value += (*s) - '0';
|
||
|
s++;
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
static void drop_tar_archive(fz_context *ctx, fz_archive *arch)
|
||
|
{
|
||
|
fz_tar_archive *tar = (fz_tar_archive *) arch;
|
||
|
int i;
|
||
|
for (i = 0; i < tar->count; ++i)
|
||
|
fz_free(ctx, tar->entries[i].name);
|
||
|
fz_free(ctx, tar->entries);
|
||
|
}
|
||
|
|
||
|
static void ensure_tar_entries(fz_context *ctx, fz_tar_archive *tar)
|
||
|
{
|
||
|
fz_stream *file = tar->super.file;
|
||
|
char name[100];
|
||
|
char octsize[12];
|
||
|
char typeflag;
|
||
|
int64_t offset, blocks, size;
|
||
|
size_t n;
|
||
|
|
||
|
tar->count = 0;
|
||
|
|
||
|
fz_seek(ctx, file, 0, SEEK_SET);
|
||
|
|
||
|
while (1)
|
||
|
{
|
||
|
offset = fz_tell(ctx, file);
|
||
|
n = fz_read(ctx, file, (unsigned char *) name, nelem(name));
|
||
|
if (n < nelem(name))
|
||
|
fz_throw(ctx, FZ_ERROR_GENERIC, "premature end of data in tar entry name");
|
||
|
name[nelem(name) - 1] = '\0';
|
||
|
|
||
|
if (strlen(name) == 0)
|
||
|
break;
|
||
|
|
||
|
fz_seek(ctx, file, 24, 1);
|
||
|
n = fz_read(ctx, file, (unsigned char *) octsize, nelem(octsize));
|
||
|
if (n < nelem(octsize))
|
||
|
fz_throw(ctx, FZ_ERROR_GENERIC, "premature end of data in tar entry size");
|
||
|
octsize[nelem(octsize) - 1] = '\0';
|
||
|
size = otoi(octsize);
|
||
|
if (size > INT_MAX)
|
||
|
fz_throw(ctx, FZ_ERROR_GENERIC, "tar archive entry too large");
|
||
|
|
||
|
fz_seek(ctx, file, 20, 1);
|
||
|
typeflag = fz_read_byte(ctx, file);
|
||
|
|
||
|
fz_seek(ctx, file, 355, 1);
|
||
|
blocks = (size + 511) / 512;
|
||
|
fz_seek(ctx, file, blocks * 512, 1);
|
||
|
|
||
|
if (typeflag != '0' && typeflag != '\0')
|
||
|
continue;
|
||
|
|
||
|
tar->entries = fz_realloc_array(ctx, tar->entries, tar->count + 1, tar_entry);
|
||
|
|
||
|
tar->entries[tar->count].name = fz_strdup(ctx, name);
|
||
|
tar->entries[tar->count].offset = offset;
|
||
|
tar->entries[tar->count].size = size;
|
||
|
|
||
|
tar->count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static tar_entry *lookup_tar_entry(fz_context *ctx, fz_tar_archive *tar, const char *name)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < tar->count; i++)
|
||
|
if (!fz_strcasecmp(name, tar->entries[i].name))
|
||
|
return &tar->entries[i];
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static fz_stream *open_tar_entry(fz_context *ctx, fz_archive *arch, const char *name)
|
||
|
{
|
||
|
fz_tar_archive *tar = (fz_tar_archive *) arch;
|
||
|
fz_stream *file = tar->super.file;
|
||
|
tar_entry *ent;
|
||
|
|
||
|
ent = lookup_tar_entry(ctx, tar, name);
|
||
|
if (!ent)
|
||
|
fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find named tar archive entry");
|
||
|
|
||
|
fz_seek(ctx, file, ent->offset + 512, 0);
|
||
|
return fz_open_null_filter(ctx, file, ent->size, fz_tell(ctx, file));
|
||
|
}
|
||
|
|
||
|
static fz_buffer *read_tar_entry(fz_context *ctx, fz_archive *arch, const char *name)
|
||
|
{
|
||
|
fz_tar_archive *tar = (fz_tar_archive *) arch;
|
||
|
fz_stream *file = tar->super.file;
|
||
|
fz_buffer *ubuf;
|
||
|
tar_entry *ent;
|
||
|
|
||
|
ent = lookup_tar_entry(ctx, tar, name);
|
||
|
if (!ent)
|
||
|
fz_throw(ctx, FZ_ERROR_GENERIC, "cannot find named tar archive entry");
|
||
|
|
||
|
ubuf = fz_new_buffer(ctx, ent->size);
|
||
|
|
||
|
fz_try(ctx)
|
||
|
{
|
||
|
fz_seek(ctx, file, ent->offset + 512, 0);
|
||
|
ubuf->len = fz_read(ctx, file, ubuf->data, ent->size);
|
||
|
if (ubuf->len != (size_t)ent->size)
|
||
|
fz_throw(ctx, FZ_ERROR_GENERIC, "cannot read entire archive entry");
|
||
|
}
|
||
|
fz_catch(ctx)
|
||
|
{
|
||
|
fz_drop_buffer(ctx, ubuf);
|
||
|
fz_rethrow(ctx);
|
||
|
}
|
||
|
|
||
|
return ubuf;
|
||
|
}
|
||
|
|
||
|
static int has_tar_entry(fz_context *ctx, fz_archive *arch, const char *name)
|
||
|
{
|
||
|
fz_tar_archive *tar = (fz_tar_archive *) arch;
|
||
|
tar_entry *ent = lookup_tar_entry(ctx, tar, name);
|
||
|
return ent != NULL;
|
||
|
}
|
||
|
|
||
|
static const char *list_tar_entry(fz_context *ctx, fz_archive *arch, int idx)
|
||
|
{
|
||
|
fz_tar_archive *tar = (fz_tar_archive *) arch;
|
||
|
if (idx < 0 || idx >= tar->count)
|
||
|
return NULL;
|
||
|
return tar->entries[idx].name;
|
||
|
}
|
||
|
|
||
|
static int count_tar_entries(fz_context *ctx, fz_archive *arch)
|
||
|
{
|
||
|
fz_tar_archive *tar = (fz_tar_archive *) arch;
|
||
|
return tar->count;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Detect if stream object is a tar achieve.
|
||
|
|
||
|
Assumes that the stream object is seekable.
|
||
|
*/
|
||
|
int
|
||
|
fz_is_tar_archive(fz_context *ctx, fz_stream *file)
|
||
|
{
|
||
|
const unsigned char gnusignature[6] = { 'u', 's', 't', 'a', 'r', ' ' };
|
||
|
const unsigned char paxsignature[6] = { 'u', 's', 't', 'a', 'r', '\0' };
|
||
|
const unsigned char v7signature[6] = { '\0', '\0', '\0', '\0', '\0', '\0' };
|
||
|
unsigned char data[6];
|
||
|
size_t n;
|
||
|
|
||
|
fz_seek(ctx, file, 257, 0);
|
||
|
n = fz_read(ctx, file, data, nelem(data));
|
||
|
if (n != nelem(data))
|
||
|
return 0;
|
||
|
if (!memcmp(data, gnusignature, nelem(gnusignature)))
|
||
|
return 1;
|
||
|
if (!memcmp(data, paxsignature, nelem(paxsignature)))
|
||
|
return 1;
|
||
|
if (!memcmp(data, v7signature, nelem(v7signature)))
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Open a tar archive stream.
|
||
|
|
||
|
Open an archive using a seekable stream object rather than
|
||
|
opening a file or directory on disk.
|
||
|
|
||
|
An exception is throw if the stream is not a tar archive as
|
||
|
indicated by the presence of a tar signature.
|
||
|
|
||
|
*/
|
||
|
fz_archive *
|
||
|
fz_open_tar_archive_with_stream(fz_context *ctx, fz_stream *file)
|
||
|
{
|
||
|
fz_tar_archive *tar;
|
||
|
|
||
|
if (!fz_is_tar_archive(ctx, file))
|
||
|
fz_throw(ctx, FZ_ERROR_GENERIC, "cannot recognize tar archive");
|
||
|
|
||
|
tar = fz_new_derived_archive(ctx, file, fz_tar_archive);
|
||
|
tar->super.format = "tar";
|
||
|
tar->super.count_entries = count_tar_entries;
|
||
|
tar->super.list_entry = list_tar_entry;
|
||
|
tar->super.has_entry = has_tar_entry;
|
||
|
tar->super.read_entry = read_tar_entry;
|
||
|
tar->super.open_entry = open_tar_entry;
|
||
|
tar->super.drop_archive = drop_tar_archive;
|
||
|
|
||
|
fz_try(ctx)
|
||
|
{
|
||
|
ensure_tar_entries(ctx, tar);
|
||
|
}
|
||
|
fz_catch(ctx)
|
||
|
{
|
||
|
fz_drop_archive(ctx, &tar->super);
|
||
|
fz_rethrow(ctx);
|
||
|
}
|
||
|
|
||
|
return &tar->super;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Open a tar archive file.
|
||
|
|
||
|
An exception is throw if the file is not a tar archive as
|
||
|
indicated by the presence of a tar signature.
|
||
|
|
||
|
filename: a path to a tar archive file as it would be given to
|
||
|
open(2).
|
||
|
*/
|
||
|
fz_archive *
|
||
|
fz_open_tar_archive(fz_context *ctx, const char *filename)
|
||
|
{
|
||
|
fz_archive *tar = NULL;
|
||
|
fz_stream *file;
|
||
|
|
||
|
file = fz_open_file(ctx, filename);
|
||
|
|
||
|
fz_try(ctx)
|
||
|
tar = fz_open_tar_archive_with_stream(ctx, file);
|
||
|
fz_always(ctx)
|
||
|
fz_drop_stream(ctx, file);
|
||
|
fz_catch(ctx)
|
||
|
fz_rethrow(ctx);
|
||
|
|
||
|
return tar;
|
||
|
}
|