wxWidgets/src/common/tarstrm.cpp

1547 lines
41 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/common/tarstrm.cpp
// Purpose: Streams for Tar files
// Author: Mike Wetherell
// Copyright: (c) 2004 Mike Wetherell
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_TARSTREAM
#include "wx/tarstrm.h"
#ifndef WX_PRECOMP
#include "wx/intl.h"
#include "wx/log.h"
#include "wx/utils.h"
#endif
#include "wx/buffer.h"
#include "wx/datetime.h"
#include "wx/scopedptr.h"
#include "wx/filename.h"
#include "wx/thread.h"
#include <ctype.h>
#ifdef __UNIX__
#include <pwd.h>
#include <grp.h>
#endif
/////////////////////////////////////////////////////////////////////////////
// constants
enum {
TAR_NAME,
TAR_MODE,
TAR_UID,
TAR_GID,
TAR_SIZE,
TAR_MTIME,
TAR_CHKSUM,
TAR_TYPEFLAG,
TAR_LINKNAME,
TAR_MAGIC,
TAR_VERSION,
TAR_UNAME,
TAR_GNAME,
TAR_DEVMAJOR,
TAR_DEVMINOR,
TAR_PREFIX,
TAR_UNUSED,
TAR_NUMFIELDS
};
enum {
TAR_BLOCKSIZE = 512
};
// checksum type
enum {
SUM_UNKNOWN,
SUM_UNSIGNED,
SUM_SIGNED
};
// type of input tar
enum {
TYPE_OLDTAR, // fields after TAR_LINKNAME are invalid
TYPE_GNUTAR, // all fields except TAR_PREFIX are valid
TYPE_USTAR // all fields are valid
};
// signatures
static const char *USTAR_MAGIC = "ustar";
static const char *USTAR_VERSION = "00";
static const char *GNU_MAGIC = "ustar ";
static const char *GNU_VERION = " ";
wxIMPLEMENT_DYNAMIC_CLASS(wxTarEntry, wxArchiveEntry);
wxIMPLEMENT_DYNAMIC_CLASS(wxTarClassFactory, wxArchiveClassFactory);
/////////////////////////////////////////////////////////////////////////////
// Class factory
static wxTarClassFactory g_wxTarClassFactory;
wxTarClassFactory::wxTarClassFactory()
{
if (this == &g_wxTarClassFactory)
PushFront();
}
const wxChar * const *
wxTarClassFactory::GetProtocols(wxStreamProtocolType type) const
{
static const wxChar *protocols[] = { wxT("tar"), NULL };
static const wxChar *mimetypes[] = { wxT("application/x-tar"), NULL };
static const wxChar *fileexts[] = { wxT(".tar"), NULL };
static const wxChar *empty[] = { NULL };
switch (type) {
case wxSTREAM_PROTOCOL: return protocols;
case wxSTREAM_MIMETYPE: return mimetypes;
case wxSTREAM_FILEEXT: return fileexts;
default: return empty;
}
}
/////////////////////////////////////////////////////////////////////////////
// tar header block
typedef wxFileOffset wxTarNumber;
struct wxTarField { const wxChar *name; int pos; };
class wxTarHeaderBlock
{
public:
wxTarHeaderBlock()
{ memset(data, 0, sizeof(data)); }
wxTarHeaderBlock(const wxTarHeaderBlock& hb)
{ memcpy(data, hb.data, sizeof(data)); }
bool Read(wxInputStream& in);
bool Write(wxOutputStream& out);
inline bool WriteField(wxOutputStream& out, int id);
bool IsAllZeros() const;
wxUint32 Sum(bool SignedSum = false);
wxUint32 SumField(int id);
char *Get(int id) { return data + fields[id].pos + id; }
static size_t Len(int id) { return fields[id + 1].pos - fields[id].pos; }
static const wxChar *Name(int id) { return fields[id].name; }
static size_t Offset(int id) { return fields[id].pos; }
bool SetOctal(int id, wxTarNumber n);
wxTarNumber GetOctal(int id);
bool SetPath(const wxString& name, wxMBConv& conv);
private:
char data[TAR_BLOCKSIZE + TAR_NUMFIELDS];
static const wxTarField fields[];
static void check();
};
wxDEFINE_SCOPED_PTR_TYPE(wxTarHeaderBlock)
// A table giving the field names and offsets in a tar header block
const wxTarField wxTarHeaderBlock::fields[] =
{
{ wxT("name"), 0 }, // 100
{ wxT("mode"), 100 }, // 8
{ wxT("uid"), 108 }, // 8
{ wxT("gid"), 116 }, // 8
{ wxT("size"), 124 }, // 12
{ wxT("mtime"), 136 }, // 12
{ wxT("chksum"), 148 }, // 8
{ wxT("typeflag"), 156 }, // 1
{ wxT("linkname"), 157 }, // 100
{ wxT("magic"), 257 }, // 6
{ wxT("version"), 263 }, // 2
{ wxT("uname"), 265 }, // 32
{ wxT("gname"), 297 }, // 32
{ wxT("devmajor"), 329 }, // 8
{ wxT("devminor"), 337 }, // 8
{ wxT("prefix"), 345 }, // 155
{ wxT("unused"), 500 }, // 12
{ NULL, TAR_BLOCKSIZE }
};
void wxTarHeaderBlock::check()
{
#if 0
wxCOMPILE_TIME_ASSERT(
WXSIZEOF(fields) == TAR_NUMFIELDS + 1,
Wrong_number_of_elements_in_fields_table
);
#endif
}
bool wxTarHeaderBlock::IsAllZeros() const
{
const char *p = data;
for (size_t i = 0; i < sizeof(data); i++)
if (p[i])
return false;
return true;
}
wxUint32 wxTarHeaderBlock::Sum(bool SignedSum /*=false*/)
{
// the chksum field itself should be blanks during the calculation
memset(Get(TAR_CHKSUM), ' ', Len(TAR_CHKSUM));
const char *p = data;
wxUint32 n = 0;
if (SignedSum)
for (size_t i = 0; i < sizeof(data); i++)
n += (signed char)p[i];
else
for (size_t i = 0; i < sizeof(data); i++)
n += (unsigned char)p[i];
return n;
}
wxUint32 wxTarHeaderBlock::SumField(int id)
{
unsigned char *p = (unsigned char*)Get(id);
unsigned char *q = p + Len(id);
wxUint32 n = 0;
while (p < q)
n += *p++;
return n;
}
bool wxTarHeaderBlock::Read(wxInputStream& in)
{
bool ok = true;
for (int id = 0; id < TAR_NUMFIELDS && ok; id++)
ok = in.Read(Get(id), Len(id)).LastRead() == Len(id);
return ok;
}
bool wxTarHeaderBlock::Write(wxOutputStream& out)
{
bool ok = true;
for (int id = 0; id < TAR_NUMFIELDS && ok; id++)
ok = WriteField(out, id);
return ok;
}
inline bool wxTarHeaderBlock::WriteField(wxOutputStream& out, int id)
{
return out.Write(Get(id), Len(id)).LastWrite() == Len(id);
}
wxTarNumber wxTarHeaderBlock::GetOctal(int id)
{
wxTarNumber n = 0;
const char *p = Get(id);
while (*p == ' ')
p++;
while (*p >= '0' && *p < '8')
n = (n << 3) | (*p++ - '0');
return n;
}
bool wxTarHeaderBlock::SetOctal(int id, wxTarNumber n)
{
// set an octal field, return true if the number fits
char *field = Get(id);
char *p = field + Len(id);
*--p = 0;
while (p > field) {
*--p = char('0' + (n & 7));
n >>= 3;
}
return n == 0;
}
bool wxTarHeaderBlock::SetPath(const wxString& name, wxMBConv& conv)
{
bool badconv = false;
#if wxUSE_UNICODE
wxCharBuffer nameBuf = name.mb_str(conv);
// if the conversion fails make an approximation
if (!nameBuf) {
badconv = true;
size_t len = name.length();
wxCharBuffer approx(len);
for (size_t i = 0; i < len; i++)
{
wxChar c = name[i];
approx.data()[i] = c & ~0x7F ? '_' : c;
}
nameBuf = approx;
}
const char *mbName = nameBuf;
#else
const char *mbName = name.c_str();
(void)conv;
#endif
bool fits;
bool notGoingToFit = false;
size_t len = strlen(mbName);
size_t maxname = Len(TAR_NAME);
size_t maxprefix = Len(TAR_PREFIX);
size_t i = 0;
size_t nexti = 0;
for (;;) {
fits = i < maxprefix && len - i <= maxname;
if (!fits) {
const char *p = strchr(mbName + i, '/');
if (p)
nexti = p - mbName + 1;
if (!p || nexti - 1 > maxprefix)
notGoingToFit = true;
}
if (fits || notGoingToFit) {
strncpy(Get(TAR_NAME), mbName + i, maxname);
if (i > 0)
strncpy(Get(TAR_PREFIX), mbName, i - 1);
break;
}
i = nexti;
}
return fits && !badconv;
}
/////////////////////////////////////////////////////////////////////////////
// Some helpers
static wxFileOffset RoundUpSize(wxFileOffset size, int factor = 1)
{
wxFileOffset chunk = TAR_BLOCKSIZE * factor;
return ((size + chunk - 1) / chunk) * chunk;
}
#ifdef __UNIX__
static wxString wxTarUserName(int uid)
{
struct passwd *ppw;
#ifdef HAVE_GETPWUID_R
#if defined HAVE_SYSCONF && defined _SC_GETPW_R_SIZE_MAX
long pwsize = sysconf(_SC_GETPW_R_SIZE_MAX);
size_t bufsize(wxMin(wxMax(1024l, pwsize), 32768l));
#else
size_t bufsize = 1024;
#endif
wxCharBuffer buf(bufsize);
struct passwd pw;
memset(&pw, 0, sizeof(pw));
if (getpwuid_r(uid, &pw, buf.data(), bufsize, &ppw) == 0 && pw.pw_name)
return wxString(pw.pw_name, wxConvLibc);
#else
if ((ppw = getpwuid(uid)) != NULL)
return wxString(ppw->pw_name, wxConvLibc);
#endif
return _("unknown");
}
static wxString wxTarGroupName(int gid)
{
struct group *pgr;
#ifdef HAVE_GETGRGID_R
#if defined HAVE_SYSCONF && defined _SC_GETGR_R_SIZE_MAX
long grsize = sysconf(_SC_GETGR_R_SIZE_MAX);
size_t bufsize(wxMin(wxMax(1024l, grsize), 32768l));
#else
size_t bufsize = 1024;
#endif
wxCharBuffer buf(bufsize);
struct group gr;
memset(&gr, 0, sizeof(gr));
if (getgrgid_r(gid, &gr, buf.data(), bufsize, &pgr) == 0 && gr.gr_name)
return wxString(gr.gr_name, wxConvLibc);
#else
if ((pgr = getgrgid(gid)) != NULL)
return wxString(pgr->gr_name, wxConvLibc);
#endif
return _("unknown");
}
#endif // __UNIX__
// Cache the user and group names since getting them can be expensive,
// get both names and ids at the same time.
//
struct wxTarUser
{
wxTarUser();
~wxTarUser() { delete [] uname; delete [] gname; }
int uid;
int gid;
wxChar *uname;
wxChar *gname;
};
wxTarUser::wxTarUser()
{
#ifdef __UNIX__
uid = getuid();
gid = getgid();
wxString usr = wxTarUserName(uid);
wxString grp = wxTarGroupName(gid);
#else
uid = 0;
gid = 0;
wxString usr = wxGetUserId();
wxString grp = _("unknown");
#endif
uname = new wxChar[usr.length() + 1];
wxStrcpy(uname, usr.c_str());
gname = new wxChar[grp.length() + 1];
wxStrcpy(gname, grp.c_str());
}
static const wxTarUser& wxGetTarUser()
{
#if wxUSE_THREADS
static wxCriticalSection cs;
wxCriticalSectionLocker lock(cs);
#endif
static wxTarUser tu;
return tu;
}
// ignore the size field for entry types 3, 4, 5 and 6
//
static inline wxFileOffset GetDataSize(const wxTarEntry& entry)
{
switch (entry.GetTypeFlag()) {
case wxTAR_CHRTYPE:
case wxTAR_BLKTYPE:
case wxTAR_DIRTYPE:
case wxTAR_FIFOTYPE:
return 0;
default:
return entry.GetSize();
}
}
/////////////////////////////////////////////////////////////////////////////
// Tar Entry
// Holds all the meta-data for a file in the tar
wxTarEntry::wxTarEntry(const wxString& name /*=wxEmptyString*/,
const wxDateTime& dt /*=wxDateTime::Now()*/,
wxFileOffset size /*=0*/)
: m_Mode(0644),
m_IsModeSet(false),
m_UserId(wxGetTarUser().uid),
m_GroupId(wxGetTarUser().gid),
m_Size(size),
m_Offset(wxInvalidOffset),
m_ModifyTime(dt),
m_TypeFlag(wxTAR_REGTYPE),
m_UserName(wxGetTarUser().uname),
m_GroupName(wxGetTarUser().gname),
m_DevMajor(~0),
m_DevMinor(~0)
{
if (!name.empty())
SetName(name);
}
wxTarEntry::~wxTarEntry()
{
}
wxTarEntry::wxTarEntry(const wxTarEntry& e)
: wxArchiveEntry(),
m_Name(e.m_Name),
m_Mode(e.m_Mode),
m_IsModeSet(e.m_IsModeSet),
m_UserId(e.m_UserId),
m_GroupId(e.m_GroupId),
m_Size(e.m_Size),
m_Offset(e.m_Offset),
m_ModifyTime(e.m_ModifyTime),
m_AccessTime(e.m_AccessTime),
m_CreateTime(e.m_CreateTime),
m_TypeFlag(e.m_TypeFlag),
m_LinkName(e.m_LinkName),
m_UserName(e.m_UserName),
m_GroupName(e.m_GroupName),
m_DevMajor(e.m_DevMajor),
m_DevMinor(e.m_DevMinor)
{
}
wxTarEntry& wxTarEntry::operator=(const wxTarEntry& e)
{
if (&e != this) {
m_Name = e.m_Name;
m_Mode = e.m_Mode;
m_IsModeSet = e.m_IsModeSet;
m_UserId = e.m_UserId;
m_GroupId = e.m_GroupId;
m_Size = e.m_Size;
m_Offset = e.m_Offset;
m_ModifyTime = e.m_ModifyTime;
m_AccessTime = e.m_AccessTime;
m_CreateTime = e.m_CreateTime;
m_TypeFlag = e.m_TypeFlag;
m_LinkName = e.m_LinkName;
m_UserName = e.m_UserName;
m_GroupName = e.m_GroupName;
m_DevMajor = e.m_DevMajor;
m_DevMinor = e.m_DevMinor;
}
return *this;
}
wxString wxTarEntry::GetName(wxPathFormat format /*=wxPATH_NATIVE*/) const
{
bool isDir = IsDir() && !m_Name.empty();
// optimisations for common (and easy) cases
switch (wxFileName::GetFormat(format)) {
case wxPATH_DOS:
{
wxString name(isDir ? m_Name + wxT("\\") : m_Name);
for (size_t i = 0; i < name.length(); i++)
if (name[i] == wxT('/'))
name[i] = wxT('\\');
return name;
}
case wxPATH_UNIX:
return isDir ? m_Name + wxT("/") : m_Name;
default:
;
}
wxFileName fn;
if (isDir)
fn.AssignDir(m_Name, wxPATH_UNIX);
else
fn.Assign(m_Name, wxPATH_UNIX);
return fn.GetFullPath(format);
}
void wxTarEntry::SetName(const wxString& name, wxPathFormat format)
{
bool isDir;
m_Name = GetInternalName(name, format, &isDir);
SetIsDir(isDir);
}
// Static - Internally tars and zips use forward slashes for the path
// separator, absolute paths aren't allowed, and directory names have a
// trailing slash. This function converts a path into this internal format,
// but without a trailing slash for a directory.
//
wxString wxTarEntry::GetInternalName(const wxString& name,
wxPathFormat format /*=wxPATH_NATIVE*/,
bool *pIsDir /*=NULL*/)
{
wxString internal;
if (wxFileName::GetFormat(format) != wxPATH_UNIX)
internal = wxFileName(name, format).GetFullPath(wxPATH_UNIX);
else
internal = name;
bool isDir = !internal.empty() && internal.Last() == '/';
if (pIsDir)
*pIsDir = isDir;
if (isDir)
internal.erase(internal.length() - 1);
while (!internal.empty() && *internal.begin() == '/')
internal.erase(0, 1);
while (!internal.empty() && internal.compare(0, 2, wxT("./")) == 0)
internal.erase(0, 2);
if (internal == wxT(".") || internal == wxT(".."))
internal.clear();
return internal;
}
bool wxTarEntry::IsDir() const
{
return m_TypeFlag == wxTAR_DIRTYPE;
}
void wxTarEntry::SetIsDir(bool isDir)
{
if (isDir)
m_TypeFlag = wxTAR_DIRTYPE;
else if (m_TypeFlag == wxTAR_DIRTYPE)
m_TypeFlag = wxTAR_REGTYPE;
}
void wxTarEntry::SetIsReadOnly(bool isReadOnly)
{
if (isReadOnly)
m_Mode &= ~0222;
else
m_Mode |= 0200;
}
int wxTarEntry::GetMode() const
{
if (m_IsModeSet || !IsDir())
return m_Mode;
else
return m_Mode | 0111;
}
void wxTarEntry::SetMode(int mode)
{
m_Mode = mode & 07777;
m_IsModeSet = true;
}
/////////////////////////////////////////////////////////////////////////////
// Input stream
wxDECLARE_SCOPED_PTR(wxTarEntry, wxTarEntryPtr_)
wxDEFINE_SCOPED_PTR (wxTarEntry, wxTarEntryPtr_)
wxTarInputStream::wxTarInputStream(wxInputStream& stream,
wxMBConv& conv /*=wxConvLocal*/)
: wxArchiveInputStream(stream, conv)
{
Init();
}
wxTarInputStream::wxTarInputStream(wxInputStream *stream,
wxMBConv& conv /*=wxConvLocal*/)
: wxArchiveInputStream(stream, conv)
{
Init();
}
void wxTarInputStream::Init()
{
m_pos = wxInvalidOffset;
m_offset = 0;
m_size = wxInvalidOffset;
m_sumType = SUM_UNKNOWN;
m_tarType = TYPE_USTAR;
m_hdr = new wxTarHeaderBlock;
m_HeaderRecs = NULL;
m_GlobalHeaderRecs = NULL;
m_lasterror = m_parent_i_stream->GetLastError();
}
wxTarInputStream::~wxTarInputStream()
{
delete m_hdr;
delete m_HeaderRecs;
delete m_GlobalHeaderRecs;
}
wxTarEntry *wxTarInputStream::GetNextEntry()
{
m_lasterror = ReadHeaders();
if (!IsOk())
return NULL;
wxTarEntryPtr_ entry(new wxTarEntry);
entry->SetMode(GetHeaderNumber(TAR_MODE));
entry->SetUserId(GetHeaderNumber(TAR_UID));
entry->SetGroupId(GetHeaderNumber(TAR_UID));
entry->SetSize(GetHeaderNumber(TAR_SIZE));
entry->SetOffset(m_offset);
entry->SetDateTime(GetHeaderDate(wxT("mtime")));
entry->SetAccessTime(GetHeaderDate(wxT("atime")));
entry->SetCreateTime(GetHeaderDate(wxT("ctime")));
entry->SetTypeFlag(*m_hdr->Get(TAR_TYPEFLAG));
bool isDir = entry->IsDir();
entry->SetLinkName(GetHeaderString(TAR_LINKNAME));
if (m_tarType != TYPE_OLDTAR) {
entry->SetUserName(GetHeaderString(TAR_UNAME));
entry->SetGroupName(GetHeaderString(TAR_GNAME));
entry->SetDevMajor(GetHeaderNumber(TAR_DEVMAJOR));
entry->SetDevMinor(GetHeaderNumber(TAR_DEVMINOR));
}
entry->SetName(GetHeaderPath(), wxPATH_UNIX);
if (isDir)
entry->SetIsDir();
if (m_HeaderRecs)
m_HeaderRecs->clear();
m_size = GetDataSize(*entry);
m_pos = 0;
return entry.release();
}
bool wxTarInputStream::OpenEntry(wxTarEntry& entry)
{
wxFileOffset offset = entry.GetOffset();
if (GetLastError() != wxSTREAM_READ_ERROR
&& m_parent_i_stream->IsSeekable()
&& m_parent_i_stream->SeekI(offset) == offset)
{
m_offset = offset;
m_size = GetDataSize(entry);
m_pos = 0;
m_lasterror = wxSTREAM_NO_ERROR;
return true;
} else {
m_lasterror = wxSTREAM_READ_ERROR;
return false;
}
}
bool wxTarInputStream::OpenEntry(wxArchiveEntry& entry)
{
wxTarEntry *tarEntry = wxStaticCast(&entry, wxTarEntry);
return tarEntry ? OpenEntry(*tarEntry) : false;
}
bool wxTarInputStream::CloseEntry()
{
if (m_lasterror == wxSTREAM_READ_ERROR)
return false;
if (!IsOpened())
return true;
wxFileOffset size = RoundUpSize(m_size);
wxFileOffset remainder = size - m_pos;
if (remainder && m_parent_i_stream->IsSeekable()) {
wxLogNull nolog;
if (m_parent_i_stream->SeekI(remainder, wxFromCurrent)
!= wxInvalidOffset)
remainder = 0;
}
if (remainder) {
const int BUFSIZE = 8192;
wxCharBuffer buf(BUFSIZE);
while (remainder > 0 && m_parent_i_stream->IsOk())
remainder -= m_parent_i_stream->Read(
buf.data(), wxMin(BUFSIZE, remainder)).LastRead();
}
m_pos = wxInvalidOffset;
m_offset += size;
m_lasterror = m_parent_i_stream->GetLastError();
return IsOk();
}
wxStreamError wxTarInputStream::ReadHeaders()
{
if (!CloseEntry())
return wxSTREAM_READ_ERROR;
bool done = false;
while (!done) {
m_hdr->Read(*m_parent_i_stream);
if (m_parent_i_stream->Eof())
{
wxLogError(_("incomplete header block in tar"));
}
if (!*m_parent_i_stream)
return wxSTREAM_READ_ERROR;
m_offset += TAR_BLOCKSIZE;
// an all-zero header marks the end of the tar
if (m_hdr->IsAllZeros())
return wxSTREAM_EOF;
// the checksum is supposed to be the unsigned sum of the header bytes,
// but there have been versions of tar that used the signed sum, so
// accept that too, but only if used throughout.
wxUint32 chksum = m_hdr->GetOctal(TAR_CHKSUM);
bool ok = false;
if (m_sumType != SUM_SIGNED) {
ok = chksum == m_hdr->Sum();
if (m_sumType == SUM_UNKNOWN)
m_sumType = ok ? SUM_UNSIGNED : SUM_SIGNED;
}
if (m_sumType == SUM_SIGNED)
ok = chksum == m_hdr->Sum(true);
if (!ok) {
wxLogError(_("checksum failure reading tar header block"));
return wxSTREAM_READ_ERROR;
}
if (strcmp(m_hdr->Get(TAR_MAGIC), USTAR_MAGIC) == 0)
m_tarType = TYPE_USTAR;
else if (strcmp(m_hdr->Get(TAR_MAGIC), GNU_MAGIC) == 0 &&
strcmp(m_hdr->Get(TAR_VERSION), GNU_VERION) == 0)
m_tarType = TYPE_GNUTAR;
else
m_tarType = TYPE_OLDTAR;
if (m_tarType != TYPE_USTAR)
break;
switch (*m_hdr->Get(TAR_TYPEFLAG)) {
case 'g': ReadExtendedHeader(m_GlobalHeaderRecs); break;
case 'x': ReadExtendedHeader(m_HeaderRecs); break;
default: done = true;
}
}
return wxSTREAM_NO_ERROR;
}
wxString wxTarInputStream::GetExtendedHeader(const wxString& key) const
{
wxTarHeaderRecords::iterator it;
// look at normal extended header records first
if (m_HeaderRecs) {
it = m_HeaderRecs->find(key);
if (it != m_HeaderRecs->end())
return wxString(it->second.wc_str(wxConvUTF8), GetConv());
}
// if not found, look at the global header records
if (m_GlobalHeaderRecs) {
it = m_GlobalHeaderRecs->find(key);
if (it != m_GlobalHeaderRecs->end())
return wxString(it->second.wc_str(wxConvUTF8), GetConv());
}
return wxEmptyString;
}
wxString wxTarInputStream::GetHeaderPath() const
{
wxString path(GetExtendedHeader(wxS("path")));
if (!path.empty())
return path;
path = wxString(m_hdr->Get(TAR_NAME), GetConv());
if (m_tarType != TYPE_USTAR)
return path;
const char *prefix = m_hdr->Get(TAR_PREFIX);
return *prefix ? wxString(prefix, GetConv()) + wxT("/") + path : path;
}
wxDateTime wxTarInputStream::GetHeaderDate(const wxString& key) const
{
wxString value(GetExtendedHeader(key));
// try extended header, stored as decimal seconds since the epoch
if (!value.empty()) {
wxLongLong ll;
ll.Assign(wxAtof(value) * 1000.0);
return ll;
}
if (key == wxT("mtime"))
return wxLongLong(m_hdr->GetOctal(TAR_MTIME)) * 1000L;
return wxDateTime();
}
wxTarNumber wxTarInputStream::GetHeaderNumber(int id) const
{
wxString value(GetExtendedHeader(m_hdr->Name(id)));
if (!value.empty()) {
wxTarNumber n = 0;
wxString::const_iterator p = value.begin();
while (p != value.end() && *p == ' ')
p++;
while (isdigit(*p))
n = n * 10 + (*p++ - '0');
return n;
} else {
return m_hdr->GetOctal(id);
}
}
wxString wxTarInputStream::GetHeaderString(int id) const
{
wxString value(GetExtendedHeader(m_hdr->Name(id)));
if (value.empty())
value = wxString(m_hdr->Get(id), GetConv());
return value;
}
// An extended header consists of one or more records, each constructed:
// "%d %s=%s\n", <length>, <keyword>, <value>
// <length> is the byte length, <keyword> and <value> are UTF-8
bool wxTarInputStream::ReadExtendedHeader(wxTarHeaderRecords*& recs)
{
if (!recs)
recs = new wxTarHeaderRecords;
// round length up to a whole number of blocks
size_t len = m_hdr->GetOctal(TAR_SIZE);
size_t size = RoundUpSize(len);
// read in the whole header since it should be small
wxCharBuffer buf(size);
size_t lastread = m_parent_i_stream->Read(buf.data(), size).LastRead();
if (lastread < len)
len = lastread;
buf.data()[len] = 0;
m_offset += lastread;
size_t recPos, recSize;
bool ok = true;
for (recPos = 0; recPos < len && ok; recPos += recSize) {
char *pRec = buf.data() + recPos;
char *p = pRec;
// read the record size (byte count in ascii decimal)
recSize = 0;
while (isdigit((unsigned char) *p))
recSize = recSize * 10 + *p++ - '0';
// validity checks
if (recPos + recSize > len)
break;
if (recSize < p - pRec + (size_t)3 || *p != ' '
|| pRec[recSize - 1] != '\012') {
ok = false;
continue;
}
// replace the final '\n' with a nul, to terminate value
pRec[recSize - 1] = 0;
// the key is here, following the space
char *pKey = ++p;
// look forward for the '=', the value follows
while (*p && *p != '=')
p++;
if (!*p) {
ok = false;
continue;
}
// replace the '=' with a nul, to terminate the key
*p++ = 0;
wxString key(wxConvUTF8.cMB2WC(pKey), GetConv());
wxString value(wxConvUTF8.cMB2WC(p), GetConv());
// an empty value unsets a previously given value
if (value.empty())
recs->erase(key);
else
(*recs)[key] = value;
}
if (!ok || recPos < len || size != lastread) {
wxLogWarning(_("invalid data in extended tar header"));
return false;
}
return true;
}
wxFileOffset wxTarInputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
{
if (!IsOpened()) {
wxLogError(_("tar entry not open"));
m_lasterror = wxSTREAM_READ_ERROR;
}
if (!IsOk())
return wxInvalidOffset;
switch (mode) {
case wxFromStart: break;
case wxFromCurrent: pos += m_pos; break;
case wxFromEnd: pos += m_size; break;
}
if (pos < 0 || m_parent_i_stream->SeekI(m_offset + pos) == wxInvalidOffset)
return wxInvalidOffset;
m_pos = pos;
return m_pos;
}
size_t wxTarInputStream::OnSysRead(void *buffer, size_t size)
{
if (!IsOpened()) {
wxLogError(_("tar entry not open"));
m_lasterror = wxSTREAM_READ_ERROR;
}
if (!IsOk() || !size)
return 0;
if (m_pos >= m_size)
size = 0;
else if (m_pos + size > m_size + (size_t)0)
size = m_size - m_pos;
size_t lastread = m_parent_i_stream->Read(buffer, size).LastRead();
m_pos += lastread;
if (m_pos >= m_size) {
m_lasterror = wxSTREAM_EOF;
} else if (!m_parent_i_stream->IsOk()) {
// any other error will have been reported by the underlying stream
if (m_parent_i_stream->Eof())
{
wxLogError(_("unexpected end of file"));
}
m_lasterror = wxSTREAM_READ_ERROR;
}
return lastread;
}
/////////////////////////////////////////////////////////////////////////////
// Output stream
wxTarOutputStream::wxTarOutputStream(wxOutputStream& stream,
wxTarFormat format /*=wxTAR_PAX*/,
wxMBConv& conv /*=wxConvLocal*/)
: wxArchiveOutputStream(stream, conv)
{
Init(format);
}
wxTarOutputStream::wxTarOutputStream(wxOutputStream *stream,
wxTarFormat format /*=wxTAR_PAX*/,
wxMBConv& conv /*=wxConvLocal*/)
: wxArchiveOutputStream(stream, conv)
{
Init(format);
}
void wxTarOutputStream::Init(wxTarFormat format)
{
m_pos = wxInvalidOffset;
m_maxpos = wxInvalidOffset;
m_size = wxInvalidOffset;
m_headpos = wxInvalidOffset;
m_datapos = wxInvalidOffset;
m_tarstart = wxInvalidOffset;
m_tarsize = 0;
m_pax = format == wxTAR_PAX;
m_BlockingFactor = m_pax ? 10 : 20;
m_chksum = 0;
m_large = false;
m_hdr = new wxTarHeaderBlock;
m_hdr2 = NULL;
m_extendedHdr = NULL;
m_extendedSize = 0;
m_lasterror = m_parent_o_stream->GetLastError();
m_endrecWritten = false;
}
wxTarOutputStream::~wxTarOutputStream()
{
Close();
delete m_hdr;
delete m_hdr2;
delete [] m_extendedHdr;
}
bool wxTarOutputStream::PutNextEntry(wxTarEntry *entry)
{
wxTarEntryPtr_ e(entry);
if (!CloseEntry())
return false;
if (!m_tarsize) {
wxLogNull nolog;
m_tarstart = m_parent_o_stream->TellO();
}
if (m_tarstart != wxInvalidOffset)
m_headpos = m_tarstart + m_tarsize;
if (WriteHeaders(*e)) {
m_pos = 0;
m_maxpos = 0;
m_size = GetDataSize(*e);
if (m_tarstart != wxInvalidOffset)
m_datapos = m_tarstart + m_tarsize;
// types that are not allowed any data
const char nodata[] = {
wxTAR_LNKTYPE, wxTAR_SYMTYPE, wxTAR_CHRTYPE, wxTAR_BLKTYPE,
wxTAR_DIRTYPE, wxTAR_FIFOTYPE, 0
};
int typeflag = e->GetTypeFlag();
// pax does now allow data for wxTAR_LNKTYPE
if (!m_pax || typeflag != wxTAR_LNKTYPE)
if (strchr(nodata, typeflag) != NULL)
CloseEntry();
}
return IsOk();
}
bool wxTarOutputStream::PutNextEntry(const wxString& name,
const wxDateTime& dt,
wxFileOffset size)
{
return PutNextEntry(new wxTarEntry(name, dt, size));
}
bool wxTarOutputStream::PutNextDirEntry(const wxString& name,
const wxDateTime& dt)
{
wxTarEntry *entry = new wxTarEntry(name, dt);
entry->SetIsDir();
return PutNextEntry(entry);
}
bool wxTarOutputStream::PutNextEntry(wxArchiveEntry *entry)
{
wxTarEntry *tarEntry = wxStaticCast(entry, wxTarEntry);
if (!tarEntry)
delete entry;
return PutNextEntry(tarEntry);
}
bool wxTarOutputStream::CopyEntry(wxTarEntry *entry,
wxTarInputStream& inputStream)
{
if (PutNextEntry(entry))
Write(inputStream);
return IsOk() && inputStream.Eof();
}
bool wxTarOutputStream::CopyEntry(wxArchiveEntry *entry,
wxArchiveInputStream& inputStream)
{
if (PutNextEntry(entry))
Write(inputStream);
return IsOk() && inputStream.Eof();
}
bool wxTarOutputStream::CloseEntry()
{
if (!IsOpened())
return true;
if (m_pos < m_maxpos) {
wxASSERT(m_parent_o_stream->IsSeekable());
m_parent_o_stream->SeekO(m_datapos + m_maxpos);
m_lasterror = m_parent_o_stream->GetLastError();
m_pos = m_maxpos;
}
if (IsOk()) {
wxFileOffset size = RoundUpSize(m_pos);
if (size > m_pos) {
memset(m_hdr, 0, size - m_pos);
m_parent_o_stream->Write(m_hdr, size - m_pos);
m_lasterror = m_parent_o_stream->GetLastError();
}
m_tarsize += size;
}
if (IsOk() && m_pos != m_size)
ModifyHeader();
m_pos = wxInvalidOffset;
m_maxpos = wxInvalidOffset;
m_size = wxInvalidOffset;
m_headpos = wxInvalidOffset;
m_datapos = wxInvalidOffset;
return IsOk();
}
bool wxTarOutputStream::Close()
{
if (!CloseEntry() || (m_tarsize == 0 && m_endrecWritten))
return false;
memset(m_hdr, 0, sizeof(*m_hdr));
int count = (RoundUpSize(m_tarsize + 2 * TAR_BLOCKSIZE, m_BlockingFactor)
- m_tarsize) / TAR_BLOCKSIZE;
while (count--)
m_parent_o_stream->Write(m_hdr, TAR_BLOCKSIZE);
m_tarsize = 0;
m_tarstart = wxInvalidOffset;
m_lasterror = m_parent_o_stream->GetLastError();
m_endrecWritten = true;
return IsOk();
}
bool wxTarOutputStream::WriteHeaders(wxTarEntry& entry)
{
memset(m_hdr, 0, sizeof(*m_hdr));
SetHeaderPath(entry.GetName(wxPATH_UNIX));
SetHeaderNumber(TAR_MODE, entry.GetMode());
SetHeaderNumber(TAR_UID, entry.GetUserId());
SetHeaderNumber(TAR_GID, entry.GetGroupId());
if (entry.GetSize() == wxInvalidOffset)
entry.SetSize(0);
m_large = !SetHeaderNumber(TAR_SIZE, entry.GetSize());
SetHeaderDate(wxT("mtime"), entry.GetDateTime());
if (entry.GetAccessTime().IsValid())
SetHeaderDate(wxT("atime"), entry.GetAccessTime());
if (entry.GetCreateTime().IsValid())
SetHeaderDate(wxT("ctime"), entry.GetCreateTime());
*m_hdr->Get(TAR_TYPEFLAG) = char(entry.GetTypeFlag());
strcpy(m_hdr->Get(TAR_MAGIC), USTAR_MAGIC);
strcpy(m_hdr->Get(TAR_VERSION), USTAR_VERSION);
SetHeaderString(TAR_LINKNAME, entry.GetLinkName());
SetHeaderString(TAR_UNAME, entry.GetUserName());
SetHeaderString(TAR_GNAME, entry.GetGroupName());
if (~entry.GetDevMajor())
SetHeaderNumber(TAR_DEVMAJOR, entry.GetDevMajor());
if (~entry.GetDevMinor())
SetHeaderNumber(TAR_DEVMINOR, entry.GetDevMinor());
m_chksum = m_hdr->Sum();
m_hdr->SetOctal(TAR_CHKSUM, m_chksum);
if (!m_large)
m_chksum -= m_hdr->SumField(TAR_SIZE);
// The main header is now fully prepared so we know what extended headers
// (if any) will be needed. Output any extended headers before writing
// the main header.
if (m_extendedHdr && *m_extendedHdr) {
wxASSERT(m_pax);
// the extended headers are written to the tar as a file entry,
// so prepare a regular header block for the pseudo-file.
if (!m_hdr2)
m_hdr2 = new wxTarHeaderBlock;
memset(m_hdr2, 0, sizeof(*m_hdr2));
// an old tar that doesn't understand extended headers will
// extract it as a file, so give these fields reasonable values
// so that the user will have access to read and remove it.
m_hdr2->SetPath(PaxHeaderPath(wxT("%d/PaxHeaders.%p/%f"),
entry.GetName(wxPATH_UNIX)), GetConv());
m_hdr2->SetOctal(TAR_MODE, 0600);
strcpy(m_hdr2->Get(TAR_UID), m_hdr->Get(TAR_UID));
strcpy(m_hdr2->Get(TAR_GID), m_hdr->Get(TAR_GID));
size_t length = strlen(m_extendedHdr);
m_hdr2->SetOctal(TAR_SIZE, length);
strcpy(m_hdr2->Get(TAR_MTIME), m_hdr->Get(TAR_MTIME));
*m_hdr2->Get(TAR_TYPEFLAG) = 'x';
strcpy(m_hdr2->Get(TAR_MAGIC), USTAR_MAGIC);
strcpy(m_hdr2->Get(TAR_VERSION), USTAR_VERSION);
strcpy(m_hdr2->Get(TAR_UNAME), m_hdr->Get(TAR_UNAME));
strcpy(m_hdr2->Get(TAR_GNAME), m_hdr->Get(TAR_GNAME));
m_hdr2->SetOctal(TAR_CHKSUM, m_hdr2->Sum());
m_hdr2->Write(*m_parent_o_stream);
m_tarsize += TAR_BLOCKSIZE;
size_t rounded = RoundUpSize(length);
memset(m_extendedHdr + length, 0, rounded - length);
m_parent_o_stream->Write(m_extendedHdr, rounded);
m_tarsize += rounded;
*m_extendedHdr = 0;
// update m_headpos which is used to seek back to fix up the file
// length if it is not known in advance
if (m_tarstart != wxInvalidOffset)
m_headpos = m_tarstart + m_tarsize;
}
// if don't have extended headers just report error
if (!m_badfit.empty()) {
wxASSERT(!m_pax);
wxLogWarning(_("%s did not fit the tar header for entry '%s'"),
m_badfit.c_str(), entry.GetName().c_str());
m_badfit.clear();
}
m_hdr->Write(*m_parent_o_stream);
m_tarsize += TAR_BLOCKSIZE;
m_lasterror = m_parent_o_stream->GetLastError();
return IsOk();
}
wxString wxTarOutputStream::PaxHeaderPath(const wxString& format,
const wxString& path)
{
wxString d = path.BeforeLast(wxT('/'));
wxString f = path.AfterLast(wxT('/'));
wxString ret;
if (d.empty())
d = wxT(".");
ret.reserve(format.length() + path.length() + 16);
size_t begin = 0;
size_t end;
for (;;) {
end = format.find('%', begin);
if (end == wxString::npos || end + 1 >= format.length())
break;
ret << format.substr(begin, end - begin);
switch ( format[end + 1].GetValue() ) {
case 'd': ret << d; break;
case 'f': ret << f; break;
case 'p': ret << wxGetProcessId(); break;
case '%': ret << wxT("%"); break;
}
begin = end + 2;
}
ret << format.substr(begin);
return ret;
}
bool wxTarOutputStream::ModifyHeader()
{
wxFileOffset originalPos = wxInvalidOffset;
wxFileOffset sizePos = wxInvalidOffset;
if (!m_large && m_headpos != wxInvalidOffset
&& m_parent_o_stream->IsSeekable())
{
wxLogNull nolog;
originalPos = m_parent_o_stream->TellO();
if (originalPos != wxInvalidOffset)
sizePos =
m_parent_o_stream->SeekO(m_headpos + m_hdr->Offset(TAR_SIZE));
}
if (sizePos == wxInvalidOffset || !m_hdr->SetOctal(TAR_SIZE, m_pos)) {
wxLogError(_("incorrect size given for tar entry"));
m_lasterror = wxSTREAM_WRITE_ERROR;
return false;
}
m_chksum += m_hdr->SumField(TAR_SIZE);
m_hdr->SetOctal(TAR_CHKSUM, m_chksum);
wxFileOffset sumPos = m_headpos + m_hdr->Offset(TAR_CHKSUM);
return
m_hdr->WriteField(*m_parent_o_stream, TAR_SIZE) &&
m_parent_o_stream->SeekO(sumPos) == sumPos &&
m_hdr->WriteField(*m_parent_o_stream, TAR_CHKSUM) &&
m_parent_o_stream->SeekO(originalPos) == originalPos;
}
void wxTarOutputStream::SetHeaderPath(const wxString& name)
{
if (!m_hdr->SetPath(name, GetConv()) || (m_pax && !name.IsAscii()))
SetExtendedHeader(wxT("path"), name);
}
bool wxTarOutputStream::SetHeaderNumber(int id, wxTarNumber n)
{
if (m_hdr->SetOctal(id, n)) {
return true;
} else {
SetExtendedHeader(m_hdr->Name(id), wxLongLong(n).ToString());
return false;
}
}
void wxTarOutputStream::SetHeaderString(int id, const wxString& str)
{
strncpy(m_hdr->Get(id), str.mb_str(GetConv()), m_hdr->Len(id));
if (str.length() > m_hdr->Len(id))
SetExtendedHeader(m_hdr->Name(id), str);
}
void wxTarOutputStream::SetHeaderDate(const wxString& key,
const wxDateTime& datetime)
{
wxLongLong ll = datetime.IsValid() ? datetime.GetValue() : wxLongLong(0);
wxLongLong secs = ll / 1000L;
if (key != wxT("mtime")
|| !m_hdr->SetOctal(TAR_MTIME, wxTarNumber(secs.GetValue()))
|| secs <= 0 || secs >= 0x7fffffff)
{
wxString str;
if (ll >= LONG_MIN && ll <= LONG_MAX) {
str.Printf(wxT("%g"), ll.ToLong() / 1000.0);
} else {
str = ll.ToString();
str.insert(str.end() - 3, '.');
}
SetExtendedHeader(key, str);
}
}
void wxTarOutputStream::SetExtendedHeader(const wxString& key,
const wxString& value)
{
if (m_pax) {
#if wxUSE_UNICODE
const wxCharBuffer utf_key = key.utf8_str();
const wxCharBuffer utf_value = value.utf8_str();
#else
const wxWX2WCbuf wide_key = key.wc_str(GetConv());
const wxCharBuffer utf_key = wxConvUTF8.cWC2MB(wide_key);
const wxWX2WCbuf wide_value = value.wc_str(GetConv());
const wxCharBuffer utf_value = wxConvUTF8.cWC2MB(wide_value);
#endif // wxUSE_UNICODE/!wxUSE_UNICODE
// a small buffer to format the length field in
char buf[32];
// length of "99<space><key>=<value>\n"
unsigned long length = strlen(utf_value) + strlen(utf_key) + 5;
sprintf(buf, "%lu", length);
// the length includes itself
size_t lenlen = strlen(buf);
if (lenlen != 2) {
length += lenlen - 2;
sprintf(buf, "%lu", length);
if (strlen(buf) > lenlen)
sprintf(buf, "%lu", ++length);
}
// reallocate m_extendedHdr if it's not big enough
if (m_extendedSize < length) {
size_t rounded = RoundUpSize(length);
m_extendedSize <<= 1;
if (rounded > m_extendedSize)
m_extendedSize = rounded;
char *oldHdr = m_extendedHdr;
m_extendedHdr = new char[m_extendedSize];
if (oldHdr) {
strcpy(m_extendedHdr, oldHdr);
delete oldHdr;
} else {
*m_extendedHdr = 0;
}
}
// append the new record
char *append = strchr(m_extendedHdr, 0);
sprintf(append, "%s %s=%s\012", buf,
(const char*)utf_key, (const char*)utf_value);
}
else {
// if not pax then make a list of fields to report as errors
if (!m_badfit.empty())
m_badfit += wxT(", ");
m_badfit += key;
}
}
void wxTarOutputStream::Sync()
{
m_parent_o_stream->Sync();
}
wxFileOffset wxTarOutputStream::OnSysSeek(wxFileOffset pos, wxSeekMode mode)
{
if (!IsOpened()) {
wxLogError(_("tar entry not open"));
m_lasterror = wxSTREAM_WRITE_ERROR;
}
if (!IsOk() || m_datapos == wxInvalidOffset)
return wxInvalidOffset;
switch (mode) {
case wxFromStart: break;
case wxFromCurrent: pos += m_pos; break;
case wxFromEnd: pos += m_maxpos; break;
}
if (pos < 0 || m_parent_o_stream->SeekO(m_datapos + pos) == wxInvalidOffset)
return wxInvalidOffset;
m_pos = pos;
return m_pos;
}
size_t wxTarOutputStream::OnSysWrite(const void *buffer, size_t size)
{
if (!IsOpened()) {
wxLogError(_("tar entry not open"));
m_lasterror = wxSTREAM_WRITE_ERROR;
}
if (!IsOk() || !size)
return 0;
size_t lastwrite = m_parent_o_stream->Write(buffer, size).LastWrite();
m_pos += lastwrite;
if (m_pos > m_maxpos)
m_maxpos = m_pos;
if (lastwrite != size)
m_lasterror = wxSTREAM_WRITE_ERROR;
return lastwrite;
}
#endif // wxUSE_TARSTREAM