2025-11-27 16:46:48 +09:00

694 lines
18 KiB
C++

// ==========================================================================
// Class Specification : COXUUDecoder
// ==========================================================================
// Header file : oxuudec.cpp
// Version: 9.3
// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office. For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
// ///////////////////////////////////////////////////////////////////////////
#include "stdafx.h" // standard MFC include
#include "oxuudec.h" // file header
#include "afxpriv.h" // for CMirrorFile
#include <io.h>
#include <direct.h>
#include <ctype.h>
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNAMIC(COXUUDecoder, CObject)
#define new DEBUG_NEW
#include "OXUUDRet.h"
/////////////////////////////////////////////////////////////////////////////
// Definition of static members
// Data members -------------------------------------------------------------
// protected:
// private:
// Member functions ---------------------------------------------------------
// public:
COXUUDecoder::COXUUDecoder()
: m_buf(m_bufa),
m_bufm1(m_bufb),
m_bufm2(m_bufc),
m_bufm3(m_bufd),
m_goodbuf(-1),
m_goodbufm1(-1),
m_goodbufm2(-1),
m_goodbufm3(-1),
m_bSkipIfExists(FALSE)
{
}
BOOL COXUUDecoder::IsUUEncodedFile(const char* EncodedFilNam)
{
CStdioFile EncodedFile;
if (!EncodedFile.Open((LPCTSTR)EncodedFilNam, CFile::modeRead | CFile::typeBinary | CFile::shareDenyWrite))
return FALSE;
m_buf[0] = 0; /* tells extractuu to search for begin line */
int res = ExtractUU(&EncodedFile, NULL);
m_buf[0] = 0; // Reset Buffer
EncodedFile.Close();
return (res == 0);
}
HRESULT COXUUDecoder::UUDecodeFile(const char* EncodedFilNam, const char* pszDir /* = NULL */,
CStringArray* pTargetFiles /* = NULL */)
{
HRESULT hr;
CStdioFile EncodedFile;
CStringArray TempTargetFiles;
CStringArray* pTrgtFiles = pTargetFiles;
if (pTargetFiles == NULL)
pTrgtFiles = new CStringArray;
if (!EncodedFile.Open((LPCTSTR)EncodedFilNam, CFile::modeRead | CFile::typeBinary | CFile::shareDenyWrite))
return (HRESULT)MSG_UUDEC_OPEN_ENCODED_ERROR;
hr = UUDecodeFile(&EncodedFile, pszDir, pTrgtFiles, &TempTargetFiles);
EncodedFile.Close();
// Examine the TempTargetFiles array to see whether there are some temporary files created
// that have to be renamed
for (int i = 0; i < TempTargetFiles.GetSize(); i++)
{
if (!TempTargetFiles.GetAt(i).IsEmpty() && pTrgtFiles != NULL)
{
CFile::Remove(pTrgtFiles->GetAt(i));
CFile::Rename(TempTargetFiles.GetAt(i), pTrgtFiles->GetAt(i));
}
}
delete pTrgtFiles;
return hr;
}
HRESULT COXUUDecoder::UUDecodeFile(CStdioFile* pEncodFil, const char* pszDir,
CStringArray* pTargetFiles, CStringArray* pTempTargetFiles)
{
ASSERT_VALID(pEncodFil);
HRESULT hr=NULL;
#ifdef WIN32
CMirrorFile TargetFile;
#else
CFile TargetFile;
#endif
CString sTargetName, sTempTargetName;
char targetfile[256]; /* 255 chars max length of a pathname */
int res,success,errors; /* gp result, success and error counters */
int amode; /* "begin xxx" where xxx is amode, for chmod() */
int nTargetIndex(-1); // index of the encoding part in a multiple encoded file
errors = 0;
success = 0;
res = 0;
amode = -1;
targetfile[0] = 0;
while (res < 3)
{ /* 0 success, 1 error, 2 begin before end, 3 eof seen */
if (res < 2)
{ /* skip this if buf[] already contains begin line */
m_buf[0] = 0; /* tells extractuu to search for begin line */
res = ExtractUU(pEncodFil, NULL);
nTargetIndex++;
}
/*
* here if we have res==2 then we must be recovering from a
* begin before end error on the previous pass, recycling buf[],
* the extractuu() above couldn't have generated a res==2 because
* it's looking for a begin so it must have come from the one below
*/
if (res == 0 || res == 2)
{
#if _MSC_VER >= 1400
if (sscanf_s(m_buf, "begin %d %255s", &amode, targetfile) != 2)
#else
if (sscanf(m_buf, "begin %d %255s", &amode, targetfile) != 2)
#endif
{
res = 1;
goto badfilename;
}
// Renaming target when Stringarray has new target name
sTargetName.Empty();
if (pTargetFiles != NULL)
if (pTargetFiles->GetSize() > nTargetIndex)
sTargetName = pTargetFiles->GetAt(nTargetIndex);
if (sTargetName.IsEmpty())
if (pszDir != NULL)
sTargetName = CString(pszDir) + CString("\\") + CString(targetfile);
else
sTargetName = targetfile;
else
TRACE1("renaming target to \"%s\"\n", sTargetName);
/* check for file existance */
sTempTargetName = sTargetName;
#if _MSC_VER >= 1400
if (_access((LPCSTR)(LPCTSTR)sTargetName, 0) == 0)
#else
if (access((LPCSTR)(LPCTSTR)sTargetName, 0) == 0)
#endif
{ /* it exists */
if (m_bSkipIfExists)
{ /* quietly skip without counting as error */
TRACE1("Skipped, file exists : %s\n", sTargetName);
res = 0;
if (pTargetFiles != NULL)
pTargetFiles->SetAtGrow(nTargetIndex, sTargetName);
goto badfilename1;
}
/* check if file has write access */
CFileStatus status;
pEncodFil->GetStatus(status);
if (sTargetName != status.m_szFullName &&
#if _MSC_VER >= 1400
_access((LPCSTR)(LPCTSTR)sTargetName, 2) != 0)
#else
access((LPCSTR)(LPCTSTR)sTargetName, 2) != 0)
#endif
{ /* write not allowed */
TRACE1("\"%s\" ", sTargetName);
TRACE(_T("Skipped, file exists write protected.\n"));
res = 1;
hr = (HRESULT)MSG_UUDEC_WRITING_OF_A_FILE_FAILED;
goto badfilename;
}
// get the directory for a temporary file
sTempTargetName.Empty();
#ifdef WIN32
TCHAR szPath[_MAX_PATH];
GetTempPath(_MAX_PATH, szPath);
//let's create a temporary file name
GetTempFileName(szPath, _T("OXTEMP"), 0,
sTempTargetName.GetBuffer(_MAX_PATH+1));
#else
//let's create a temporary file name
::GetTempFileName(0, _T("OXTEMP"), 0,
sTempTargetName.GetBuffer(_MAX_PATH+1));
#endif
sTempTargetName.ReleaseBuffer();
pTempTargetFiles->SetAtGrow(nTargetIndex, sTempTargetName);
/* we determined that the file is ok to overwrite */
TRACE1("updating \"%s\"\n",targetfile);
}
/* Create the new decoded file */
if (pszDir != NULL)
#if _MSC_VER >= 1400
_mkdir(pszDir);
#else
mkdir(pszDir);
#endif
if (!TargetFile.Open(sTempTargetName, CFile:: modeCreate | CFile::modeWrite | CFile::typeBinary))
{
TRACE2("%s\"%s\"\n", "fatal: can't open target file ", sTempTargetName);
res = 3;
hr = (HRESULT)MSG_UUDEC_WRITING_OF_A_FILE_FAILED;
goto badfilename;
}
/* m_buf[] contains begin line, this tells extractuu to decode */
/* rather than search for the next begin line */
res = ExtractUU(pEncodFil, &TargetFile);
TargetFile.Close();
if (res > 0)
{
TRACE1("trying to delete \"%s\"\n", sTargetName);
#if _MSC_VER >= 1400
if (_unlink((LPCSTR)(LPCTSTR)sTargetName) != 0)
#else
if (unlink((LPCSTR)(LPCTSTR)sTargetName) != 0)
#endif
TRACE1("error deleting \"%s\"\n",sTargetName);
}
else
{
if (pTargetFiles != NULL)
pTargetFiles->SetAtGrow(nTargetIndex, sTargetName);
}
}
badfilename:
if (res == 0)
success++;
badfilename1:
if (res == 1 || res == 2)
errors++;
} /* <-- end of while (res<3) { ... */
if (errors != 0)
return (hr);
return (HRESULT)MSG_UUDEC_SUCCESS;
}
#ifdef _DEBUG
void COXUUDecoder::Dump(CDumpContext& dc) const
{
CObject::Dump(dc);
}
void COXUUDecoder::AssertValid() const
{
CObject::AssertValid();
}
#endif
COXUUDecoder::~COXUUDecoder()
{
}
// protected:
void COXUUDecoder::FixEOLN(char buf[], int n)
// --- In : buf[] : buffer to be examined
// n : length of buffer
// --- Out :
// --- Returns :
// --- Effect : This subroutine strips all <cr> and <lf> from the end of buf[]
// This is done because a lot of Internet-News newbies don't know how to
// uuencode their files properly.
// Standard unix uudecode programs usually puke if there is a ^M in the line.
{
size_t len,nn;
int done;
if (n < 1)
return;
nn = (size_t)n - 1;
len = strlen(buf);
if (nn < len)
len = n;
done=0;
while (len>0 && !done)
{
switch (buf[len-1])
{
case '\r':
len--;
break;
case '\n':
len--;
break;
case ' ':
if (len > 61)
len--;
else
done = 1;
break;
default:
done = 1;
}
}
buf[len] = '\0';
return;
}
BOOL COXUUDecoder::GetLongLine(char* s, int n, CStdioFile* pFile)
// --- In : s : buffer to store read line
// n : max length of line to read
// pFile : the encoded file
// --- Out :
// --- Returns :
// --- Effect : This subroutine is just like the standard fgets() except smarter.
// When lines in the stream are longer than the buffer size, the line gets
// truncated and the stream is purged until the actual <lf> is seen.
// This works just like the Pascal readln(f,stringvar)
{
char buf[32];
char* result;
size_t nn;
TRY
{
result = (LPSTR)pFile->ReadString((LPTSTR)s, n);
if (n < 2 || result == NULL)
return FALSE;
nn = (size_t)n - 1;
if (strlen(s) == nn && s[n-2] != '\n')
{
while (pFile->ReadString((LPTSTR)buf, sizeof(buf)))
if (strlen(buf) + 1 < sizeof(buf) || buf[sizeof(buf) - 2] == '\n')
break;
}
FixEOLN(s, n);
}
CATCH(CFileException, e)
{
return FALSE;
}
END_CATCH
return TRUE;
}
int COXUUDecoder::IsGoodBuf(char buf[], int n)
// --- In : buf[] : buffer to examine
// n : length of buffer
// --- Out :
// --- Returns :
// --- Effect : This subroutine checks buf to see if it looks like
// a standard uuencode line and returns a number for best guess.
//
// returned values are one of the following...
// 0) unknown, possibly text or short last line
// 1) "begin 644 filename" type of line
// 2) "M..." etc., main body line, short last line is not detected as 2
// 3) "`" or " ", this is usually the 2nd from last line in a uuencode
// 4) "end", usually last line of a uuencode
//
// n is the size of buf[]
{
size_t len,nn;
len = strlen(buf);
nn = (size_t)n - 1;
if (nn < len)
len = nn;
if (len == 61 && buf[0] == 'M')
return(2);
if (len > 10 && strncmp(buf, "begin ", 6) == 0 && isdigit(buf[6]) &&
isdigit(buf[7]) && isdigit(buf[8]))
return(1);
if (len >= 1 && (buf[0] == ' ' || buf[0] == '`'))
return(3);
if (len >= 3 && strncmp(buf, "end", 3) == 0)
return(4);
return(0);
}
int COXUUDecoder::UUD1Line(CFile* pFile, char buf[])
// --- In : pFile : file to write to
// buf : buffer contaning encode chars
// --- Out :
// --- Returns :
// --- Effect : uudecode 1 line, assumes buf[] is a valid uuencoded line
// if test=1 then range check every character decoded, a little slower!
// return 0 if no error
// return 1 if general error
// return 2 if illegal character (only when test=1)
{
int len,b,i,j;
size_t l;
unsigned char c0, c1, x, *from, *to;
static unsigned char binbuf[45];
static const unsigned char c[3][4] = { /* used by slow decoder below */
{2, 4, 6 , 0 },
{4, 2, 0 , 0 },
{0x03, 0x0f, 0x3f, 0 }
};
len = (int)buf[0] - 0x20; /* first char on line is # 8 bit bytes to decode */
l = strlen((const char*)buf);
b = 0;
/* this is the FAST! decoder for the main body lines */
TRY
{
if (len == 45 && l >= 61)
{
from = (unsigned char*)&buf[1];
to = binbuf;
while ( b < len)
{
if (*from < ' ' || *from > '`')
return(2);
*(to++) = (unsigned char)(((*from - 0x20) << 2) | (((*(from + 1) - 0x20) >> 4) & 0x03));
from++;
if (*from < ' ' || *from > '`')
return(2);
*(to++) = (unsigned char)(((*from - 0x20) << 4) | (((*(from + 1) - 0x20) >> 2) & 0x0f));
from++;
if (*from < ' ' || *from > '`')
return(2);
*(to++) = (unsigned char)(((*from - 0x20) << 6) | (( *(from + 1) - 0x20 ) & 0x3f));
from++;
if (*from < ' ' || *from > '`')
return(2);
from++;
b = b + 3;
}
pFile->Write(binbuf, len);
return(0);
}
if (len < 1 || len > 45)
{
return(2);
}
while (b < len)
{ /* this is the slow decoder for the last short line */
i = b % 3;
j = b * 4 / 3 + 1;
if ((size_t)j < l)
c0 = buf[j];
else
c0 = ' ';
if ((size_t)j + 1 < l)
c1 = buf[j + 1];
else
c1 = ' ';
if (c0 < ' ' || c0 > '`' || c1 < ' ' || c1 > '`')
return(2);
x = (unsigned char)(((c0 - 0x20) << c[0][i]) + (((c1 - 0x20) >> c[1][i]) & c[2][i]));
pFile->Write(&x, 1);
b++;
}
}
CATCH(CFileException, e)
{
return(1);
}
END_CATCH
return(0);
}
void COXUUDecoder::rotatebufs()
{
char* temp;
temp = m_bufm3;
m_bufm3 = m_bufm2;
m_bufm2 = m_bufm1;
m_bufm1 = m_buf;
m_buf = temp;
m_buf[0] = 0;
m_goodbufm3 = m_goodbufm2;
m_goodbufm2 = m_goodbufm1;
m_goodbufm1 = m_goodbuf;
m_goodbuf = -1;
}
void COXUUDecoder::clearmbufs()
{
m_bufm3[0] = 0;
m_bufm2[0] = 0;
m_bufm1[0] = 0;
m_goodbufm3 = -1;
m_goodbufm2 = -1;
m_goodbufm1 = -1;
}
int COXUUDecoder::ExtractUU(CStdioFile* pfrom, /* pointer to input file*/
CFile* pTo) /* pointer to output file*/
// --- In : pfrom : file to read from
// pTo : file to write to
// --- Out :
// --- Returns :
// --- Effect : This is the main extract subroutine.
// buf[] is used in the event that a new "begin" is encountered
// before the ending of the previous uuencoding. This is an error
// condition and to keep from messing up the next uudecode which is
// probably ok, this begin line is stuffed into the buf[] for
// the next pass through extractuu().
// returned values...
// 0=success
// 1=error
// 2=begin line encountered before end line decoded
// 3=eof seen
{
int amode,res;
char fn[BUFSIZE];
if (strlen(m_buf) == 0)
{
retry:
m_goodbuf = 0;
while (GetLongLine(m_buf, BUFSIZE, pfrom))
if ((m_goodbuf = IsGoodBuf(m_buf, BUFSIZE)) == 1)
break;
if (m_goodbuf != 1)
return(3); /* indicate eof seen */
#if _MSC_VER >= 1400
if (sscanf_s(m_buf, "begin %d %80s", &amode, fn) != 2)
#else
if (sscanf(m_buf, "begin %d %80s", &amode, fn) != 2)
#endif
goto retry;
return(0);
}
/* here when begin line was found and is in buf[], now do decoding */
clearmbufs(); /* clear back history buffers */
m_goodbuf = 1; /* assume begin line is in buf[] */
if (IsGoodBuf(m_buf, BUFSIZE) != m_goodbuf)
{
return(1);
}
rotatebufs();
while (GetLongLine(m_buf, BUFSIZE, pfrom))
{
m_goodbuf = IsGoodBuf(m_buf, BUFSIZE);
if (m_goodbufm1 == 2 && (m_goodbuf == 2 || m_goodbufm2 == 2 || m_goodbufm2 == 1))
if ((res = UUD1Line(pTo, m_bufm1)) != 0)
return(1);
if (m_goodbuf == 1)
return(2);
/* end detected...logic for what to do with 3rd from last line bufm2
* (for relax=0 or 1)
*
* ignore this end | decode line bufm2 | just exit, already decoded
* (m2) (m3) | (m2) (m3) | (m2) (m3) <-- goodbuf
* ----------------+-------------------+---------------------------
* 0 0 | 0 1 | 1 x
* 0 3 | 0 2 | 2 1
* 0 4 | 2 0 | 2 2
* 2 3 | |
* 2 4 | |
* 3 x | |
* 4 x | |
*/
if (m_goodbuf == 4 && m_goodbufm1 == 3)
{ /* end sequence seen */
if (m_goodbufm2 == 1)
return(0);
if ((m_goodbufm2 == 0 && (m_goodbufm3 == 0 || m_goodbufm3 >= 3)) ||
(m_goodbufm2 >= 3) || (m_goodbufm2 == 2 && m_goodbufm3 >= 3))
goto nextline;
if ((m_goodbufm2 == 0 && (m_goodbufm3 == 1 || m_goodbufm3 == 2)) ||
(m_goodbufm2 == 2 && m_goodbufm3 == 0))
{
if ((res = UUD1Line(pTo, m_bufm2)) != 0)
return(1);
}
return(0);
}
/* end detected...logic for what to do with 2nd from last line bufm1
* (for relax=1)
*
* ignore this end | decode line bufm2 | just exit, already decoded
* (m1) (m2) | (m1) (m2) | (m1) (m2) <-- goodbuf
* ----------------+-------------------+---------------------------
* 0 0 | 0 1 | 1 x
* 0 3 | 0 2 | 2 1
* 0 4 | 2 0 | 2 2
* 2 3 | |
* 2 4 | |
* 3 x | |
* 4 x | |
*/
if (m_goodbuf == 4)
{ /* end sequence seen */
if (m_goodbufm1 == 1)
return(0);
if ((m_goodbufm1 == 0 && (m_goodbufm2 == 0 || m_goodbufm2 >= 3)) ||
(m_goodbufm1 >= 3) || (m_goodbufm1 == 2 && m_goodbufm2 >= 3))
goto nextline;
if ((m_goodbufm1 == 0 && (m_goodbufm2 == 1 || m_goodbufm2 == 2)) ||
(m_goodbufm1 == 2 && m_goodbufm2 == 0))
{
if ((res = UUD1Line(pTo, m_bufm1)) != 0)
return(1);
}
return(0);
}
nextline:
rotatebufs();
}
if (m_goodbufm1 == 2 && (m_goodbufm2 == 1 || m_goodbufm2 == 2))
res = UUD1Line(pTo, m_bufm1);
return(3); /* indicate end of input file */
}
// private:
// ==========================================================================