+/////////////////////////////////////////////////////////////////////////////
+// Name: tarstrm.cpp
+// Purpose: Streams for Tar files
+// Author: Mike Wetherell
+// RCS-ID: $Id$
+// 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/hashmap.h"
+ #include "wx/intl.h"
+ #include "wx/log.h"
+ #include "wx/utils.h"
+#endif
+
+#include "wx/buffer.h"
+#include "wx/datetime.h"
+#include "wx/ptr_scpd.h"
+#include "wx/filename.h"
+
+#include <ctype.h>
+
+#ifdef __UNIX__
+#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 = " ";
+
+IMPLEMENT_DYNAMIC_CLASS(wxTarEntry, wxArchiveEntry)
+IMPLEMENT_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[] = { _T("tar"), NULL };
+ static const wxChar *mimetypes[] = { _T("application/x-tar"), NULL };
+ static const wxChar *fileexts[] = { _T(".tar"), NULL };
+ static const wxChar *empty[] = { NULL };
+
+ switch (type) {
+ case wxSTREAM_PROTOCOL: return protocols;
+ case wxSTREAM_MIMETYPE: return mimetypes;
+ case wxSTREAM_FILEEXTENSION: 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[] =
+{
+ { _T("name"), 0 }, // 100
+ { _T("mode"), 100 }, // 8
+ { _T("uid"), 108 }, // 8
+ { _T("gid"), 116 }, // 8
+ { _T("size"), 124 }, // 12
+ { _T("mtime"), 136 }, // 12
+ { _T("chksum"), 148 }, // 8
+ { _T("typeflag"), 156 }, // 1
+ { _T("linkname"), 157 }, // 100
+ { _T("magic"), 257 }, // 6
+ { _T("version"), 263 }, // 2
+ { _T("uname"), 265 }, // 32
+ { _T("gname"), 297 }, // 32
+ { _T("devmajor"), 329 }, // 8
+ { _T("devminor"), 337 }, // 8
+ { _T("prefix"), 345 }, // 155
+ { _T("unused"), 500 }, // 12
+ { NULL, TAR_BLOCKSIZE }
+};
+
+void wxTarHeaderBlock::check()
+{
+ wxCOMPILE_TIME_ASSERT(
+ WXSIZEOF(fields) == TAR_NUMFIELDS + 1,
+ Wrong_number_of_elements_in_fields_table
+ );
+}
+
+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;
+}
+
+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 = '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++)
+ approx.data()[i] = name[i] & ~0x7F ? '_' : name[i];
+ 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;
+}
+
+static wxString GroupName()
+{
+#ifdef __UNIX__
+ group *gr;
+ if ((gr = getgrgid(getgid())) != NULL)
+ return wxString(gr->gr_name, wxConvLibc);
+#endif
+ return _("unknown");
+}
+
+static inline int UserId()
+{
+#ifdef __UNIX__
+ return getuid();
+#else
+ return 0;
+#endif
+}
+
+static inline int GroupId()
+{
+#ifdef __UNIX__
+ return getgid();
+#else
+ return 0;
+#endif
+}
+
+// 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(UserId()),
+ m_GroupId(GroupId()),
+ m_Size(size),
+ m_Offset(wxInvalidOffset),
+ m_ModifyTime(dt),
+ m_TypeFlag(wxTAR_REGTYPE),
+ m_UserName(wxGetUserId()),
+ m_GroupName(GroupName()),
+ m_DevMajor(~0),
+ m_DevMinor(~0)
+{
+ if (!name.empty())
+ SetName(name);
+}
+
+wxTarEntry::~wxTarEntry()
+{
+}
+
+wxTarEntry::wxTarEntry(const wxTarEntry& e)
+ : 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 + _T("\\") : m_Name);
+ for (size_t i = 0; i < name.length(); i++)
+ if (name[i] == _T('/'))
+ name[i] = _T('\\');
+ return name;
+ }
+
+ case wxPATH_UNIX:
+ return isDir ? m_Name + _T("/") : 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, _T("./")) == 0)
+ internal.erase(0, 2);
+ if (internal == _T(".") || internal == _T(".."))
+ internal = wxEmptyString;
+
+ return internal;
+}
+
+bool wxTarEntry::IsDir() const
+{
+ return m_TypeFlag - wxTAR_DIRTYPE == 0;
+}
+
+void wxTarEntry::SetIsDir(bool isDir)
+{
+ if (isDir)
+ m_TypeFlag = wxTAR_DIRTYPE;
+ else if (m_TypeFlag - wxTAR_DIRTYPE == 0)
+ 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_)
+
+WX_DECLARE_STRING_HASH_MAP(wxString, wxTarHeaderRecords);
+
+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(_T("mtime")));
+ entry->SetAccessTime(GetHeaderDate(_T("atime")));
+ entry->SetCreateTime(GetHeaderDate(_T("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;
+
+ if ((path = GetExtendedHeader(_T("path"))) != wxEmptyString)
+ 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()) + _T("/") + path : path;
+}
+
+wxDateTime wxTarInputStream::GetHeaderDate(const wxString& key) const
+{
+ wxString value;
+
+ // try extended header, stored as decimal seconds since the epoch
+ if ((value = GetExtendedHeader(key)) != wxEmptyString) {
+ wxLongLong ll;
+ ll.Assign(wxAtof(value) * 1000.0);
+ return ll;
+ }
+
+ if (key == _T("mtime"))
+ return wxLongLong(m_hdr->GetOctal(TAR_MTIME)) * 1000L;
+
+ return wxDateTime();
+}
+
+wxTarNumber wxTarInputStream::GetHeaderNumber(int id) const
+{
+ wxString value;
+
+ if ((value = GetExtendedHeader(m_hdr->Name(id))) != wxEmptyString) {
+ wxTarNumber n = 0;
+ const wxChar *p = value;
+ while (*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;
+
+ if ((value = GetExtendedHeader(m_hdr->Name(id))) != wxEmptyString)
+ return value;
+
+ return wxString(m_hdr->Get(id), GetConv());
+}
+
+// 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; recPos += recSize) {
+ char *pRec = buf.data() + recPos;
+ char *p = pRec;
+
+ // read the record size (byte count in ascii decimal)
+ recSize = 0;
+ while (isdigit(*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();
+}
+
+wxTarOutputStream::~wxTarOutputStream()
+{
+ if (m_tarsize)
+ 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 allowd any data
+ const char nodata[] = {
+ wxTAR_LNKTYPE, wxTAR_SYMTYPE, wxTAR_CHRTYPE, wxTAR_BLKTYPE,
+ wxTAR_DIRTYPE, wxTAR_FIFOTYPE, 0
+ };
+ char typeflag = e->GetTypeFlag();
+
+ // pax does now allow data for wxTAR_LNKTYPE
+ if (!m_pax || typeflag - wxTAR_LNKTYPE != 0)
+ 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())
+ 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();
+ 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(_T("mtime"), entry.GetDateTime());
+ if (entry.GetAccessTime().IsValid())
+ SetHeaderDate(_T("atime"), entry.GetAccessTime());
+ if (entry.GetCreateTime().IsValid())
+ SetHeaderDate(_T("ctime"), entry.GetCreateTime());
+
+ *m_hdr->Get(TAR_TYPEFLAG) = 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(_T("%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;
+ }
+
+ // 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(_T('/'));
+ wxString f = path.AfterLast(_T('/'));
+ wxString ret;
+
+ if (d.empty())
+ d = _T(".");
+
+ 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]) {
+ case 'd': ret << d; break;
+ case 'f': ret << f; break;
+ case 'p': ret << wxGetProcessId(); break;
+ case '%': ret << _T("%"); 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(_T("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 != _T("mtime") || !m_hdr->SetOctal(TAR_MTIME, secs.GetValue())
+ || secs <= 0 || secs >= 0x7fffffff)
+ {
+ wxString str;
+ if (ll >= LONG_MIN && ll <= LONG_MAX) {
+ str.Printf(_T("%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) {
+ 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);
+
+ // 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 += _T(", ");
+ 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