521 lines
15 KiB
C
521 lines
15 KiB
C
/*
|
|
* Mode for viewing archive files for QEmacs.
|
|
*
|
|
* Copyright (c) 2002-2017 Charlie Gordon.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "qe.h"
|
|
|
|
/*---------------- Archivers ----------------*/
|
|
|
|
typedef struct ArchiveType ArchiveType;
|
|
|
|
struct ArchiveType {
|
|
const char *name; /* name of archive format */
|
|
const char *magic; /* magic signature */
|
|
int magic_size;
|
|
const char *extensions;
|
|
const char *list_cmd; /* list archive contents to stdout */
|
|
const char *extract_cmd; /* extract archive element to stdout */
|
|
int sf_flags;
|
|
struct ArchiveType *next;
|
|
};
|
|
|
|
static ArchiveType archive_type_array[] = {
|
|
{ "tar", NULL, 0, "tar|tar.Z|tgz|tar.gz|tbz|tbz2|tar.bz2|tar.bzip2|"
|
|
"txz|tar.xz|tlz|tar.lzma|taz", "tar tvf $1" },
|
|
{ "zip", "PK\003\004", 4, "zip|ZIP|jar|apk|bbb", "unzip -l $1" },
|
|
{ "rar", NULL, 0, "rar|RAR", "unrar l $1" },
|
|
{ "arj", NULL, 0, "arj|ARJ", "unarj l $1" },
|
|
{ "cab", NULL, 0, "cab", "cabextract -l $1" },
|
|
{ "7zip", NULL, 0, "7z", "7z l $1" },
|
|
{ "ar", NULL, 0, "a|ar", "ar -tv $1" },
|
|
{ "xar", NULL, 0, "xar|pkg", "xar -tvf $1" },
|
|
{ "zoo", NULL, 0, "zoo", "zoo l $1" },
|
|
{ "lha", NULL, 0, "lha", "lha -l $1" },
|
|
};
|
|
|
|
static ArchiveType *archive_types;
|
|
|
|
static ArchiveType *find_archive_type(const char *filename,
|
|
const u8 *buf, int buf_size)
|
|
{
|
|
char rname[MAX_FILENAME_SIZE];
|
|
ArchiveType *atp;
|
|
|
|
/* File extension based test */
|
|
reduce_filename(rname, sizeof(rname), get_basename(filename));
|
|
for (atp = archive_types; atp; atp = atp->next) {
|
|
if (atp->magic_size && atp->magic_size <= buf_size
|
|
&& !memcmp(atp->magic, buf, atp->magic_size))
|
|
return atp;
|
|
if (match_extension(rname, atp->extensions))
|
|
return atp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int archive_mode_probe(ModeDef *mode, ModeProbeData *p)
|
|
{
|
|
ArchiveType *atp = find_archive_type(p->filename, p->buf, p->buf_size);
|
|
|
|
if (atp) {
|
|
if (p->b && p->b->data_type == mode->data_type) {
|
|
/* buffer loaded, re-selecting mode causes buffer reload */
|
|
return 0;//9
|
|
} else {
|
|
/* buffer not yet loaded */
|
|
return 85;//70
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qe_shell_subst(char *buf, int size, const char *cmd,
|
|
const char *arg1, const char *arg2)
|
|
{
|
|
buf_t outbuf, *out;
|
|
|
|
out = buf_init(&outbuf, buf, size);
|
|
while (*cmd) {
|
|
if (*cmd == '$') {
|
|
if (cmd[1] == '1' && arg1) {
|
|
buf_put_byte(out, '\'');
|
|
buf_puts(out, arg1);
|
|
buf_put_byte(out, '\'');
|
|
cmd += 2;
|
|
continue;
|
|
}
|
|
if (cmd[1] == '2' && arg2) {
|
|
buf_put_byte(out, '\'');
|
|
buf_puts(out, arg2);
|
|
buf_put_byte(out, '\'');
|
|
cmd += 2;
|
|
continue;
|
|
}
|
|
}
|
|
buf_put_byte(out, *cmd++);
|
|
}
|
|
return out->len;
|
|
}
|
|
|
|
static int file_read_block(EditBuffer *b, FILE *f1, u8 *buf, int buf_size)
|
|
{
|
|
FILE *f = f1;
|
|
int nread = 0;
|
|
|
|
if (!f)
|
|
f = fopen(b->filename, "rb");
|
|
if (f)
|
|
nread = fread(buf, 1, buf_size, f);
|
|
if (f && !f1)
|
|
fclose(f);
|
|
return nread;
|
|
}
|
|
|
|
static int archive_buffer_load(EditBuffer *b, FILE *f)
|
|
{
|
|
/* Launch subprocess to list archive contents */
|
|
char cmd[1024];
|
|
ArchiveType *atp;
|
|
u8 buf[256];
|
|
int buf_size;
|
|
|
|
buf_size = file_read_block(b, f, buf, sizeof(buf));
|
|
atp = find_archive_type(b->filename, buf, buf_size);
|
|
if (atp) {
|
|
b->data_type_name = atp->name;
|
|
eb_clear(b);
|
|
eb_printf(b, " Directory of %s archive %s\n",
|
|
atp->name, b->filename);
|
|
qe_shell_subst(cmd, sizeof(cmd), atp->list_cmd, b->filename, NULL);
|
|
new_shell_buffer(b, NULL, get_basename(b->filename), NULL, NULL, cmd,
|
|
atp->sf_flags | SF_INFINITE | SF_BUFED_MODE);
|
|
|
|
/* XXX: should check for archiver error */
|
|
/* XXX: should delay BF_SAVELOG until buffer is fully loaded */
|
|
b->flags |= BF_READONLY;
|
|
|
|
return 0;
|
|
} else {
|
|
eb_printf(b, "Cannot find archiver\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int archive_buffer_save(EditBuffer *b, int start, int end,
|
|
const char *filename)
|
|
{
|
|
/* XXX: prevent saving parsed contents to archive file */
|
|
return -1;
|
|
}
|
|
|
|
static void archive_buffer_close(EditBuffer *b)
|
|
{
|
|
/* XXX: kill process? */
|
|
}
|
|
|
|
static EditBufferDataType archive_data_type = {
|
|
"archive",
|
|
archive_buffer_load,
|
|
archive_buffer_save,
|
|
archive_buffer_close,
|
|
NULL, /* next */
|
|
};
|
|
|
|
static ModeDef archive_mode = {
|
|
.name = "archive",
|
|
.mode_probe = archive_mode_probe,
|
|
.data_type = &archive_data_type,
|
|
};
|
|
|
|
static int archive_init(void)
|
|
{
|
|
int i;
|
|
|
|
/* copy and patch text_mode */
|
|
memcpy(&archive_mode, &text_mode, sizeof(ModeDef));
|
|
archive_mode.name = "archive";
|
|
archive_mode.mode_probe = archive_mode_probe;
|
|
archive_mode.data_type = &archive_data_type;
|
|
|
|
for (i = 1; i < countof(archive_type_array); i++) {
|
|
archive_type_array[i - 1].next = archive_type_array + i;
|
|
}
|
|
archive_types = archive_type_array;
|
|
|
|
eb_register_data_type(&archive_data_type);
|
|
qe_register_mode(&archive_mode, MODEF_DATATYPE | MODEF_SHELLPROC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*---------------- Compressors ----------------*/
|
|
|
|
typedef struct CompressType CompressType;
|
|
|
|
struct CompressType {
|
|
const char *name; /* name of compressed format */
|
|
const char *magic; /* magic signature */
|
|
int magic_size;
|
|
const char *extensions;
|
|
const char *load_cmd; /* uncompress file to stdout */
|
|
const char *save_cmd; /* compress to file from stdin */
|
|
int sf_flags;
|
|
struct CompressType *next;
|
|
};
|
|
|
|
static CompressType compress_type_array[] = {
|
|
{ "gzip", NULL, 0, "gz", "gunzip -c $1", "gzip > $1" },
|
|
{ "bzip2", NULL, 0, "bz2|bzip2", "bunzip2 -c $1", "bzip2 > $1" },
|
|
{ "compress", NULL, 0, "Z", "uncompress -c < $1", "compress > $1" },
|
|
{ "LZMA", NULL, 0, "lzma", "unlzma -c $1", "lzma > $1" },
|
|
{ "XZ", NULL, 0, "xz", "unxz -c $1", "xz > $1" },
|
|
{ "BinHex", NULL, 0, "hqx", "binhex decode -o /tmp/qe-$$ $1 && "
|
|
"cat /tmp/qe-$$ ; rm -f /tmp/qe-$$", NULL },
|
|
{ "sqlite", "SQLite format 3\0", 16, NULL, "sqlite3 $1 .dump", NULL },
|
|
{ "bplist", "bplist00", 8, "plist", "plutil -p $1", NULL },
|
|
// { "bplist", "bplist00", 8, "plist", "plutil -convert xml1 -o - $1", NULL },
|
|
// { "jpeg", NULL, 0, "jpg", "jp2a --height=35 --background=dark $1", NULL, SF_COLOR },
|
|
// { "image", NULL, 0, "bmp", "img2txt -f utf8 $1", NULL, SF_COLOR },
|
|
{ "pdf", NULL, 0, "pdf", "pstotext $1", NULL },
|
|
};
|
|
|
|
static CompressType *compress_types;
|
|
|
|
static CompressType *find_compress_type(const char *filename,
|
|
const u8 *buf, int buf_size)
|
|
{
|
|
char rname[MAX_FILENAME_SIZE];
|
|
CompressType *ctp;
|
|
|
|
/* File extension based test */
|
|
reduce_filename(rname, sizeof(rname), get_basename(filename));
|
|
for (ctp = compress_types; ctp; ctp = ctp->next) {
|
|
if (ctp->magic && ctp->magic_size && ctp->magic_size <= buf_size
|
|
&& !memcmp(ctp->magic, buf, ctp->magic_size))
|
|
return ctp;
|
|
if (match_extension(rname, ctp->extensions))
|
|
return ctp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int compress_mode_probe(ModeDef *mode, ModeProbeData *p)
|
|
{
|
|
CompressType *ctp = find_compress_type(p->filename, p->buf, p->buf_size);
|
|
|
|
if (ctp) {
|
|
if (p->b && p->b->data_type == mode->data_type) {
|
|
/* buffer loaded, re-selecting mode causes buffer reload */
|
|
return 0;//9;
|
|
} else {
|
|
/* buffer not yet loaded */
|
|
return 82;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int compress_buffer_load(EditBuffer *b, FILE *f)
|
|
{
|
|
/* Launch subprocess to expand compressed contents */
|
|
char cmd[1024];
|
|
CompressType *ctp;
|
|
u8 buf[256];
|
|
int buf_size;
|
|
|
|
buf_size = file_read_block(b, f, buf, sizeof(buf));
|
|
ctp = find_compress_type(b->filename, buf, buf_size);
|
|
if (ctp) {
|
|
b->data_type_name = ctp->name;
|
|
eb_clear(b);
|
|
qe_shell_subst(cmd, sizeof(cmd), ctp->load_cmd, b->filename, NULL);
|
|
new_shell_buffer(b, NULL, get_basename(b->filename), NULL, NULL, cmd,
|
|
ctp->sf_flags | SF_INFINITE | SF_AUTO_CODING | SF_AUTO_MODE);
|
|
/* XXX: should check for archiver error */
|
|
/* XXX: should delay BF_SAVELOG until buffer is fully loaded */
|
|
b->flags |= BF_READONLY;
|
|
|
|
return 0;
|
|
} else {
|
|
eb_printf(b, "cannot find compressor\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int compress_buffer_save(EditBuffer *b, int start, int end,
|
|
const char *filename)
|
|
{
|
|
/* XXX: should recompress contents to compressed file */
|
|
return -1;
|
|
}
|
|
|
|
static void compress_buffer_close(EditBuffer *b)
|
|
{
|
|
/* XXX: kill process? */
|
|
}
|
|
|
|
static EditBufferDataType compress_data_type = {
|
|
"compress",
|
|
compress_buffer_load,
|
|
compress_buffer_save,
|
|
compress_buffer_close,
|
|
NULL, /* next */
|
|
};
|
|
|
|
static ModeDef compress_mode = {
|
|
.name = "compress",
|
|
.mode_probe = compress_mode_probe,
|
|
.data_type = &compress_data_type,
|
|
};
|
|
|
|
static int compress_init(void)
|
|
{
|
|
int i;
|
|
|
|
/* copy and patch text_mode */
|
|
memcpy(&compress_mode, &text_mode, sizeof(ModeDef));
|
|
compress_mode.name = "compress";
|
|
compress_mode.mode_probe = compress_mode_probe;
|
|
compress_mode.data_type = &compress_data_type;
|
|
|
|
for (i = 1; i < countof(compress_type_array); i++) {
|
|
compress_type_array[i - 1].next = compress_type_array + i;
|
|
}
|
|
compress_types = compress_type_array;
|
|
|
|
eb_register_data_type(&compress_data_type);
|
|
qe_register_mode(&compress_mode, MODEF_DATATYPE | MODEF_SHELLPROC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*---------------- Wget ----------------*/
|
|
|
|
static ModeDef wget_mode;
|
|
|
|
static int wget_mode_probe(ModeDef *mode, ModeProbeData *p)
|
|
{
|
|
if (strstart(p->real_filename, "http:", NULL)
|
|
|| strstart(p->real_filename, "https:", NULL)
|
|
|| strstart(p->real_filename, "ftp:", NULL)) {
|
|
if (p->b && p->b->data_type == mode->data_type) {
|
|
/* buffer loaded, re-selecting mode causes buffer reload */
|
|
return 9;
|
|
} else {
|
|
/* buffer not yet loaded */
|
|
return 90;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wget_buffer_load(EditBuffer *b, FILE *f)
|
|
{
|
|
/* Launch wget subprocess to retrieve contents */
|
|
char cmd[1024];
|
|
|
|
eb_clear(b);
|
|
qe_shell_subst(cmd, sizeof(cmd), "wget -q -O - $1", b->filename, NULL);
|
|
new_shell_buffer(b, NULL, get_basename(b->filename), NULL, NULL, cmd,
|
|
SF_INFINITE | SF_AUTO_CODING | SF_AUTO_MODE);
|
|
/* XXX: should refilter by content type */
|
|
/* XXX: should have a way to keep http headers --save-headers */
|
|
/* XXX: should check for wget error */
|
|
/* XXX: should delay BF_SAVELOG until buffer is fully loaded */
|
|
b->flags |= BF_READONLY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wget_buffer_save(EditBuffer *b, int start, int end,
|
|
const char *filename)
|
|
{
|
|
/* XXX: should put contents back to web server */
|
|
return -1;
|
|
}
|
|
|
|
static void wget_buffer_close(EditBuffer *b)
|
|
{
|
|
/* XXX: kill process? */
|
|
}
|
|
|
|
static EditBufferDataType wget_data_type = {
|
|
"wget",
|
|
wget_buffer_load,
|
|
wget_buffer_save,
|
|
wget_buffer_close,
|
|
NULL, /* next */
|
|
};
|
|
|
|
static int wget_init(void)
|
|
{
|
|
/* copy and patch text_mode */
|
|
memcpy(&wget_mode, &text_mode, sizeof(ModeDef));
|
|
wget_mode.name = "wget";
|
|
wget_mode.mode_probe = wget_mode_probe;
|
|
wget_mode.data_type = &wget_data_type;
|
|
|
|
eb_register_data_type(&wget_data_type);
|
|
qe_register_mode(&wget_mode, MODEF_DATATYPE | MODEF_SHELLPROC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*---------------- Manual pages ----------------*/
|
|
|
|
static ModeDef man_mode;
|
|
|
|
static int man_mode_probe(ModeDef *mode, ModeProbeData *p)
|
|
{
|
|
if (match_extension(p->real_filename,
|
|
"1.gz|1m.gz|1ssl.gz|1tcl.gz|2.gz|3.gz|3o.gz|3ssl.gz|"
|
|
"4.gz|5.gz|5ssl.gz|6.gz|7.gz|7ssl.gz|8.gz|9.gz")) {
|
|
goto has_man;
|
|
}
|
|
|
|
if (match_extension(p->real_filename,
|
|
"1|1m|1ssl|1tcl|2|3|3o|3ssl|4|5|5ssl|6|7|7ssl|8|9|"
|
|
"n|ntcl|man|roff")
|
|
// && !strchr(p->filename, '.')
|
|
&& (p->buf[0] == '.' ||
|
|
(p->buf[0] == '\n' && p->buf[1] == '.') ||
|
|
!memcmp(p->buf, "'\\\"", 3) ||
|
|
!memcmp(p->buf, "'''", 3) ||
|
|
!memcmp(p->buf, "\\\"", 2))) {
|
|
has_man:
|
|
if (p->b && p->b->data_type == mode->data_type) {
|
|
/* buffer loaded, re-selecting mode causes buffer reload */
|
|
return 9;
|
|
} else {
|
|
/* buffer not yet loaded */
|
|
return 90;
|
|
}
|
|
}
|
|
|
|
if (!memcmp(p->buf, ".tr *\\(**", 9)
|
|
|| !memcmp(p->buf, ".\\\" ", 4))
|
|
goto has_man;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int man_buffer_load(EditBuffer *b, FILE *f)
|
|
{
|
|
/* Launch man subprocess to format manual page */
|
|
char cmd[1024];
|
|
|
|
eb_clear(b);
|
|
qe_shell_subst(cmd, sizeof(cmd), "man $1", b->filename, NULL);
|
|
new_shell_buffer(b, NULL, get_basename(b->filename), NULL, NULL, cmd,
|
|
SF_COLOR | SF_INFINITE);
|
|
/* XXX: should check for man error */
|
|
/* XXX: should delay BF_SAVELOG until buffer is fully loaded */
|
|
b->flags |= BF_READONLY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int man_buffer_save(EditBuffer *b, int start, int end,
|
|
const char *filename)
|
|
{
|
|
/* XXX: should put contents back to web server */
|
|
return -1;
|
|
}
|
|
|
|
static void man_buffer_close(EditBuffer *b)
|
|
{
|
|
/* XXX: kill process? */
|
|
}
|
|
|
|
static EditBufferDataType man_data_type = {
|
|
"man",
|
|
man_buffer_load,
|
|
man_buffer_save,
|
|
man_buffer_close,
|
|
NULL, /* next */
|
|
};
|
|
|
|
static int man_init(void)
|
|
{
|
|
/* copy and patch text_mode */
|
|
memcpy(&man_mode, &text_mode, sizeof(ModeDef));
|
|
man_mode.name = "man";
|
|
man_mode.mode_probe = man_mode_probe;
|
|
man_mode.data_type = &man_data_type;
|
|
|
|
eb_register_data_type(&man_data_type);
|
|
qe_register_mode(&man_mode, MODEF_DATATYPE | MODEF_SHELLPROC);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*---------------- Initialization ----------------*/
|
|
|
|
static int archive_compress_init(void)
|
|
{
|
|
return archive_init() ||
|
|
compress_init() ||
|
|
wget_init() ||
|
|
man_init();
|
|
}
|
|
|
|
qe_module_init(archive_compress_init);
|