Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions

View File

@ -0,0 +1,23 @@
bin_PROGRAMS=lha
check_PROGRAMS=test-lha
SOURCE_FILES= \
main.c \
options.h \
filter.c filter.h \
list.c list.h \
extract.c extract.h \
safe.c safe.h
lha_SOURCES=$(SOURCE_FILES)
lha_CFLAGS=$(MAIN_CFLAGS) -I$(top_builddir)/lib/public -I$(top_builddir)
lha_LDADD=$(top_builddir)/lib/liblhasa.la
test_lha_SOURCES=$(SOURCE_FILES)
test_lha_CFLAGS=$(TEST_CFLAGS) -I$(top_builddir)/lib/public -I$(top_builddir)
test_lha_LDADD=$(top_builddir)/lib/liblhasatest.a
clean-local:
rm -f *.gcno *.gcda *.c.gcov

View File

@ -0,0 +1,76 @@
Notes on command line arguments:
Mode - must be the first character of the first argument. Leading '-'
is ignored:
e/x - Extract
l/v - List (normal / verbose mode)
t - Test CRC
p - Print files to STDOUT
Options - flags following the mode character. Options are accepted
even if they are inappropriate for the mode, eg. "lha lf".
q[level] - Quiet mode. Normal running is quiet level 0.
in list mode, quiet level 2 strips header/footer from
listing.
in extract mode, overwrite is always performed, and:
- at quiet level 1, the 'progress bar' is not shown.
- at quiet level 2, no messages are printed at all.
in test mode, behavior is similar to extract mode, but
level 2 behavior is similar to level 0.
in print mode, at quiet level 2 the filename is not
printed before the file contents.
all quiet modes imply 'f', disabling overwrite without
confirmation.
f - Force overwrite. When extracting, existing files are
overwritten with no confirmation.
n - Don't execute commands, just do a dry run of what would
be done.
eg. for lha en:
EXTRACT lhasa/src/ (directory)
EXTRACT lhasa/src/extract.c
EXTRACT lhasa/src/extract.c but file is exist.
eg. for lha tn:
VERIFY lhasa/src/main.c
i - Ignore directory path. When extracting, the directory
of each file is ignored - they are all extracted into
the same directory.
Also applies to test mode - the output filename of the
tested file does not include the directory.
In print mode, the path is not printed in the header
before each file.
v - Verbose mode, prints extra information.
in list mode, verbose mode splits the filename onto a
separate preceding line and the header level is shown
in its normal place.
in extract mode, verbose mode prints a message when
creating a directory. If creating missing parent directories,
they are printed in reverse order, eg.
Making directory "lhasa/src".
Making directory "lhasa".
lhasa/src/main.c - Melted : o
w=dir - Specify destination directory to extract files. Must be
the last option specified. '=' can be omitted. If combined
with 'i', all files get placed in that one directory.

View File

@ -0,0 +1,676 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "lib/lha_arch.h"
#include "extract.h"
#include "safe.h"
// Maximum number of dots in progress output:
#define MAX_PROGRESS_LEN 58
typedef struct {
int invoked;
LHAFileHeader *header;
LHAOptions *options;
char *filename;
char *operation;
} ProgressCallbackData;
// Given a file header structure, get the path to extract to.
// Returns a newly allocated string that must be free()d.
static char *file_full_path(LHAFileHeader *header, LHAOptions *options)
{
size_t len;
char *result;
char *p;
// Full path, or filename only?
len = 0;
if (options->extract_path != NULL) {
len += strlen(options->extract_path) + 1;
}
if (options->use_path && header->path != NULL) {
len += strlen(header->path);
}
if (header->filename != NULL) {
len += strlen(header->filename);
}
result = malloc(len + 1);
if (result == NULL) {
// TODO?
exit(-1);
}
result[0] = '\0';
if (options->extract_path != NULL) {
strcat(result, options->extract_path);
strcat(result, "/");
}
// Add path. If it is an absolute path (contains a leading '/')
// then skip over the leading '/' to make it a relative path.
// This prevents the possibility of a security threat with a
// malicious archive that might try to write to arbitrary
// filesystem locations.
// It also removes the double '/' when using the -w option.
if (options->use_path && header->path != NULL) {
p = header->path;
while (*p == '/') {
++p;
}
strcat(result, p);
}
// The filename header field might conceivably try to include
// a path separator as well, so skip over any leading '/'
// here too.
if (header->filename != NULL) {
p = header->filename;
while (*p == '/') {
++p;
}
strcat(result, p);
}
return result;
}
static void print_filename(char *filename, char *status)
{
printf("\r");
safe_printf("%s", filename);
printf("\t- %s ", status);
}
static void print_filename_brief(char *filename)
{
printf("\r");
safe_printf("%s :", filename);
}
// Callback function invoked during decompression progress.
static void progress_callback(unsigned int block,
unsigned int num_blocks,
void *data)
{
ProgressCallbackData *progress = data;
unsigned int factor;
unsigned int i;
progress->invoked = 1;
// If the quiet mode options are specified, print a limited amount
// of information without a progress bar (level 1) or no message
// at all (level 2).
if (progress->options->quiet >= 2) {
return;
} else if (progress->options->quiet == 1) {
if (block == 0) {
print_filename_brief(progress->filename);
fflush(stdout);
}
return;
}
// Scale factor for blocks, so that the line is never too long. When
// MAX_PROGRESS_LEN is exceeded, the length is halved (factor=2), then
// progressively larger scale factors are applied.
factor = 1 + (num_blocks / MAX_PROGRESS_LEN);
num_blocks = (num_blocks + factor - 1) / factor;
// First call to specify number of blocks?
if (block == 0) {
print_filename(progress->filename, progress->operation);
for (i = 0; i < num_blocks; ++i) {
printf(".");
}
print_filename(progress->filename, progress->operation);
} else if (((block + factor - 1) % factor) == 0) {
// Otherwise, signal progress:
printf("o");
}
fflush(stdout);
}
// Print a line to stdout describing a symlink.
static void print_symlink_line(char *src, char *dest)
{
safe_printf("Symbolic Link %s -> %s", src, dest);
printf("\n");
}
// Perform CRC check of an archived file.
static int test_archived_file_crc(LHAReader *reader,
LHAFileHeader *header,
LHAOptions *options)
{
ProgressCallbackData progress;
char *filename;
int success;
filename = file_full_path(header, options);
if (options->dry_run) {
if (strcmp(header->compress_method,
LHA_COMPRESS_TYPE_DIR) != 0) {
safe_printf("VERIFY %s", filename);
printf("\n");
}
free(filename);
return 1;
}
progress.invoked = 0;
progress.operation = "Testing :";
progress.options = options;
progress.filename = filename;
progress.header = header;
success = lha_reader_check(reader, progress_callback, &progress);
if (progress.invoked && options->quiet < 2) {
if (success) {
print_filename(filename, "Tested");
printf("\n");
} else {
print_filename(filename, "CRC error");
printf("\n");
}
fflush(stdout);
}
if (!success) {
// TODO: Exit with error
}
free(filename);
return success;
}
// Check that the specified directory exists, and create it if it
// does not.
static int check_parent_directory(char *path)
{
LHAFileType file_type;
file_type = lha_arch_exists(path);
switch (file_type) {
case LHA_FILE_DIRECTORY:
// Already exists.
break;
case LHA_FILE_NONE:
// Create the missing directory:
if (!lha_arch_mkdir(path, 0755)) {
fprintf(stderr,
"Failed to create parent directory %s\n",
path);
return 0;
}
break;
case LHA_FILE_FILE:
fprintf(stderr, "Parent path %s is not a directory!\n",
path);
return 0;
case LHA_FILE_ERROR:
fprintf(stderr, "Failed to stat %s\n", path);
return 0;
}
return 1;
}
// Given a filename, create its parent directories as necessary.
static int make_parent_directories(char *orig_path)
{
int result;
char *p;
char *path;
result = 1;
// Duplicate the path and strip off any trailing '/'s:
path = strdup(orig_path);
if (path == NULL) {
exit(-1);
}
p = path + strlen(path) - 1;
while (p >= path && *p == '/') {
*p = '\0';
--p;
}
// Iterate through the string, finding each path separator. At
// each place, temporarily chop off the end of the path to get
// each parent directory in turn.
for (p = path; *p == '/'; ++p);
for (;;) {
p = strchr(p, '/');
if (p == NULL) {
break;
}
*p = '\0';
// Check if this parent directory exists and create it:
if (!check_parent_directory(path)) {
result = 0;
break;
}
// Restore path separator and advance to the next path.
*p = '/';
++p;
}
free(path);
return result;
}
// Prompt the user with a message, and return the first character of
// the typed response.
static char prompt_user(char *message)
{
char result;
int c;
fprintf(stderr, "%s", message);
fflush(stderr);
// Read characters until a newline is found, saving the first
// character entered.
result = 0;
do {
c = getchar();
if (c < 0) {
exit(-1);
}
if (result == 0) {
result = c;
}
} while (c != '\n');
return result;
}
// A file to be extracted already exists. Apply the overwrite policy
// to decide whether to overwrite the existing file, prompting the
// user if necessary.
static int confirm_file_overwrite(char *filename, LHAOptions *options)
{
char response;
switch (options->overwrite_policy) {
case LHA_OVERWRITE_PROMPT:
break;
case LHA_OVERWRITE_SKIP:
return 0;
case LHA_OVERWRITE_ALL:
return 1;
}
for (;;) {
safe_fprintf(stderr, "%s ", filename);
response = prompt_user("OverWrite ?(Yes/[No]/All/Skip) ");
switch (tolower((unsigned int) response)) {
case 'y':
return 1;
case 'n':
case '\n':
return 0;
case 'a':
options->overwrite_policy = LHA_OVERWRITE_ALL;
return 1;
case 's':
options->overwrite_policy = LHA_OVERWRITE_SKIP;
return 0;
default:
break;
}
}
return 0;
}
// Check if the file pointed to by the specified header exists.
static int file_exists(char *filename)
{
LHAFileType file_type;
file_type = lha_arch_exists(filename);
if (file_type == LHA_FILE_ERROR) {
fprintf(stderr, "Failed to read file type of '%s'\n", filename);
exit(-1);
}
return file_type != LHA_FILE_NONE;
}
// Extract an archived file.
static int extract_archived_file(LHAReader *reader,
LHAFileHeader *header,
LHAOptions *options)
{
ProgressCallbackData progress;
char *filename;
int success;
int is_dir, is_symlink;
filename = file_full_path(header, options);
is_symlink = header->symlink_target != NULL;
is_dir = !strcmp(header->compress_method, LHA_COMPRESS_TYPE_DIR)
&& !is_symlink;
// If a file already exists with this name, confirm overwrite.
if (!is_dir && !is_symlink && file_exists(filename)
&& !confirm_file_overwrite(filename, options)) {
if (options->overwrite_policy == LHA_OVERWRITE_SKIP) {
safe_printf("%s : Skipped...", filename);
printf("\n");
}
free(filename);
return 1;
}
// No need to extract directories if use_path is disabled.
if (!options->use_path && is_dir) {
return 1;
}
// Create parent directories for file:
if (!make_parent_directories(filename)) {
free(filename);
return 0;
}
progress.invoked = 0;
progress.operation = "Melting :";
progress.options = options;
progress.header = header;
progress.filename = filename;
success = lha_reader_extract(reader, filename,
progress_callback, &progress);
if (!lha_reader_current_is_fake(reader) && options->quiet < 2) {
if (progress.invoked) {
if (success) {
print_filename(filename, "Melted");
printf("\n");
} else {
print_filename(filename, "Failure");
printf("\n");
}
} else if (is_symlink) {
print_symlink_line(filename, header->symlink_target);
}
fflush(stdout);
}
if (!success) {
// TODO: Exit with error
}
free(filename);
return success;
}
// lha -t command.
int test_file_crc(LHAFilter *filter, LHAOptions *options)
{
int result;
result = 1;
for (;;) {
LHAFileHeader *header;
header = lha_filter_next_file(filter);
if (header == NULL) {
break;
}
if (!test_archived_file_crc(filter->reader, header, options)) {
result = 0;
}
}
return result;
}
// lha -en / -xn / -pn:
// Simulate extracting an archive file, but just print the operations
// that would have been performed to stdout.
static int extract_archive_dry_run(LHAFilter *filter, LHAOptions *options)
{
char *filename;
int result;
result = 1;
for (;;) {
LHAFileHeader *header;
header = lha_filter_next_file(filter);
if (header == NULL) {
break;
}
filename = file_full_path(header, options);
// Every line begins the same - "EXTRACT filename..."
safe_printf("EXTRACT %s", filename);
// After the filename we might print something extra.
// The message if we have an existing file is weird, but this
// is just accurately duplicating what the Unix LHA tool says.
// The symlink handling is particularly odd - they are treated
// as directories (a bleed-through of the way in which
// symlinks are stored).
if (header->symlink_target != NULL) {
safe_printf("|%s (directory)", header->symlink_target);
} else if (!strcmp(header->compress_method,
LHA_COMPRESS_TYPE_DIR)) {
safe_printf(" (directory)");
} else if (file_exists(filename)) {
safe_printf(" but file is exist.");
}
printf("\n");
free(filename);
}
return result;
}
// lha -e / -x
int extract_archive(LHAFilter *filter, LHAOptions *options)
{
int result;
if (options->dry_run) {
return extract_archive_dry_run(filter, options);
}
result = 1;
for (;;) {
LHAFileHeader *header;
header = lha_filter_next_file(filter);
if (header == NULL) {
break;
}
if (!extract_archived_file(filter->reader, header, options)) {
result = 0;
}
}
return result;
}
// Dump contents of the current file from the specified reader to stdout.
static int print_archived_file(LHAReader *reader)
{
char buf[512];
size_t bytes;
for (;;) {
bytes = lha_reader_read(reader, buf, sizeof(buf));
if (bytes <= 0) {
break;
}
if (fwrite(buf, 1, bytes, stdout) < bytes) {
return 0;
}
}
return 1;
}
// lha -p
int print_archive(LHAFilter *filter, LHAOptions *options)
{
LHAFileHeader *header;
int is_normal_file;
char *full_path;
// As a weird quirk of Unix LHA, lha -pn is equivalent to lha -en:
if (options->dry_run) {
return extract_archive_dry_run(filter, options);
}
for (;;) {
header = lha_filter_next_file(filter);
if (header == NULL) {
break;
}
is_normal_file = strcmp(header->compress_method,
LHA_COMPRESS_TYPE_DIR) != 0;
// Print "header" before the file containing the filename.
// For normal files this is a three line separator.
// Symlinks get shown in the same way as during extract.
// Directories are ignored.
if (options->quiet < 2) {
full_path = file_full_path(header, options);
if (header->symlink_target != NULL) {
print_symlink_line(full_path,
header->symlink_target);
} else if (is_normal_file) {
printf("::::::::\n");
safe_printf("%s", full_path);
printf("\n::::::::\n");
}
free(full_path);
}
// If this is a normal file, dump the contents to stdout.
if (is_normal_file && !print_archived_file(filter->reader)) {
return 0;
}
}
return 1;
}

View File

@ -0,0 +1,32 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef LHASA_EXTRACT_H
#define LHASA_EXTRACT_H
#include "filter.h"
#include "options.h"
int test_file_crc(LHAFilter *filter, LHAOptions *options);
int extract_archive(LHAFilter *filter, LHAOptions *options);
int print_archive(LHAFilter *filter, LHAOptions *options);
#endif /* #ifndef LHASA_EXTRACT_H */

View File

@ -0,0 +1,137 @@
/*
Copyright (c) 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include "filter.h"
void lha_filter_init(LHAFilter *filter, LHAReader *reader,
char **filters, unsigned int num_filters)
{
filter->reader = reader;
filter->filters = filters;
filter->num_filters = num_filters;
}
static int match_glob(char *glob, char *str)
{
// Iterate through the string, matching each character against the
// equivalent character from the glob.
while (*str != '\0') {
// When we reach a '*', cut off the remainder of the glob
// and shift forward through the string trying to find
// a point that matches it.
if (*glob == '*') {
if (match_glob(glob + 1, str)) {
return 1;
}
} else if (*glob == '?' || *glob == *str) {
++glob;
} else {
return 0;
}
++str;
}
// We have reached the end of the string to match against.
// Any '*'s left at the end of the string are superfluous and
// can be ignored.
while (*glob == '*') {
++glob;
}
// We now have a successful match only if we have simultaneously
// matched the end of the glob.
return *glob == '\0';
}
static int matches_filter(LHAFilter *filter, LHAFileHeader *header)
{
size_t path_len;
char *path;
unsigned int i;
// Special case: no filters means match all.
if (filter->num_filters == 0) {
return 1;
}
path_len = 0;
if (header->path != NULL) {
path_len += strlen(header->path);
}
if (header->filename != NULL) {
path_len += strlen(header->filename);
}
path = malloc(path_len + 1);
if (path == NULL) {
// TODO?
return 0;
}
path[0] = '\0';
if (header->path != NULL) {
strcat(path, header->path);
}
if (header->filename != NULL) {
strcat(path, header->filename);
}
// Check this path with the list of filters. If one matches,
// we must return true.
for (i = 0; i < filter->num_filters; ++i) {
if (match_glob(filter->filters[i], path)) {
break;
}
}
free(path);
return i < filter->num_filters;
}
LHAFileHeader *lha_filter_next_file(LHAFilter *filter)
{
LHAFileHeader *header;
// Read through headers until we find one that matches.
do {
header = lha_reader_next_file(filter->reader);
} while (header != NULL && !matches_filter(filter, header));
return header;
}

View File

@ -0,0 +1,61 @@
/*
Copyright (c) 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef LHASA_FILTER_H
#define LHASA_FILTER_H
#include "lha_reader.h"
typedef struct _LHAFilter LHAFilter;
struct _LHAFilter {
LHAReader *reader;
char **filters;
unsigned int num_filters;
};
/**
* Initialize a @ref LHAFilter structure to read files from
* the specified @ref LHAReader, applying the specified list of
* filters.
*
* @param filter The filter structure to initialize.
* @param reader The reader object to read files from.
* @param filters List of strings containing glob-style filters
* to apply to the filenames to read.
* @param num_filters Number of filters in the 'filters' array.
*/
void lha_filter_init(LHAFilter *filter, LHAReader *reader,
char **filters, unsigned int num_filters);
/**
* Read the next file from the input stream.
*
* @param filter The filter structure.
* @return File header structure for the next file that
* matches the filters, or NULL for end of file
* or error.
*/
LHAFileHeader *lha_filter_next_file(LHAFilter *filter);
#endif /* #ifndef LHASA_FILTER_H */

View File

@ -0,0 +1,682 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
#include "lha_reader.h"
#include "list.h"
#include "safe.h"
typedef struct {
unsigned int num_files;
unsigned int compressed_length;
unsigned int length;
unsigned int timestamp;
} FileStatistics;
typedef struct {
char *name;
unsigned int width;
void (*handler)(LHAFileHeader *header);
void (*footer)(FileStatistics *stats);
} ListColumn;
// Display OS type:
static char *os_type_to_string(uint8_t os_type)
{
switch (os_type) {
case LHA_OS_TYPE_MSDOS:
return "[MS-DOS]";
case LHA_OS_TYPE_WIN95:
return "[Win9x]";
case LHA_OS_TYPE_WINNT:
return "[WinNT]";
case LHA_OS_TYPE_UNIX:
return "[Unix]";
case LHA_OS_TYPE_OS2:
return "[OS/2]";
case LHA_OS_TYPE_CPM:
return "[CP/M]";
case LHA_OS_TYPE_MACOS:
return "[Mac OS]";
case LHA_OS_TYPE_JAVA:
return "[Java]";
case LHA_OS_TYPE_FLEX:
return "[FLEX]";
case LHA_OS_TYPE_RUNSER:
return "[Runser]";
case LHA_OS_TYPE_TOWNSOS:
return "[TownsOS]";
case LHA_OS_TYPE_OS9:
return "[OS-9]";
case LHA_OS_TYPE_OS9_68K:
return "[OS-9/68K]";
case LHA_OS_TYPE_OS386:
return "[OS-386]";
case LHA_OS_TYPE_HUMAN68K:
return "[Human68K]";
case LHA_OS_TYPE_ATARI:
return "[Atari]";
case LHA_OS_TYPE_AMIGA:
return "[Amiga]";
case LHA_OS_TYPE_UNKNOWN:
return "[generic]";
default:
return "[unknown]";
}
}
// Print Unix file permissions.
static void unix_permissions_print(LHAFileHeader *header)
{
const char *perms = "rwxrwxrwx";
unsigned int i;
if (strcmp(header->compress_method, LHA_COMPRESS_TYPE_DIR) != 0) {
printf("-");
} else if (header->symlink_target != NULL) {
printf("l");
} else {
printf("d");
}
for (i = 0; i < 9; ++i) {
if (header->unix_perms & (1U << (8 - i))) {
printf("%c", perms[i]);
} else {
printf("-");
}
}
}
// Print OS-9 file permissions.
static void os9_permissions_print(LHAFileHeader *header)
{
const char *perms = "sewrewr";
unsigned int i;
if (strcmp(header->compress_method, LHA_COMPRESS_TYPE_DIR) != 0) {
printf("-");
} else {
printf("d");
}
for (i = 0; i < 7; ++i) {
if (header->os9_perms & (1U << (6 - i))) {
printf("%c", perms[i]);
} else {
printf("-");
}
}
printf(" ");
}
// File permissions
static void permission_column_print(LHAFileHeader *header)
{
// Print permissions. If we do not have any permissions to
// print, fall back to printing the OS type.
if (LHA_FILE_HAVE_EXTRA(header, LHA_FILE_OS9_PERMS)) {
os9_permissions_print(header);
} else if (LHA_FILE_HAVE_EXTRA(header, LHA_FILE_UNIX_PERMS)) {
unix_permissions_print(header);
} else {
printf("%-10s", os_type_to_string(header->os_type));
}
}
static void permission_column_footer(FileStatistics *stats)
{
printf(" Total ");
}
static ListColumn permission_column = {
" PERMSSN", 10,
permission_column_print,
permission_column_footer
};
// Unix UID/GID
static void unix_uid_gid_column_print(LHAFileHeader *header)
{
if (LHA_FILE_HAVE_EXTRA(header, LHA_FILE_UNIX_UID_GID)) {
printf("%5i/%-5i", header->unix_uid, header->unix_gid);
} else {
printf(" ");
}
}
static void unix_uid_gid_column_footer(FileStatistics *stats)
{
// The UID/GID column has the total number of files
// listed below it.
if (stats->num_files == 1) {
printf("%5i file ", stats->num_files);
} else {
printf("%5i files", stats->num_files);
}
}
static ListColumn unix_uid_gid_column = {
" UID GID", 11,
unix_uid_gid_column_print,
unix_uid_gid_column_footer
};
// Compressed file size
static void packed_column_print(LHAFileHeader *header)
{
printf("%7lu", (unsigned long) header->compressed_length);
}
static void packed_column_footer(FileStatistics *stats)
{
printf("%7lu", (unsigned long) stats->compressed_length);
}
static ListColumn packed_column = {
" PACKED", 7,
packed_column_print,
packed_column_footer
};
// Uncompressed file size
static void size_column_print(LHAFileHeader *header)
{
printf("%7lu", (unsigned long) header->length);
}
static void size_column_footer(FileStatistics *stats)
{
printf("%7lu", (unsigned long) stats->length);
}
static ListColumn size_column = {
" SIZE", 7,
size_column_print,
size_column_footer
};
// Compression ratio
static float compression_percent(size_t compressed, size_t uncompressed)
{
if (uncompressed > 0) {
return ((float) compressed * 100.0f) / (float) uncompressed;
} else {
return 100.0f;
}
}
static void ratio_column_print(LHAFileHeader *header)
{
if (!strcmp(header->compress_method, "-lhd-")) {
printf("******");
} else {
printf("%5.1f%%", compression_percent(header->compressed_length,
header->length));
}
}
static void ratio_column_footer(FileStatistics *stats)
{
if (stats->length == 0) {
printf("******");
} else {
printf("%5.1f%%", compression_percent(stats->compressed_length,
stats->length));
}
}
static ListColumn ratio_column = {
" RATIO", 6,
ratio_column_print,
ratio_column_footer
};
// Compression method and CRC checksum
static void method_crc_column_print(LHAFileHeader *header)
{
printf("%-5s %04x", header->compress_method, header->crc);
}
static ListColumn method_crc_column = {
"METHOD CRC", 10,
method_crc_column_print
};
// Get the current time.
static time_t get_now_time(void)
{
// For test builds, allow the current time to be overridden using
// an environment variable. This is because the list output can
// change depending on the current date.
#ifdef TEST_BUILD
char *env_val;
unsigned int result;
env_val = getenv("TEST_NOW_TIME");
if (env_val != NULL
&& sscanf(env_val, "%u", &result) == 1) {
return (time_t) result;
}
#endif
return time(NULL);
}
// File timestamp
static void output_timestamp(unsigned int timestamp)
{
const char *months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
struct tm *ts;
time_t tmp;
if (timestamp == 0) {
printf(" ");
return;
}
tmp = (time_t) timestamp;
ts = localtime(&tmp);
// Print date:
printf("%s %2d ", months[ts->tm_mon], ts->tm_mday);
// If this is an old time (more than 6 months), print the year.
// For recent timestamps, print the time.
tmp = get_now_time();
if ((time_t) timestamp > tmp - 6 * 30 * 24 * 60 * 60) {
printf("%02i:%02i", ts->tm_hour, ts->tm_min);
} else {
printf(" %04i", ts->tm_year + 1900);
}
}
static void timestamp_column_print(LHAFileHeader *header)
{
output_timestamp(header->timestamp);
};
static void timestamp_column_footer(FileStatistics *stats)
{
output_timestamp(stats->timestamp);
};
static ListColumn timestamp_column = {
" STAMP", 12,
timestamp_column_print,
timestamp_column_footer
};
// Filename
static void name_column_print(LHAFileHeader *header)
{
if (header->path != NULL) {
safe_printf("%s", header->path);
}
if (header->filename != NULL) {
safe_printf("%s", header->filename);
}
if (header->symlink_target != NULL) {
safe_printf(" -> %s", header->symlink_target);
}
}
static ListColumn name_column = {
" NAME", 20,
name_column_print
};
static ListColumn short_name_column = {
" NAME", 13,
name_column_print
};
// "Separate line" filename display. Used when the 'v' option
// is added.
static void whole_line_name_column_print(LHAFileHeader *header)
{
if (header->path != NULL) {
safe_printf("%s", header->path);
}
if (header->filename != NULL) {
safe_printf("%s", header->filename);
}
// For wide filename mode (-v), | is used as the symlink separator,
// instead of ->. The reason is that this is how symlinks are
// represented within the file headers - the Unix LHA tool only
// does the parsing in normal list mode.
if (header->symlink_target != NULL) {
safe_printf("|%s", header->symlink_target);
}
printf("\n");
}
static ListColumn whole_line_name_column = {
"", 0,
whole_line_name_column_print
};
// Header level column. Used when the 'v' option is added.
static void header_level_column_print(LHAFileHeader *header)
{
printf(" [%i]", header->header_level);
}
static ListColumn header_level_column = {
"", 0,
header_level_column_print
};
// Get the last "real" column in a list of columns. This is the last
// column with a width > 0. Beyond this last column it isn't necessary
// to print any more whitespace.
static ListColumn *last_column(ListColumn **columns)
{
ListColumn *last;
unsigned int i;
last = NULL;
for (i = 0; columns[i] != NULL; ++i) {
if (columns[i]->width != 0) {
last = columns[i];
}
}
return last;
}
// Print the names of the column headings at the top of the file list.
static void print_list_headings(ListColumn **columns)
{
ListColumn *last;
unsigned int i, j;
last = last_column(columns);
for (i = 0; columns[i] != NULL; ++i) {
j = (unsigned) printf("%s", columns[i]->name);
if (columns[i]->width > 0 && columns[i] != last) {
for (; j < columns[i]->width + 1; ++j) {
printf(" ");
}
}
}
printf("\n");
}
// Print separator lines shown at top and bottom of file list.
static void print_list_separators(ListColumn **columns)
{
ListColumn *last;
unsigned int i, j;
last = last_column(columns);
for (i = 0; columns[i] != NULL; ++i) {
for (j = 0; j < columns[i]->width; ++j) {
printf("-");
}
if (columns[i]->width != 0 && columns[i] != last) {
printf(" ");
}
}
printf("\n");
}
// Print a row in the list corresponding to a file.
static void print_columns(ListColumn **columns, LHAFileHeader *header)
{
ListColumn *last;
unsigned int i;
last = last_column(columns);
for (i = 0; columns[i] != NULL; ++i) {
columns[i]->handler(header);
if (columns[i]->width != 0 && columns[i] != last) {
printf(" ");
}
}
printf("\n");
}
// Print footer information shown at end of list (overall file stats)
static void print_footers(ListColumn **columns, FileStatistics *stats)
{
unsigned int i, j, len;
unsigned int num_columns;
// Work out how many columns there are to print, ignoring trailing
// columns that have no footer:
num_columns = 0;
for (i = 0; columns[i] != NULL; ++i) {
++num_columns;
}
while (num_columns > 0 && columns[num_columns-1]->footer == NULL) {
--num_columns;
}
// Print footers for each column.
// Some columns do not have footers: fill in with spaces instead.
for (i = 0; i < num_columns; ++i) {
if (columns[i]->footer != NULL) {
columns[i]->footer(stats);
} else if (i + 1 < num_columns) {
len = strlen(columns[i]->name);
for (j = 0; j < len; ++j) {
printf(" ");
}
}
if (columns[i]->width != 0 && i + 1 < num_columns) {
printf(" ");
}
}
printf("\n");
}
static unsigned int read_file_timestamp(FILE *fstream)
{
struct stat data;
if (fstat(fileno(fstream), &data) != 0) {
return (unsigned int) -1;
}
return (unsigned int) data.st_mtime;
}
// List contents of file, using the specified columns.
// Different columns are provided for basic and verbose modes.
static void list_file_contents(LHAFilter *filter, FILE *fstream,
LHAOptions *options, ListColumn **columns)
{
FileStatistics stats;
if (options->quiet < 2) {
print_list_headings(columns);
print_list_separators(columns);
}
stats.num_files = 0;
stats.length = 0;
stats.compressed_length = 0;
stats.timestamp = read_file_timestamp(fstream);
for (;;) {
LHAFileHeader *header;
header = lha_filter_next_file(filter);
if (header == NULL) {
break;
}
print_columns(columns, header);
++stats.num_files;
stats.length += header->length;
stats.compressed_length += header->compressed_length;
}
if (options->quiet < 2) {
print_list_separators(columns);
print_footers(columns, &stats);
}
}
// Used for lha -l:
static ListColumn *normal_column_headers[] = {
&permission_column,
&unix_uid_gid_column,
&size_column,
&ratio_column,
&timestamp_column,
&name_column,
NULL
};
// Used for lha -lv:
static ListColumn *normal_column_headers_verbose[] = {
&whole_line_name_column,
&permission_column,
&unix_uid_gid_column,
&size_column,
&ratio_column,
&timestamp_column,
&header_level_column,
NULL
};
// lha -l command.
void list_file_basic(LHAFilter *filter, LHAOptions *options, FILE *fstream)
{
ListColumn **headers;
if (options->verbose) {
headers = normal_column_headers_verbose;
} else {
headers = normal_column_headers;
}
list_file_contents(filter, fstream, options, headers);
}
// Used for lha -v:
static ListColumn *verbose_column_headers[] = {
&permission_column,
&unix_uid_gid_column,
&packed_column,
&size_column,
&ratio_column,
&method_crc_column,
&timestamp_column,
&short_name_column,
NULL
};
// Used for lha -vv:
static ListColumn *verbose_column_headers_verbose[] = {
&whole_line_name_column,
&permission_column,
&unix_uid_gid_column,
&packed_column,
&size_column,
&ratio_column,
&method_crc_column,
&timestamp_column,
&header_level_column,
NULL
};
// lha -v command.
void list_file_verbose(LHAFilter *filter, LHAOptions *options, FILE *fstream)
{
ListColumn **headers;
if (options->verbose) {
headers = verbose_column_headers_verbose;
} else {
headers = verbose_column_headers;
}
list_file_contents(filter, fstream, options, headers);
}

View File

@ -0,0 +1,31 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef LHASA_LIST_H
#define LHASA_LIST_H
#include "filter.h"
#include "options.h"
void list_file_basic(LHAFilter *filter, LHAOptions *options, FILE *fstream);
void list_file_verbose(LHAFilter *filter, LHAOptions *options, FILE *fstream);
#endif /* #ifndef LHASA_LIST_H */

View File

@ -0,0 +1,275 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "lib/lha_arch.h"
#include "lha_reader.h"
#include "config.h"
#include "extract.h"
#include "list.h"
typedef enum {
MODE_UNKNOWN,
MODE_LIST,
MODE_LIST_VERBOSE,
MODE_CRC_CHECK,
MODE_EXTRACT,
MODE_PRINT
} ProgramMode;
static void help_page(char *progname)
{
printf(
PACKAGE_NAME " v" PACKAGE_VERSION " command line LHA tool "
"- Copyright (C) 2011,2012 Simon Howard\n"
"usage: %s [-]{lvtxe[q{num}][finv]}[w=<dir>] archive_file [file...]\n"
"commands: options:\n"
" l,v List / Verbose List f Force overwrite (no prompt)\n"
" t Test file CRC in archive i Ignore directory path\n"
" x,e Extract from archive n Perform dry run\n"
" p Print to stdout from archive q{num} Quiet mode\n"
" v Verbose\n"
" w=<dir> Specify extract directory\n"
, progname);
exit(-1);
}
static int do_command(ProgramMode mode, char *filename,
LHAOptions *options,
char **filters, unsigned int num_filters)
{
FILE *fstream;
LHAInputStream *stream;
LHAReader *reader;
LHAFilter filter;
int result;
if (!strcmp(filename, "-")) {
fstream = stdin;
} else {
fstream = fopen(filename, "rb");
if (fstream == NULL) {
fprintf(stderr, "LHa: Error: %s %s\n",
filename, strerror(errno));
exit(-1);
}
}
stream = lha_input_stream_from_FILE(fstream);
reader = lha_reader_new(stream);
lha_filter_init(&filter, reader, filters, num_filters);
result = 1;
switch (mode) {
case MODE_LIST:
list_file_basic(&filter, options, fstream);
break;
case MODE_LIST_VERBOSE:
list_file_verbose(&filter, options, fstream);
break;
case MODE_CRC_CHECK:
result = test_file_crc(&filter, options);
break;
case MODE_EXTRACT:
result = extract_archive(&filter, options);
break;
case MODE_PRINT:
result = print_archive(&filter, options);
break;
case MODE_UNKNOWN:
break;
}
lha_reader_free(reader);
lha_input_stream_free(stream);
fclose(fstream);
return result;
}
static void init_options(LHAOptions *options)
{
options->overwrite_policy = LHA_OVERWRITE_PROMPT;
options->quiet = 0;
options->verbose = 0;
options->dry_run = 0;
options->extract_path = NULL;
options->use_path = 1;
}
// Determine the program mode from the first character of the command
// argument.
static ProgramMode mode_for_char(char c)
{
switch (c) {
case 'l':
return MODE_LIST;
case 'v':
return MODE_LIST_VERBOSE;
case 't':
return MODE_CRC_CHECK;
case 'e':
case 'x':
return MODE_EXTRACT;
case 'p':
return MODE_PRINT;
default:
return MODE_UNKNOWN;
}
}
// Parse the option flags from the command argument.
static int parse_options(char *arg, LHAOptions *options)
{
for (; *arg != '\0'; ++arg) {
switch (*arg) {
// Force overwrite of existing files.
case 'f':
options->overwrite_policy = LHA_OVERWRITE_ALL;
break;
// -i option turns off paths for extract.
case 'i':
options->use_path = 0;
break;
// Dry run?
case 'n':
options->dry_run = 1;
break;
// Quiet mode parsing. The quiet 'level' can be
// specified - if the level is omitted, level 2
// is implied. All quiet mode options imply
// -f (overwrite without confirmation).
case 'q':
if (arg[1] >= '0' && arg[1] <= '9') {
++arg;
options->quiet = *arg - '0';
} else {
options->quiet = 2;
}
options->overwrite_policy = LHA_OVERWRITE_ALL;
break;
// Verbose mode.
case 'v':
options->verbose = 1;
break;
// Specify extract directory: must be last option
// specified. Optional '=' separator.
case 'w':
++arg;
if (*arg == '=') {
++arg;
}
options->extract_path = arg;
arg += strlen(arg) - 1;
break;
default:
return 0;
}
}
return 1;
}
/**
* Parse the command line options, initializing the options structure
* used by the main program code.
*
* @param cmd The 'command' argument (first command line option).
* @param mode Pointer to variable to store program mode.
* @param options Pointer to options structure to initialize.
* @return Non-zero if successful.
*/
static int parse_command_line(char *cmd, ProgramMode *mode,
LHAOptions *options)
{
// Parse program mode argument. Initial '-' is ignored.
if (*cmd == '-') {
++cmd;
}
*mode = mode_for_char(*cmd);
if (*mode == MODE_UNKNOWN) {
return 0;
}
// Parse remaining options.
if (!parse_options(cmd + 1, options)) {
return 0;
}
return 1;
}
int main(int argc, char *argv[])
{
ProgramMode mode;
LHAOptions options;
#ifdef TEST_BUILD
// When running tests, give output to stdout in binary mode;
// on Windows, this gives the expected output (which was
// constructed for Unix):
lha_arch_set_binary(stdout);
#endif
// Parse the command line options and run command.
// As a shortcut, a single argument can be provided to list the
// contents of an archive ("lha foo.lzh" == "lha l foo.lzh").
init_options(&options);
if (argc >= 3 && parse_command_line(argv[1], &mode, &options)) {
return !do_command(mode, argv[2], &options,
argv + 3, argc - 3);
} else if (argc == 2) {
return !do_command(MODE_LIST, argv[1], &options, NULL, 0);
} else {
help_page(argv[0]);
return 0;
}
}

View File

@ -0,0 +1,64 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef LHASA_OPTIONS_H
#define LHASA_OPTIONS_H
typedef enum {
LHA_OVERWRITE_PROMPT,
LHA_OVERWRITE_SKIP,
LHA_OVERWRITE_ALL
} LHAOverwritePolicy;
// Options structure. Populated from command line arguments.
typedef struct {
// Policy to take when extracting files and a file
// already exists.
LHAOverwritePolicy overwrite_policy;
// "Quiet" level. Normal operation is level 0.
int quiet;
// "Verbose" mode.
int verbose;
// If true, just perform a dry run of the operations that
// would normally be performed, printing messages.
int dry_run;
// If not NULL, specifies a path into which to extract files.
char *extract_path;
// If true, use the directory path for files - otherwise,
// the directory path is ignored.
int use_path;
} LHAOptions;
#endif /* #ifndef LHASA_OPTIONS_H */

View File

@ -0,0 +1,107 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include "lib/lha_arch.h"
// Routines for safe terminal output.
//
// Data in LHA files (eg. filenames) may contain malicious string
// data. If printed carelessly, this can include terminal emulator
// commands that cause very unpleasant things to occur. For more
// information, see:
//
// http://marc.info/?l=bugtraq&m=104612710031920&w=2
//
// Quote:
// > Many of the features supported by popular terminal emulator
// > software can be abused when un-trusted data is displayed on the
// > screen. The impact of this abuse can range from annoying screen
// > garbage to a complete system compromise.
// TODO: This may not be ideal behavior for handling files with
// names that contain Unicode characters.
static void safe_output(FILE *stream, unsigned char *str)
{
unsigned char *p;
for (p = str; *p != '\0'; ++p) {
// Accept only plain ASCII characters.
// Control characters (0x00-0x1f) are rejected,
// as is 0x7f and all characters in the upper range.
if (*p < 0x20 || *p >= 0x7f) {
*p = '?';
}
}
fprintf(stream, "%s", str);
}
// Version of printf() that strips out any potentially malicious
// characters from the outputted string.
// Note: all escape characters are considered potentially malicious,
// including newline characters.
int safe_fprintf(FILE *stream, char *format, ...)
{
va_list args;
int result;
unsigned char *str;
va_start(args, format);
result = lha_arch_vasprintf((char **) &str, format, args);
if (str == NULL) {
exit(-1);
}
safe_output(stream, str);
free(str);
va_end(args);
return result;
}
int safe_printf(char *format, ...)
{
va_list args;
int result;
unsigned char *str;
va_start(args, format);
result = lha_arch_vasprintf((char **) &str, format, args);
if (str == NULL) {
exit(-1);
}
safe_output(stdout, str);
free(str);
va_end(args);
return result;
}

View File

@ -0,0 +1,28 @@
/*
Copyright (c) 2011, 2012, Simon Howard
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef LHASA_SAFE_H
#define LHASA_SAFE_H
int safe_fprintf(FILE *stream, char *format, ...);
int safe_printf(char *format, ...);
#endif /* #ifndef LHASA_SAFE_H */