pidgin/pidgin

closing merged branch
port-changes-from-branch-2.x.y-to-default
2020-02-03, Gary Kramlich
2f836435c33c
closing merged branch
/* untar.c */
/*#define VERSION "1.4"*/
/* DESCRIPTION:
* Untar extracts files from an uncompressed tar archive, or one which
* has been compressed with gzip. Usually such archives will have file
* names that end with ".tar" or ".tgz" respectively, although untar
* doesn't depend on any naming conventions. For a summary of the
* command-line options, run untar with no arguments.
*
* HOW TO COMPILE:
* Untar doesn't require any special libraries or compile-time flags.
* A simple "cc untar.c -o untar" (or the local equivalent) is
* sufficient. Even "make untar" works, without needing a Makefile.
* For Microsoft Visual C++, the command is "cl /D_WEAK_POSIX untar.c"
* (for 32 bit compilers) or "cl /F 1400 untar.c" (for 16-bit).
*
* IF YOU SEE COMPILER WARNINGS, THAT'S NORMAL; you can ignore them.
* Most of the warnings could be eliminated by adding #include <string.h>
* but that isn't portable -- some systems require <strings.h> and
* <malloc.h>, for example. Because <string.h> isn't quite portable,
* and isn't really necessary in the context of this program, it isn't
* included.
*
* PORTABILITY:
* Untar only requires the <stdio.h> header. It uses old-style function
* definitions. It opens all files in binary mode. Taken together,
* this means that untar should compile & run on just about anything.
*
* If your system supports the POSIX chmod(2), utime(2), link(2), and
* symlink(2) calls, then you may wish to compile with -D_POSIX_SOURCE,
* which will enable untar to use those system calls to restore the
* timestamp and permissions of the extracted files, and restore links.
* (For Linux, _POSIX_SOURCE is always defined.)
*
* For systems which support some POSIX features but not enough to support
* -D_POSIX_SOURCE, you might be able to use -D_WEAK_POSIX. This allows
* untar to restore time stamps and file permissions, but not links.
* This should work for Microsoft systems, and hopefully others as well.
*
* AUTHOR & COPYRIGHT INFO:
* Written by Steve Kirkendall, kirkenda@cs.pdx.edu
* Placed in public domain, 6 October 1995
*
* Portions derived from inflate.c -- Not copyrighted 1992 by Mark Adler
* version c10p1, 10 January 1993
*
* Altered by Herman Bloggs <hermanator12002@yahoo.com>
* April 4, 2003
* Changes: Stripped out gz compression code, added better interface for
* untar.
*/
#include <windows.h>
#include <stdio.h>
#include <io.h>
#include <string.h>
#include <stdlib.h>
#ifndef SEEK_SET
# define SEEK_SET 0
#endif
#ifdef _WEAK_POSIX
# ifndef _POSIX_SOURCE
# define _POSIX_SOURCE
# endif
#endif
#ifdef _POSIX_SOURCE
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/utime.h>
# ifdef _WEAK_POSIX
# define mode_t int
# else
# include <unistd.h>
# endif
#endif
#include "debug.h"
#include "untar.h"
#include <glib.h>
#include <glib/gstdio.h>
#define untar_error( error, args... ) purple_debug(PURPLE_DEBUG_ERROR, "untar", error, ## args )
#define untar_warning( warning, args... ) purple_debug(PURPLE_DEBUG_WARNING, "untar", warning, ## args )
#define untar_verbose( args... ) purple_debug(PURPLE_DEBUG_INFO, "untar", ## args )
#define WSIZE 32768 /* size of decompression buffer */
#define TSIZE 512 /* size of a "tape" block */
#define CR 13 /* carriage-return character */
#define LF 10 /* line-feed character */
typedef unsigned char Uchar_t;
typedef unsigned short Ushort_t;
typedef unsigned long Ulong_t;
typedef struct
{
char filename[100]; /* 0 name of next file */
char mode[8]; /* 100 Permissions and type (octal digits) */
char owner[8]; /* 108 Owner ID (ignored) */
char group[8]; /* 116 Group ID (ignored) */
char size[12]; /* 124 Bytes in file (octal digits) */
char mtime[12]; /* 136 Modification time stamp (octal digits)*/
char checksum[8]; /* 148 Header checksum (ignored) */
char type; /* 156 File type (see below) */
char linkto[100]; /* 157 Linked-to name */
char brand[8]; /* 257 Identifies tar version (ignored) */
char ownername[32]; /* 265 Name of owner (ignored) */
char groupname[32]; /* 297 Name of group (ignored) */
char devmajor[8]; /* 329 Device major number (ignored) */
char defminor[8]; /* 337 Device minor number (ignored) */
char prefix[155]; /* 345 Prefix of name (optional) */
char RESERVED[12]; /* 500 Pad header size to 512 bytes */
} tar_t;
#define ISREGULAR(hdr) ((hdr).type < '1' || (hdr).type > '6')
Uchar_t slide[WSIZE];
static const char *inname = NULL; /* name of input archive */
static FILE *infp = NULL; /* input byte stream */
static FILE *outfp = NULL; /* output stream, for file currently being extracted */
static Ulong_t outsize = 0; /* number of bytes remainin in file currently being extracted */
static int didabs = 0; /* were any filenames affected by the absence of -p? */
static untar_opt untarops = 0; /* Untar options */
/* Options checked during untar process */
#define LISTING (untarops & UNTAR_LISTING) /* 1 if listing, 0 if extracting */
#define QUIET (untarops & UNTAR_QUIET) /* 1 to write nothing to stdout, 0 for normal chatter */
#define VERBOSE (untarops & UNTAR_VERBOSE) /* 1 to write extra information to stdout */
#define FORCE (untarops & UNTAR_FORCE) /* 1 to overwrite existing files, 0 to skip them */
#define ABSPATH (untarops & UNTAR_ABSPATH) /* 1 to allow leading '/', 0 to strip leading '/' */
#define CONVERT (untarops & UNTAR_CONVERT) /* 1 to convert newlines, 0 to leave unchanged */
/*----------------------------------------------------------------------------*/
/* create a file for writing. If necessary, create the directories leading up
* to that file as well.
*/
static FILE *createpath(name)
char *name; /* pathname of file to create */
{
FILE *fp;
int i;
/* if we aren't allowed to overwrite and this file exists, return NULL */
if (!FORCE && access(name, 0) == 0)
{
untar_warning("%s: exists, will not overwrite without \"FORCE option\"\n", name);
return NULL;
}
/* first try creating it the easy way */
fp = g_fopen(name, CONVERT ? "w" : "wb");
if (fp)
return fp;
/* Else try making all of its directories, and then try creating
* the file again.
*/
for (i = 0; name[i]; i++)
{
/* If this is a slash, then temporarily replace the '/'
* with a '\0' and do a mkdir() on the resulting string.
* Ignore errors for now.
*/
if (name[i] == '/')
{
name[i] = '\0';
(void)g_mkdir(name, 0777);
name[i] = '/';
}
}
fp = g_fopen(name, CONVERT ? "w" : "wb");
if (!fp)
untar_error("Error opening: %s\n", name);
return fp;
}
/* Create a link, or copy a file. If the file is copied (not linked) then
* give a warning.
*/
static void linkorcopy(src, dst, sym)
char *src; /* name of existing source file */
char *dst; /* name of new destination file */
int sym; /* use symlink instead of link */
{
FILE *fpsrc;
FILE *fpdst;
int c;
/* Open the source file. We do this first to make sure it exists */
fpsrc = g_fopen(src, "rb");
if (!fpsrc)
{
untar_error("Error opening: %s\n", src);
return;
}
/* Create the destination file. On POSIX systems, this is just to
* make sure the directory path exists.
*/
fpdst = createpath(dst);
if (!fpdst) {
/* error message already given */
fclose(fpsrc);
return;
}
#ifdef _POSIX_SOURCE
# ifndef _WEAK_POSIX
/* first try to link it over, instead of copying */
fclose(fpdst);
g_unlink(dst);
if (sym)
{
if (symlink(src, dst))
{
perror(dst);
}
fclose(fpsrc);
return;
}
if (!link(src, dst))
{
/* This story had a happy ending */
fclose(fpsrc);
return;
}
/* Dang. Reopen the destination again */
fpdst = g_fopen(dst, "wb");
/* This *can't* fail */
# endif /* _WEAK_POSIX */
#endif /* _POSIX_SOURCE */
/* Copy characters */
while ((c = getc(fpsrc)) != EOF)
putc(c, fpdst);
/* Close the files */
fclose(fpsrc);
fclose(fpdst);
/* Give a warning */
untar_warning("%s: copy instead of link\n", dst);
}
/* This calls fwrite(), possibly after converting CR-LF to LF */
static void cvtwrite(blk, size, fp)
Uchar_t *blk; /* the block to be written */
Ulong_t size; /* number of characters to be written */
FILE *fp; /* file to write to */
{
Ulong_t i, j;
static Uchar_t mod[TSIZE];
if (CONVERT)
{
for (i = j = 0; i < size; i++)
{
/* convert LF to local newline convention */
if (blk[i] == LF)
mod[j++] = '\n';
/* If CR-LF pair, then delete the CR */
else if (blk[i] == CR && (i+1 >= size || blk[i+1] == LF))
;
/* other characters copied literally */
else
mod[j++] = blk[i];
}
size = j;
blk = mod;
}
fwrite(blk, (size_t)size, sizeof(Uchar_t), fp);
}
/* Compute the checksum of a tar header block, and return it as a long int.
* The checksum can be computed using either POSIX rules (unsigned bytes)
* or Sun rules (signed bytes).
*/
static long checksum(tblk, sunny)
tar_t *tblk; /* buffer containing the tar header block */
int sunny; /* Boolean: Sun-style checksums? (else POSIX) */
{
long sum;
char *scan;
/* compute the sum of the first 148 bytes -- everything up to but not
* including the checksum field itself.
*/
sum = 0L;
for (scan = (char *)tblk; scan < tblk->checksum; scan++)
{
sum += (*scan) & 0xff;
if (sunny && (*scan & 0x80) != 0)
sum -= 256;
}
/* for the 8 bytes of the checksum field, add blanks to the sum */
sum += ' ' * sizeof tblk->checksum;
scan += sizeof tblk->checksum;
/* finish counting the sum of the rest of the block */
for (; scan < (char *)tblk + sizeof *tblk; scan++)
{
sum += (*scan) & 0xff;
if (sunny && (*scan & 0x80) != 0)
sum -= 256;
}
return sum;
}
/* list files in an archive, and optionally extract them as well */
static int untar_block(Uchar_t *blk) {
static char nbuf[4096];/* storage space for prefix+name, combined */
static char *name,*n2;/* prefix and name, combined */
static int first = 1;/* Boolean: first block of archive? */
long sum; /* checksum for this block */
guint i;
tar_t tblk[1];
#ifdef _POSIX_SOURCE
static mode_t mode; /* file permissions */
static struct utimbuf timestamp; /* file timestamp */
#endif
/* make a local copy of the block, and treat it as a tar header */
tblk[0] = *(tar_t *)blk;
/* process each type of tape block differently */
if (outsize > TSIZE)
{
/* data block, but not the last one */
if (outfp)
cvtwrite(blk, (Ulong_t)TSIZE, outfp);
outsize -= TSIZE;
}
else if (outsize > 0)
{
/* last data block of current file */
if (outfp)
{
cvtwrite(blk, outsize, outfp);
fclose(outfp);
outfp = NULL;
#ifdef _POSIX_SOURCE
utime(nbuf, &timestamp);
chmod(nbuf, mode);
#endif
}
outsize = 0;
}
else if ((tblk)->filename[0] == '\0')
{
/* end-of-archive marker */
if (didabs)
untar_warning("Removed leading slashes because \"ABSPATH option\" wasn't given.\n");
return 1;
}
else
{
/* file header */
/* half-assed verification -- does it look like header? */
if ((tblk)->filename[99] != '\0'
|| ((tblk)->size[0] < '0'
&& (tblk)->size[0] != ' ')
|| (tblk)->size[0] > '9')
{
if (first)
{
untar_error("%s: not a valid tar file\n", inname);
return 0;
}
else
{
untar_error("Garbage detected; preceding file may be damaged\n");
return 0;
}
}
/* combine prefix and filename */
memset(nbuf, 0, sizeof nbuf);
if ((tblk)->prefix[0])
{
g_snprintf(nbuf, sizeof(nbuf), "%s/%s", (tblk)->prefix,
(tblk)->filename);
}
else
{
g_strlcpy(nbuf, (tblk)->filename,
sizeof (nbuf));
}
/* Possibly strip the drive from the path */
if (!ABSPATH) {
/* If the path contains a colon, assume everything before the
* colon is intended to be a drive name and ignore it. This
* should be just a single drive letter, but it should be safe
* to drop it even if it's longer. */
const char *lastcolon = strrchr(nbuf, ':');
if (lastcolon) {
memmove(nbuf, lastcolon, strlen(lastcolon) + 1);
didabs = 1; /* Path was changed from absolute to relative */
}
}
/* Convert any backslashes to forward slashes, and guard
* against doubled-up slashes. (Some DOS versions of "tar"
* get this wrong.) Also strip off leading slashes.
*/
name = nbuf;
if (!ABSPATH && (*name == '/' || *name == '\\'))
didabs = 1;
for (n2 = nbuf; *name; name++)
{
if (*name == '\\')
*name = '/';
if (*name != '/'
|| (ABSPATH && n2 == nbuf)
|| (n2 != nbuf && n2[-1] != '/'))
*n2++ = *name;
}
if (n2 == nbuf)
*n2++ = '/';
*n2 = '\0';
/* verify the checksum */
for (sum = 0L, i = 0; i < sizeof((tblk)->checksum); i++)
{
if ((tblk)->checksum[i] >= '0'
&& (tblk)->checksum[i] <= '7')
sum = sum * 8 + (tblk)->checksum[i] - '0';
}
if (sum != checksum(tblk, 0) && sum != checksum(tblk, 1))
{
if (!first)
untar_error("Garbage detected; preceding file may be damaged\n");
untar_error("%s: header has bad checksum for %s\n", inname, nbuf);
return 0;
}
/* From this point on, we don't care whether this is the first
* block or not. Might as well reset the "first" flag now.
*/
first = 0;
/* if last character of name is '/' then assume directory */
if (*nbuf && nbuf[strlen(nbuf) - 1] == '/')
(tblk)->type = '5';
/* convert file size */
for (outsize = 0L, i = 0; i < sizeof((tblk)->size); i++)
{
if ((tblk)->size[i] >= '0' && (tblk)->size[i] <= '7')
outsize = outsize * 8 + (tblk)->size[i] - '0';
}
#ifdef _POSIX_SOURCE
/* convert file timestamp */
for (timestamp.modtime=0L, i=0; i < sizeof((tblk)->mtime); i++)
{
if ((tblk)->mtime[i] >= '0' && (tblk)->mtime[i] <= '7')
timestamp.modtime = timestamp.modtime * 8
+ (tblk)->mtime[i] - '0';
}
timestamp.actime = timestamp.modtime;
/* convert file permissions */
for (mode = i = 0; i < sizeof((tblk)->mode); i++)
{
if ((tblk)->mode[i] >= '0' && (tblk)->mode[i] <= '7')
mode = mode * 8 + (tblk)->mode[i] - '0';
}
#endif
/* list the file */
if (VERBOSE)
untar_verbose("%c %s",
ISREGULAR(*tblk) ? '-' : ("hlcbdp"[(tblk)->type - '1']),
nbuf);
else if (!QUIET)
untar_verbose("%s\n", nbuf);
/* if link, then do the link-or-copy thing */
if (tblk->type == '1' || tblk->type == '2')
{
if (VERBOSE)
untar_verbose(" -> %s\n", tblk->linkto);
if (!LISTING)
linkorcopy(tblk->linkto, nbuf, tblk->type == '2');
outsize = 0L;
return 1;
}
/* If directory, then make a weak attempt to create it.
* Ideally we would do the "create path" thing, but that
* seems like more trouble than it's worth since traditional
* tar archives don't contain directories anyway.
*/
if (tblk->type == '5')
{
char *tmp;
if (LISTING)
tmp = " directory";
#ifdef _POSIX_SOURCE
else if (mkdir(nbuf, mode) == 0)
#else
else if (g_mkdir(nbuf, 0755) == 0)
#endif
tmp = " created";
else
tmp = " ignored";
if (VERBOSE)
untar_verbose("%s\n", tmp);
return 1;
}
/* if not a regular file, then skip it */
if (!ISREGULAR(*tblk))
{
if (VERBOSE)
untar_verbose(" ignored\n");
outsize = 0L;
return 1;
}
/* print file statistics */
if (VERBOSE)
{
untar_verbose(" (%ld byte%s, %ld tape block%s)\n",
outsize,
outsize == 1 ? "" : "s",
(outsize + TSIZE - 1) / TSIZE,
(outsize > 0 && outsize <= TSIZE) ? "" : "s");
}
/* if extracting, then try to create the file */
if (!LISTING)
outfp = createpath(nbuf);
else
outfp = NULL;
/* if file is 0 bytes long, then we're done already! */
if (outsize == 0 && outfp)
{
fclose(outfp);
#ifdef _POSIX_SOURCE
utime(nbuf, &timestamp);
chmod(nbuf, mode);
#endif
}
}
return 1;
}
/* Process an archive file. This involves reading the blocks one at a time
* and passing them to a untar() function.
*/
int untar(const char *filename, const char* destdir, untar_opt options) {
int ret=1;
wchar_t curdir[_MAX_PATH];
wchar_t *w_destdir;
untarops = options;
/* open the archive */
inname = filename;
infp = g_fopen(filename, "rb");
if (!infp)
{
untar_error("Error opening: %s\n", filename);
return 0;
}
w_destdir = g_utf8_to_utf16(destdir, -1, NULL, NULL, NULL);
/* Set current directory */
if(!GetCurrentDirectoryW(_MAX_PATH, curdir)) {
untar_error("Could not get current directory (error %lu).\n", GetLastError());
fclose(infp);
return 0;
}
if(!SetCurrentDirectoryW(w_destdir)) {
untar_error("Could not set current directory to (error %lu): %s\n", GetLastError(), destdir);
fclose(infp);
return 0;
} else {
/* UNCOMPRESSED */
/* send each block to the untar_block() function */
while (fread(slide, 1, TSIZE, infp) == TSIZE) {
if(!untar_block(slide)) {
untar_error("untar failure: %s\n", filename);
fclose(infp);
ret=0;
}
}
if (outsize > 0 && ret) {
untar_warning("Last file might be truncated!\n");
fclose(outfp);
outfp = NULL;
}
if(!SetCurrentDirectoryW(curdir)) {
untar_error("Could not set current dir back to original (error %lu).\n", GetLastError());
ret=0;
}
}
g_free(w_destdir);
/* close the archive file. */
fclose(infp);
return ret;
}