1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00
Files
winscp/source/filezilla/FtpListResult.cpp
Martin Prikryl 3f4e41fee0 Clang imposed changes that should be noop and have no effect on classic compiler
Mostly:
– incorrectly identified uninitialized variables
– explicit static global variables
– const, sign and down casts and safety
– typename use
– semicolon after function implementation
– unused variables, fields, arguments and macros
– field initialization order
– exact include filename case
– redundant in code macros already defined at project level

(cherry picked from commit f7f00c631e)

Source commit: 90ad9390853aba308abe73cf02e0281238c53c87
2025-03-13 16:46:26 +01:00

2847 lines
70 KiB
C++

//---------------------------------------------------------------------------
#include "stdafx.h"
#include "FtpListResult.h"
#include "FileZillaApi.h"
#include <WideStrUtils.hpp>
CFtpListResult::CFtpListResult(t_server server, bool mlst, bool *bUTF8, bool vmsAllRevisions, bool debugShowListing)
{
m_mlst = mlst;
m_server = server;
m_bUTF8 = bUTF8;
m_vmsAllRevisions = vmsAllRevisions;
m_debugShowListing = debugShowListing;
//Fill the month names map
//English month names
m_MonthNamesMap[L"jan"] = 1;
m_MonthNamesMap[L"feb"] = 2;
m_MonthNamesMap[L"mar"] = 3;
m_MonthNamesMap[L"apr"] = 4;
m_MonthNamesMap[L"may"] = 5;
m_MonthNamesMap[L"jun"] = 6;
m_MonthNamesMap[L"june"] = 6;
m_MonthNamesMap[L"jul"] = 7;
m_MonthNamesMap[L"july"] = 7;
m_MonthNamesMap[L"aug"] = 8;
m_MonthNamesMap[L"sep"] = 9;
m_MonthNamesMap[L"sept"] = 9;
m_MonthNamesMap[L"oct"] = 10;
m_MonthNamesMap[L"nov"] = 11;
m_MonthNamesMap[L"dec"] = 12;
//Numerical values for the month
m_MonthNamesMap[L"1"] = 1;
m_MonthNamesMap[L"01"] = 1;
m_MonthNamesMap[L"2"] = 2;
m_MonthNamesMap[L"02"] = 2;
m_MonthNamesMap[L"3"] = 3;
m_MonthNamesMap[L"03"] = 3;
m_MonthNamesMap[L"4"] = 4;
m_MonthNamesMap[L"04"] = 4;
m_MonthNamesMap[L"5"] = 5;
m_MonthNamesMap[L"05"] = 5;
m_MonthNamesMap[L"6"] = 6;
m_MonthNamesMap[L"06"] = 6;
m_MonthNamesMap[L"7"] = 7;
m_MonthNamesMap[L"07"] = 7;
m_MonthNamesMap[L"8"] = 8;
m_MonthNamesMap[L"08"] = 8;
m_MonthNamesMap[L"9"] = 9;
m_MonthNamesMap[L"09"] = 9;
m_MonthNamesMap[L"10"] = 10;
m_MonthNamesMap[L"11"] = 11;
m_MonthNamesMap[L"12"] = 12;
//German month names
m_MonthNamesMap[L"mrz"] = 3;
m_MonthNamesMap[L"m\xE4r"] = 3;
m_MonthNamesMap[L"m\xE4rz"] = 3;
m_MonthNamesMap[L"mai"] = 5;
m_MonthNamesMap[L"juni"] = 6;
m_MonthNamesMap[L"m\xC3\xA4r"] = 3; // UTF-8
m_MonthNamesMap[L"m\xC3\xA4rz"] = 3; // UTF-8
m_MonthNamesMap[L"juli"] = 7;
m_MonthNamesMap[L"okt"] = 10;
m_MonthNamesMap[L"dez"] = 12;
//Austrian month names
m_MonthNamesMap[L"j\xE4n"] = 1;
m_MonthNamesMap[L"j\xC3\xA4n"] = 1; // UTF-8
//French month names
m_MonthNamesMap[L"janv"] = 1;
m_MonthNamesMap[L"f\xE9" L"b"] = 1;
m_MonthNamesMap[L"f\xE9v"] = 2;
m_MonthNamesMap[L"fev"] = 2;
m_MonthNamesMap[L"f\xE9vr"] = 2;
m_MonthNamesMap[L"fevr"] = 2;
m_MonthNamesMap[L"mars"] = 3;
m_MonthNamesMap[L"mrs"] = 3;
m_MonthNamesMap[L"avr"] = 4;
m_MonthNamesMap[L"juin"] = 6;
m_MonthNamesMap[L"juil"] = 7;
m_MonthNamesMap[L"jui"] = 7;
m_MonthNamesMap[L"ao\xFB"] = 8;
m_MonthNamesMap[L"ao\xFBt"] = 8;
m_MonthNamesMap[L"aout"] = 8;
m_MonthNamesMap[L"d\xE9" L"c"] = 12;
m_MonthNamesMap[L"dec"] = 12;
//Italian month names
m_MonthNamesMap[L"gen"] = 1;
m_MonthNamesMap[L"mag"] = 5;
m_MonthNamesMap[L"giu"] = 6;
m_MonthNamesMap[L"lug"] = 7;
m_MonthNamesMap[L"ago"] = 8;
m_MonthNamesMap[L"set"] = 9;
m_MonthNamesMap[L"ott"] = 10;
m_MonthNamesMap[L"dic"] = 12;
//Spanish month names
m_MonthNamesMap[L"ene"] = 1;
m_MonthNamesMap[L"fbro"] = 2;
m_MonthNamesMap[L"mzo"] = 3;
m_MonthNamesMap[L"ab"] = 4;
m_MonthNamesMap[L"abr"] = 4;
m_MonthNamesMap[L"agto"] = 8;
m_MonthNamesMap[L"sbre"] = 9;
m_MonthNamesMap[L"obre"] = 9;
m_MonthNamesMap[L"nbre"] = 9;
m_MonthNamesMap[L"dbre"] = 9;
//Polish month names
m_MonthNamesMap[L"sty"] = 1;
m_MonthNamesMap[L"lut"] = 2;
m_MonthNamesMap[L"kwi"] = 4;
m_MonthNamesMap[L"maj"] = 5;
m_MonthNamesMap[L"cze"] = 6;
m_MonthNamesMap[L"lip"] = 7;
m_MonthNamesMap[L"sie"] = 8;
m_MonthNamesMap[L"wrz"] = 9;
m_MonthNamesMap[L"pa"] = 10;
m_MonthNamesMap[L"lis"] = 11;
m_MonthNamesMap[L"gru"] = 12;
//Russian month names
m_MonthNamesMap[L"\xFF\xED\xE2"] = 1;
m_MonthNamesMap[L"\xF4\xE5\xE2"] = 2;
m_MonthNamesMap[L"\xEC\xE0\xF0"] = 3;
m_MonthNamesMap[L"\xE0\xEF\xF0"] = 4;
m_MonthNamesMap[L"\xEC\xE0\xE9"] = 5;
m_MonthNamesMap[L"\xE8\xFE\xED"] = 6;
m_MonthNamesMap[L"\xE8\xFE\xEB"] = 7;
m_MonthNamesMap[L"\xE0\xE2\xE3"] = 8;
m_MonthNamesMap[L"\xF1\xE5\xED"] = 9;
m_MonthNamesMap[L"\xEE\xEA\xF2"] = 10;
m_MonthNamesMap[L"\xED\xEE\xFF"] = 11;
m_MonthNamesMap[L"\xE4\xE5\xEA"] = 12;
//Dutch month names
m_MonthNamesMap[L"mrt"] = 3;
m_MonthNamesMap[L"mei"] = 5;
//Portuguese month names
m_MonthNamesMap[L"out"] = 10;
//Japanese month names
m_MonthNamesMap[L"1\x8c\x8e"] = 1;
m_MonthNamesMap[L"2\x8c\x8e"] = 2;
m_MonthNamesMap[L"3\x8c\x8e"] = 3;
m_MonthNamesMap[L"4\x8c\x8e"] = 4;
m_MonthNamesMap[L"5\x8c\x8e"] = 5;
m_MonthNamesMap[L"6\x8c\x8e"] = 6;
m_MonthNamesMap[L"7\x8c\x8e"] = 7;
m_MonthNamesMap[L"8\x8c\x8e"] = 8;
m_MonthNamesMap[L"9\x8c\x8e"] = 9;
m_MonthNamesMap[L"10\x8c\x8e"] = 10;
m_MonthNamesMap[L"11\x8c\x8e"] = 11;
m_MonthNamesMap[L"12\x8c\x8e"] = 12;
//Korean (Unicode) month names
m_MonthNamesMap[L"1\xC6\xD4"] = 1;
m_MonthNamesMap[L"2\xC6\xD4"] = 2;
m_MonthNamesMap[L"3\xC6\xD4"] = 3;
m_MonthNamesMap[L"4\xC6\xD4"] = 4;
m_MonthNamesMap[L"5\xC6\xD4"] = 5;
m_MonthNamesMap[L"6\xC6\xD4"] = 6;
m_MonthNamesMap[L"7\xC6\xD4"] = 7;
m_MonthNamesMap[L"8\xC6\xD4"] = 8;
m_MonthNamesMap[L"9\xC6\xD4"] = 9;
m_MonthNamesMap[L"10\xC6\xD4"] = 10;
m_MonthNamesMap[L"11\xC6\xD4"] = 11;
m_MonthNamesMap[L"12\xC6\xD4"] = 12;
//Korean (EUC-KR) month names
m_MonthNamesMap[L"1\xBF\xF9"] = 1;
m_MonthNamesMap[L"2\xBF\xF9"] = 2;
m_MonthNamesMap[L"3\xBF\xF9"] = 3;
m_MonthNamesMap[L"4\xBF\xF9"] = 4;
m_MonthNamesMap[L"5\xBF\xF9"] = 5;
m_MonthNamesMap[L"6\xBF\xF9"] = 6;
m_MonthNamesMap[L"7\xBF\xF9"] = 7;
m_MonthNamesMap[L"8\xBF\xF9"] = 8;
m_MonthNamesMap[L"9\xBF\xF9"] = 9;
m_MonthNamesMap[L"10\xBF\xF9"] = 10;
m_MonthNamesMap[L"11\xBF\xF9"] = 11;
m_MonthNamesMap[L"12\xBF\xF9"] = 12;
//Finnish month names
m_MonthNamesMap[L"tammi"] = 1;
m_MonthNamesMap[L"helmi"] = 2;
m_MonthNamesMap[L"maalis"] = 3;
m_MonthNamesMap[L"huhti"] = 4;
m_MonthNamesMap[L"touko"] = 5;
m_MonthNamesMap[L"kes\xE4"] = 6;
m_MonthNamesMap[L"hein\xE4"] = 7;
m_MonthNamesMap[L"elo"] = 8;
m_MonthNamesMap[L"syys"] = 9;
m_MonthNamesMap[L"loka"] = 10;
m_MonthNamesMap[L"marras"] = 11;
m_MonthNamesMap[L"joulu"] = 12;
//There are more languages and thus month
//names, but as long nobody reports a
//problem, I won't add them, there are way
//too much languages
//Some very strange combinations of names and numbers I've seen.
//The developers of those ftp servers must have been dumb.
m_MonthNamesMap[L"jan1"] = 1;
m_MonthNamesMap[L"feb2"] = 2;
m_MonthNamesMap[L"mar3"] = 3;
m_MonthNamesMap[L"apr4"] = 4;
m_MonthNamesMap[L"may5"] = 5;
m_MonthNamesMap[L"jun6"] = 6;
m_MonthNamesMap[L"jul7"] = 7;
m_MonthNamesMap[L"aug8"] = 8;
m_MonthNamesMap[L"sep9"] = 9;
m_MonthNamesMap[L"sept9"] = 9;
m_MonthNamesMap[L"oct0"] = 10;
m_MonthNamesMap[L"nov1"] = 11;
m_MonthNamesMap[L"dec2"] = 12;
// Slovenian month names
m_MonthNamesMap[L"avg"] = 8;
}
t_directory::t_direntry * CFtpListResult::getList(int & Num)
{
if (!FBuffer.IsEmpty())
{
SendLineToMessageLog("Unparsed listing:");
SendLineToMessageLog(FBuffer);
}
Num = m_EntryList.size();
t_directory::t_direntry * Result;
if (Num == 0)
{
SendLineToMessageLog("<Empty directory listing>");
Result = NULL;
}
else
{
Result = new t_directory::t_direntry[Num];
int I = 0;
for (tEntryList::iterator Iter = m_EntryList.begin(); Iter != m_EntryList.end(); Iter++, I++)
{
Result[I] = *Iter;
}
m_EntryList.clear();
}
return Result;
}
BOOL CFtpListResult::parseLine(const char *lineToParse, const int linelen, t_directory::t_direntry &direntry)
{
direntry.ownergroup = L"";
direntry.owner = L"";
direntry.group = L"";
if (parseAsMlsd(lineToParse, linelen, direntry))
return TRUE;
if (parseAsUnix(lineToParse, linelen, direntry))
return TRUE;
if (parseAsDos(lineToParse, linelen, direntry))
return TRUE;
if (parseAsEPLF(lineToParse, linelen, direntry))
return TRUE;
if (parseAsVMS(lineToParse, linelen, direntry))
{
m_server.nServerType |= FZ_SERVERTYPE_SUB_FTP_VMS;
return TRUE;
}
if (parseAsOther(lineToParse, linelen, direntry))
return TRUE;
if (parseAsIBMMVS(lineToParse, linelen, direntry))
return TRUE;
if (parseAsIBMMVSPDS(lineToParse, linelen, direntry))
return TRUE;
if (parseAsIBM(lineToParse, linelen, direntry))
return TRUE;
if (parseAsWfFtp(lineToParse, linelen, direntry))
return TRUE;
// Should be last
if (parseAsIBMMVSPDS2(lineToParse, linelen, direntry))
return TRUE;
// name-only entries
// (multiline VMS entries have only a name on the first line, so for VMS we have to skip this)
if (FLAGCLEAR(m_server.nServerType, FZ_SERVERTYPE_SUB_FTP_VMS))
{
RawByteString Buf(lineToParse);
// z/OS PDS members without ISPF statistics (name only) still have loads of spaces after them.
Buf = Buf.TrimRight();
if (Buf.Pos(' ') == 0)
{
direntry = t_directory::t_direntry();
direntry.name = Buf.c_str();
return TRUE;
}
}
return FALSE;
}
bool CFtpListResult::IsNewLineChar(char C) const
{
return
(C == '\r') || (C == '\n') ||
// Some of the parsing code cannot handle null characters, so if a malformed server sends some, treat is as a newline
(C == '\0');
}
void CFtpListResult::AddData(const char * Data, int Size)
{
FBuffer += RawByteString(Data, Size);
// Just in case the previous buffer was terminated between CR and LF.
while (!FBuffer.IsEmpty() && IsNewLineChar(FBuffer[1]))
{
FBuffer.Delete(1, 1);
}
bool Found;
int Pos;
int FirstLineEnd;
int Count;
bool Restart = true;
do
{
if (Restart)
{
Pos = 1;
FirstLineEnd = -1;
Count = 0;
Restart = false;
}
std::vector<RawByteString> Lines;
while ((Pos <= FBuffer.Length()) && !IsNewLineChar(FBuffer[Pos]))
{
Pos++;
}
Found = (Pos <= FBuffer.Length());
if (Found)
{
Count++;
RawByteString Record = FBuffer.SubString(1, Pos - 1);
while ((Pos <= FBuffer.Length()) && IsNewLineChar(FBuffer[Pos]))
{
Pos++;
}
if (FirstLineEnd < 0)
{
FirstLineEnd = Pos;
}
t_directory::t_direntry DirEntry;
RawByteString Line = Record;
for (int Index = 1; Index <= Line.Length(); Index++)
{
if (IsNewLineChar(Line[Index]))
{
Line[Index] = ' ';
}
}
if (parseLine(Line.c_str(), Line.Length(), DirEntry))
{
if ((DirEntry.name != L".") && (DirEntry.name != L".."))
{
AddLine(DirEntry);
}
FBuffer.Delete(1, Pos - 1);
Restart = true;
SendLineToMessageLog(Record);
}
else
{
if (Count == 2)
{
RawByteString FirstLine = FBuffer.SubString(1, FirstLineEnd - 1).TrimRight();
SendLineToMessageLog("Cannot parse line:");
SendLineToMessageLog(FirstLine);
FBuffer.Delete(1, FirstLineEnd - 1);
Restart = true;
}
}
}
}
while (Found);
}
void CFtpListResult::SendLineToMessageLog(const RawByteString & Line)
{
if (m_debugShowListing)
{
t_ffam_statusmessage * Status = new t_ffam_statusmessage;
Status->post = TRUE;
Status->status = Line.c_str();
Status->type = FZ_LOG_INFO;
if (!GetIntern()->PostMessage(FZ_MSG_MAKEMSG(FZ_MSG_STATUS, 0), (LPARAM)Status))
{
delete Status;
}
}
}
void CFtpListResult::AddLine(t_directory::t_direntry & direntry)
{
if (m_server.nTimeZoneOffset &&
direntry.date.hasdate && direntry.date.hastime && !direntry.date.utc)
{
SYSTEMTIME st = {0};
st.wYear = direntry.date.year;
st.wMonth = direntry.date.month;
st.wDay = direntry.date.day;
st.wHour = direntry.date.hour;
st.wMinute = direntry.date.minute;
st.wSecond = direntry.date.second;
FILETIME ft;
SystemTimeToFileTime(&st, &ft);
_int64 nFt = ((_int64)ft.dwHighDateTime << 32) + ft.dwLowDateTime;
_int64 nFt2 = nFt;
nFt += ((_int64)m_server.nTimeZoneOffset) * 10000000 * 60;
ft.dwHighDateTime = static_cast<unsigned long>(nFt >> 32);
ft.dwLowDateTime = static_cast<unsigned long>(nFt & 0xFFFFFFFF);
FileTimeToSystemTime(&ft, &st);
direntry.date.year = st.wYear;
direntry.date.month = st.wMonth;
direntry.date.day = st.wDay;
direntry.date.hour = st.wHour;
direntry.date.minute = st.wMinute;
direntry.date.second = st.wSecond;
}
if (m_server.nServerType&FZ_SERVERTYPE_SUB_FTP_VMS &&
(!m_vmsAllRevisions || direntry.dir))
{ //Remove version information, only keep the latest file
int pos=direntry.name.ReverseFind(L';');
if (pos<=0 || pos>=(direntry.name.GetLength()-1))
return;
int version=_ttoi(direntry.name.Mid(pos+1));
direntry.name=direntry.name.Left(pos);
tEntryList::iterator entryiter=m_EntryList.begin();
tTempData::iterator dataiter=m_TempData.begin();
BOOL bContinue=FALSE;
while (entryiter!=m_EntryList.end())
{
DebugAssert(dataiter!=m_TempData.end());
t_directory::t_direntry dir=*entryiter;
int oldversion=*dataiter;
if (direntry.name==dir.name)
{
bContinue=TRUE;
if (version>oldversion)
{
*entryiter=direntry;
*dataiter=version;
}
break;
}
entryiter++;
dataiter++;
}
if (bContinue)
return;
m_EntryList.push_back(direntry);
m_TempData.push_back(version);
}
else
{
m_EntryList.push_back(direntry);
m_TempData.push_back(0);
}
}
bool CFtpListResult::IsNumeric(const char *str, int len) const
{
if (!str)
return false;
if (!*str)
return false;
const char *p=str;
while(*p)
{
if (len != -1)
if ((p - str) >= len)
return true;
if (*p<'0' || *p>'9')
{
return false;
}
p++;
}
return true;
}
bool CFtpListResult::ParseShortDate(const char *str, int len, t_directory::t_direntry::t_date &date) const
{
if (!str)
return false;
if (len <= 0)
return false;
int i=0;
//Extract the date
BOOL bGotYear = FALSE;
BOOL bGotMonth = FALSE;
BOOL bGotDay = FALSE;
int value = 0;
bool numeric = true;
while (str[i] != '-' && str[i] != '.' && str[i] != '/')
{
if (!str[i])
return false;
// Left half of token not numeric, check if it's a month name
if (str[i] < '0' || str[i] > '9')
numeric = false;
else
{
value *= 10;
value += str[i] - '0';
}
i++;
if (i == len)
return false;
}
if (!i)
return false;
if (!numeric)
{
std::map<CString, int>::const_iterator iter;
char *tmpstr = new char[i + 1];
strncpy(tmpstr, str, i);
tmpstr[i] = 0;
strlwr(tmpstr);
USES_CONVERSION;
iter = m_MonthNamesMap.find(A2T(tmpstr));
delete [] tmpstr;
if (iter == m_MonthNamesMap.end())
return false;
date.month = iter->second;
bGotMonth = true;
}
else if (i == 4)
{ //Seems to be yyyy-mm-dd
if (value < 1900)
return false;
date.year = value;
bGotYear = TRUE;
}
else if (i <= 2)
{
if (str[i] == '.')
{
// Maybe dd.mm.yyyy
if (!value || value > 31)
return false;
date.day = value;
bGotDay = TRUE;
}
else
{
// Seems to be mm-dd-yyyy or mm/dd/yyyy (stupid format, though)
if (!value)
return false;
else if (value > 12) // sigh, guess dd/mm/yyyy instead
{
date.day = value;
bGotDay = true;
}
else
{
date.month = value;
bGotMonth = TRUE;
}
}
}
else
return false;
//Extract the second date field
const char *p = str + i + 1;
len -= i + 1;
i=0;
value=0;
if (i >= len)
return false;
while (p[i]!='-' && p[i]!='.' && p[i]!='/')
{
value *= 10;
value += p[i]-'0';
i++;
if (i >= len)
return false;
}
if (bGotYear || bGotDay)
{
// Month field in yyyy-mm-dd or dd-mm-yyyy
if (!value || value > 12)
return false;
date.month = value;
bGotMonth = TRUE;
}
else
{
// Day field in mm-dd-yyyy
if (!value || value > 31)
return false;
date.day = value;
bGotDay = TRUE;
}
//Extract the last date field
p += i+1;
len -= i + 1;
i=0;
value=0;
if (i >= len)
return false;
while (p[i]!='-' && p[i]!='.' && p[i]!='/')
{
value *= 10;
value += p[i]-'0';
i++;
if (i >= len)
break;
}
if (bGotYear)
{
// Day field in yyyy-mm-dd
if (!value || value > 31)
return false;
date.day = value;
}
else
{
//Year in dd.mm.yyyy or mm-dd-yyyy
date.year = value;
if (date.year<50)
date.year+=2000;
else if (date.year<1000)
date.year += 1900;
}
date.hasdate = TRUE;
date.hasyear = TRUE;
return true;
}
BOOL CFtpListResult::parseAsVMS(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
int tokenlen = 0;
int pos = 0;
USES_CONVERSION;
std::map<CString, int>::const_iterator iter;
t_directory::t_direntry dir;
dir.bUnsure = FALSE;
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!strnchr(str, tokenlen, ';'))
return FALSE;
dir.size = -2;
const char *separator = strnchr(str, tokenlen, ';');
if (!separator)
return FALSE;
dir.dir = FALSE;
if ((separator - str) > 4)
if (*(separator - 4) == '.')
if (*(separator - 3) == 'D')
if (*(separator - 2) == 'I')
if (*(separator - 1) == 'R')
{
dir.dir = TRUE;
}
if (dir.dir)
{
int i;
LPTSTR pBuffer = dir.name.GetBuffer(tokenlen - 4);
for (i = 0; i < (separator - str - 4); i++)
pBuffer[i] = str[i];
for (i = 0; i < (tokenlen - (separator - str)); i++)
pBuffer[i + (separator - str) - 4] = separator[i];
dir.name.ReleaseBuffer(tokenlen - 4);
}
else
copyStr(dir.name, 0, str, tokenlen);
// This field is either the size or a username (???) enclosed in [].
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
bool gotSize = false;
const char *p = strnchr(str, tokenlen, '/');
if (!p && IsNumeric(str, tokenlen))
{
gotSize = true;
dir.size = strntoi64(str, tokenlen) * 512;
}
else if (p && p > str && IsNumeric(str, p - str))
{
gotSize = true;
dir.size = strntoi64(str, p - str) * 512;
}
else
{
if (tokenlen < 3 || str[0] != '[' || str[tokenlen - 1] != ']')
return false;
copyStr(dir.ownergroup, 0, str + 1, tokenlen - 2);
}
if (!gotSize)
{
//Size
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
const char *p = strnchr(str, tokenlen, '/');
int len;
if (p)
len = p - str;
else
len = tokenlen;
if (!IsNumeric(str, len))
return FALSE;
dir.size = strntoi64(str, len) * 512;
}
//Get date
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
dir.date.hasdate = TRUE;
//Day
p = str;
while (*p != '-')
if ((++p - str) == tokenlen)
return 0;
dir.date.day = static_cast<int>(strntoi64(str, p-str));
if (!dir.date.day || dir.date.day > 31)
return FALSE;
p++;
const char *pMonth = p;
//Month
while (*p != '-')
if ((++p - str) == tokenlen)
return FALSE;
if ((p - pMonth) >= 15)
return FALSE;
char buffer[15] = {0};
memcpy(buffer, pMonth, p-pMonth);
strlwr(buffer);
iter = m_MonthNamesMap.find(A2T(buffer));
if (iter == m_MonthNamesMap.end())
return FALSE;
dir.date.month = iter->second;
p++;
dir.date.year = static_cast<int>(strntoi64(p, tokenlen - (p - str)));
dir.date.hasyear = TRUE;
//Get time
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (str && strnchr(str + 1, tokenlen - 2, ':'))
{
dir.date.hastime = TRUE;
p = str;
//Hours
while (*p != ':')
if ((++p - str) == tokenlen)
return 0;
dir.date.hour = static_cast<int>(strntoi64(str, p - str));
if (dir.date.hour < 0 || dir.date.hour > 23)
return FALSE;
p++;
const char *pMinute = p;
//Minutes
while (*p && *p != ':' )
p++;
dir.date.minute = static_cast<int>(strntoi64(pMinute, p - pMinute));
if (dir.date.minute < 0 || dir.date.minute > 59)
return FALSE;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
}
else
{
dir.date.hastime = FALSE;
}
while (str)
{
if (tokenlen > 2 && str[0] == '(' && str[tokenlen - 1] == ')')
{
if (dir.permissionstr != L"")
dir.permissionstr += L" ";
CString tmp;
copyStr(tmp, 0, str + 1, tokenlen - 2);
dir.permissionstr += tmp;
}
else if (tokenlen > 2 && str[0] == '[' && str[tokenlen - 1] == ']')
{
if (dir.ownergroup != L"")
dir.ownergroup += L" ";
CString tmp;
copyStr(tmp, 0, str + 1, tokenlen - 2);
dir.ownergroup += tmp;
}
else
{
if (dir.permissionstr != L"")
dir.permissionstr += L" ";
CString tmp;
copyStr(tmp, 0, str, tokenlen);
dir.permissionstr += tmp;
}
str = GetNextToken(line, linelen, tokenlen, pos, 0);
}
direntry = dir;
return TRUE;
}
BOOL CFtpListResult::parseAsEPLF(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
t_directory::t_direntry dir;
const char *str = strstr(line, "\t");
//Check if directory listing is an EPLF one
if (*line=='+' && str)
{
str++;
if (!*str)
return FALSE;
dir.bLink = FALSE;
dir.bUnsure = FALSE;
dir.date.hasdate = dir.date.hastime = FALSE;
dir.dir = FALSE;
dir.size = -2;
dir.name = str;
const char *fact = line + 1;
const char *nextfact = fact;
nextfact = strstr(nextfact, ",");
//if (nextfact && nextfact < str)
// *nextfact=0;
while (fact<=(str-2))
{
int len;
if (!nextfact)
len = str - fact - 1;
else
len = nextfact - fact;
if (len == 1 && fact[0] == '/')
dir.dir = TRUE;
else if (*fact=='s')
dir.size = strntoi64(fact+1, len-1);
else if (*fact=='m')
{
time_t rawtime = (time_t)strntoi64(fact+1, len-1);
TimeTToDate(rawtime, dir.date);
}
else if (len == 5 && *fact=='u' && *(fact+1)=='p')
{
char buffer[4] = {0};
memcpy(buffer, fact+2, len-2);
direntry.permissionstr = buffer;
}
if (!nextfact || nextfact>=(str-2))
break;
fact = nextfact+1;
nextfact = strstr(nextfact+1, ",");
// if (nextfact && nextfact<str)
// *nextfact=0;
}
direntry = dir;
return TRUE;
}
return FALSE;
}
BOOL CFtpListResult::parseAsMlsd(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
// MLSD format as described here: https://datatracker.ietf.org/doc/html/rfc3659
// Parsing is done strict, abort on slightest error.
// If we ever add some detection that entry is symlink,
// make sure to add support for resolving the symlink
// using MLST to TFTPFileSystem::ReadSymlink
int pos = 0;
int tokenlen = 0;
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
CString facts(str, tokenlen);
if (facts.IsEmpty())
{
return FALSE;
}
direntry.name = L"";
direntry.bUnsure = FALSE;
direntry.dir = FALSE;
direntry.bLink = FALSE;
direntry.ownergroup = L"";
direntry.owner = L"";
direntry.group = L"";
direntry.permissionstr = L"";
CString owner, group, uid, gid, ownername, groupname;
while (!facts.IsEmpty())
{
int delim = facts.Find(';');
if (delim < 3)
{
if (delim != -1)
{
return 0;
}
else
delim = facts.GetLength();
}
int pos = facts.Find('=');
if (pos < 1 || pos > delim)
{
return FALSE;
}
CString factname = facts.Left(pos);
factname.MakeLower();
CString value = facts.Mid(pos + 1, delim - pos - 1);
// When adding new facts, update filter in CFtpControlSocket::LogOnToServer
// (CONNECT_FEAT state)
if (factname == L"type")
{
if (!value.CompareNoCase(L"dir"))
{
direntry.dir = TRUE;
}
// This is syntax used by proftpd by default
// http://www.proftpd.org/docs/modules/mod_facts.html
// They claim it's the correct one.
// See also
// https://www.rfc-editor.org/errata_search.php?rfc=3659&eid=1500
else if (!value.Left(15).CompareNoCase(L"OS.unix=symlink"))
{
direntry.dir = TRUE;
direntry.bLink = TRUE;
// actually symlink target should not be included in this syntax,
// but just in case some servers do.
if ((value.GetLength() > 16) && (value[15] == ':'))
direntry.linkTarget = value.Mid(16);
}
// This is syntax shown in RFC 3659 section 7.7.4 "A More Complex Example"
// Type=OS.unix=slink:/foobar;Perm=;Unique=keVO1+4G4; foobar
// https://datatracker.ietf.org/doc/html/rfc3659
else if (!value.Left(13).CompareNoCase(L"OS.unix=slink"))
{
direntry.dir = TRUE;
direntry.bLink = TRUE;
if ((value.GetLength() > 14) && (value[13] == ':'))
direntry.linkTarget = value.Mid(14);
}
// For MLSD, these will be skipped in AddData.
else if (!value.CompareNoCase(L"cdir"))
{
// ProFTPD up to 1.3.6rc1 and 1.3.5a incorrectly uses "cdir" for the current working directory.
// So at least in MLST, where this would be the only entry, we treat it like "dir".
if (!m_mlst)
{
direntry.name = L".";
}
direntry.dir = TRUE;
}
else if (!value.CompareNoCase(L"pdir"))
{
direntry.name = L"..";
direntry.dir = TRUE;
}
}
else if (factname == L"size")
{
direntry.size = 0;
for (unsigned int i = 0; i < value.GetLength(); ++i)
{
if (value[i] < '0' || value[i] > '9')
{
return FALSE;
}
direntry.size *= 10;
direntry.size += value[i] - '0';
}
}
else if (factname == L"modify" ||
(!direntry.date.hasdate && factname == L"create"))
{
if (!parseMlsdDateTime(value, direntry.date))
{
return FALSE;
}
}
else if (factname == L"perm")
{
// there's no way we can convert Perm fact to unix-style permissions,
// so we at least present Perm as-is to a user
direntry.humanpermstr = value;
}
else if (factname == L"unix.mode")
{
direntry.permissionstr = value;
}
else if (factname == L"unix.owner" || factname == L"unix.user")
{
owner = value;
}
else if (factname == L"unix.group")
{
group = value;
}
else if (factname == L"unix.uid")
{
uid = value;
}
else if (factname == L"unix.gid")
{
gid = value;
}
else if (factname == L"unix.ownername")
{
ownername = value;
}
else if (factname == L"unix.groupname")
{
groupname = value;
}
facts = facts.Mid(delim + 1);
}
// The order of the facts is undefined
if (!ownername.IsEmpty())
direntry.owner = ownername;
else if (!owner.IsEmpty())
direntry.owner = owner;
else if (!uid.IsEmpty())
direntry.owner = uid;
if (!groupname.IsEmpty())
direntry.group = groupname;
else if (!group.IsEmpty())
direntry.group = group;
else if (!gid.IsEmpty())
direntry.group = gid;
if (line[pos] != L' ')
{
return FALSE;
}
if (direntry.name.IsEmpty())
{
pos++;
CString fileName;
copyStr(fileName, 0, line + pos, linelen - pos, true);
if (m_mlst)
{
// do not try to detect path type, assume a standard *nix syntax + do not trim
CServerPath path(fileName, FZ_SERVERTYPE_FTP, false);
direntry.name = path.GetLastSegment();
if (direntry.name.IsEmpty())
{
direntry.name = fileName;
}
}
else
{
direntry.name = fileName;
}
}
return TRUE;
}
bool CFtpListResult::parseMlsdDateTime(const CString value, t_directory::t_direntry::t_date &date) const
{
if (value.IsEmpty())
{
return FALSE;
}
bool result = FALSE;
int Year, Month, Day, Hours, Minutes, Seconds;
Year=Month=Day=Hours=Minutes=Seconds=0;
// Time can include a fraction after a dot, this will ignore the fraction part.
if (swscanf((LPCWSTR)value, L"%4d%2d%2d%2d%2d%2d", &Year, &Month, &Day, &Hours, &Minutes, &Seconds) == 6)
{
date.hasdate = TRUE;
date.hastime = TRUE;
date.hasseconds = TRUE;
result = TRUE;
}
else if (swscanf((LPCWSTR)value, L"%4d%2d%2d", &Year, &Month, &Day) == 3)
{
date.hasdate = TRUE;
date.hastime = FALSE;
result = TRUE;
}
if (result)
{
date.year = Year;
date.hasyear = TRUE;
date.month = Month;
date.day = Day;
date.hour = Hours;
date.minute = Minutes;
date.second = Seconds;
date.utc = TRUE;
}
return result;
}
void CFtpListResult::GuessYearIfUnknown(t_directory::t_direntry::t_date & Date)
{
// Problem: Some servers use times only for files newer than 6 months,
// others use one year as limit. IIS shows time for files from the current year (jan-dec).
// So there is no support for files with time
// dated in the near future. Under normal conditions there should not be such files.
if (!Date.year) // might use direntry.date.hasyear now?
{
CTime curtime = CTime::GetCurrentTime();
int curday = curtime.GetDay();
int curmonth = curtime.GetMonth();
int curyear = curtime.GetYear();
int now = curmonth * 31 + curday;
int file = Date.month * 31 + Date.day;
if ((now + 1) >= file)
{
Date.year = curyear;
}
else
{
Date.year = curyear - 1;
}
// year is guessed, not setting hasyear
}
}
BOOL CFtpListResult::parseAsUnix(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
USES_CONVERSION;
int pos = 0;
int tokenlen = 0;
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
//Check the first token
if (str[0] != 'b' &&
str[0] != 'c' &&
str[0] != 'd' &&
str[0] != 'l' &&
str[0] != 'p' &&
str[0] != 's' &&
str[0] != '-')
{
return FALSE;
}
//First check if it is a netware server
bool bNetWare = false;
copyStr(direntry.permissionstr, 0, str, tokenlen);
if (tokenlen == 1)
{
//Yes, it's most likely a netware server
//Now get the full permission string
bNetWare = true;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
direntry.permissionstr += L" ";
copyStr(direntry.permissionstr, direntry.permissionstr.GetLength(), str, tokenlen);
}
//Set directory and link flags
//Always assume links point directories
//GUI frontend should try to figure out
//to where the link really points
if (direntry.permissionstr[0]==L'd' || direntry.permissionstr[0]==L'l')
direntry.dir = true;
else
direntry.dir = false;
if (direntry.permissionstr[0]==L'l')
direntry.bLink = true;
else
direntry.bLink = false;
bool bNetPresenz = false;
if (!bNetWare) //On non-netware servers, expect at least two unused tokens
{
bool groupid = false;
// Unused param
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
if (tokenlen == 6 && !strncmp(str, "folder", tokenlen) && direntry.dir)
bNetPresenz = true; //Assume NetPresenz server
//However, it's possible that we mark a non-NetPresenz
//server if the fileowner is "folder"
else if (!IsNumeric(str, tokenlen))
{
// Check for Connect:Enterprise server
if (direntry.permissionstr.GetLength() > 3 &&
(direntry.permissionstr.Right(3) == L"FTP" ||
direntry.permissionstr.Right(3) == L"FTS" ||
direntry.permissionstr.Right(3) == L"TCP" ||
direntry.permissionstr.Right(3) == L"SSH"))
groupid = TRUE;
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, tokenlen);
}
else
groupid = TRUE;
if (!bNetPresenz && groupid)
{
//Unused param
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
if (direntry.ownergroup != L"")
direntry.ownergroup += L" ";
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, tokenlen);
}
}
//Skip param, may be used for size
int skippedlen = 0;
const char *skipped = GetNextToken(line, linelen,skippedlen, pos, 0);
if (!skipped)
{
return FALSE;
}
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
// Keep parsing the information until we get a numerical string,
// because some broken servers may send 3 tags: domain/network, group and user
const char *prevstr = 0;
int prevstrlen = 0;
__int64 tmp = 0;
std::map<CString, int>::const_iterator iter;
while (str && !ParseSize(str, tokenlen, tmp) && !IsNumeric(skipped, skippedlen))
{
//Maybe the server has left no space between the group and the size
//because of stupid alignment
char *tmpstr = new char[tokenlen + 1];
strncpy(tmpstr, str, tokenlen);
tmpstr[tokenlen] = 0;
strlwr(tmpstr);
iter = m_MonthNamesMap.find(A2T(tmpstr));
delete [] tmpstr;
if (iter != m_MonthNamesMap.end())
{
BOOL bRightNumeric = true;
if (skipped[skippedlen-1]<'0' || skipped[skippedlen-1]>'9')
bRightNumeric = false;
if (bRightNumeric)
break;
bRightNumeric = true;
if (prevstr && prevstrlen)
{
if (prevstr[prevstrlen-1]<'0' || prevstr[prevstrlen-1]>'9')
bRightNumeric = false;
if (bRightNumeric)
{
if (direntry.ownergroup != L"")
direntry.ownergroup += L" ";
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, tokenlen);
skipped = prevstr;
skippedlen = prevstrlen;
break;
}
}
}
if (prevstr)
{
if (direntry.ownergroup != L"")
direntry.ownergroup += L" ";
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), prevstr, prevstrlen);
}
prevstr = str;
prevstrlen = tokenlen;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
}
if (!str)
{
return FALSE;
}
const char *size = str;
int sizelen = tokenlen;
if (!ParseSize(str, tokenlen, direntry.size))
{
//Maybe we've skipped too much tokens
if (!ParseSize(skipped, skippedlen, direntry.size))
{
//Maybe the server has left no space between the group and the size
//because of stupid alignment
bool bRightNumeric = true;
const char *pos;
for (pos=(str+tokenlen-1); pos > str; pos--)
{
if (*pos<'0' || *pos>'9')
{
if (pos==(str+tokenlen-1))
bRightNumeric=false;
break;
}
}
if (bRightNumeric && pos>str)
{
size = pos + 1;
sizelen = pos - str;
direntry.ownergroup += L" ";
if (direntry.ownergroup != L"")
direntry.ownergroup += L" ";
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), str, pos-str);
}
else
{
for (pos=(skipped+skippedlen-1); pos > skipped; pos--)
{
if (*pos<'0' || *pos>'9')
{
if (pos==(skipped+skippedlen-1))
{
return false;
}
break;
}
}
size = pos + 1;
sizelen = skippedlen + skipped - size;
if (direntry.ownergroup != L"")
direntry.ownergroup += L" ";
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), skipped, skippedlen - sizelen);
}
direntry.size = strntoi64(size, sizelen);
}
else
{
//We should've not skipped the last token
//This also fixes the problem with the NetPresenz detection
if (bNetPresenz && direntry.dir && direntry.ownergroup != L"")
{
direntry.ownergroup = L"folder " + direntry.ownergroup;
}
}
}
else
{
if (direntry.ownergroup != L"")
direntry.ownergroup += L" ";
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), skipped, skippedlen);
if (prevstr)
{
direntry.ownergroup += L" ";
copyStr(direntry.ownergroup, direntry.ownergroup.GetLength(), prevstr, prevstrlen);
}
str = 0;
}
//Month
if (!str)
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
const char *smonth = str;
int smonthlen = tokenlen;
direntry.date.year = 0;
//Day
const char *sday = 0;
int sdaylen = 0;
// Some VShell server send both the year and the time, try to detect them
BOOL bCouldBeVShell = FALSE;
//Some servers use the following date formats:
// 26-09 2002, 2002-10-14, 01-jun-99
const char *p = strnchr(smonth, smonthlen, '-');
if (p)
{
int plen = smonthlen - (p - smonth);
const char *pos2 = strnchr(p+1, plen - 1, '-');
if (!pos2) //26-09 2002
{
sday = p + 1;
sdaylen = plen - 1;
smonthlen = p-smonth;
}
else if (p-smonth == 4) //2002-10-14
{
direntry.date.year = static_cast<int>(strntoi64(smonth, p-smonth));
direntry.date.hasyear = TRUE;
sday = pos2 + 1;
sdaylen = smonthlen - (pos2 - smonth) - 1;
smonthlen = pos2-smonth - (p-smonth) - 1;
smonth = p + 1;
/* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd
* Unfortunately we have to guess which one is the right if
* the month is < 12
*/
if (strntoi64(smonth, smonthlen) > 12)
{
const char *tmp = smonth;
smonth = sday;
sday = tmp;
int tmplen = smonthlen;
smonthlen = sdaylen;
sdaylen = tmplen;
}
}
else if (p-smonth) //14-10-2002 or 01-jun-99
{
direntry.date.year = static_cast<int>(strntoi64(pos2+1, tokenlen - (pos2-smonth) - 1));
direntry.date.hasyear = TRUE;
sday = smonth;
sdaylen = p - smonth;
smonthlen = pos2-smonth - (p-smonth) - 1;
smonth = p + 1;
/* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd
* Unfortunately we have to guess which one is the right if
* the month is < 12
*/
if (strntoi64(smonth, smonthlen) > 12)
{
const char *tmp = smonth;
smonth = sday;
sday = tmp;
int tmplen = smonthlen;
smonthlen = sdaylen;
sdaylen = tmplen;
}
}
else
{
return FALSE;
}
}
/* Some servers use the following date formats:
* yyyy/dd/mm, yyyy/mm/dd, dd/mm/yyyy, mm/dd/yyyy
* try to detect them.
*/
else if (strnchr(smonth, smonthlen, '/'))
{
const char *p = strnchr(smonth, smonthlen, '/');
int plen = smonthlen - (p - smonth);
const char *pos2 = strnchr(p+1, plen - 1, '/');
if (!pos2) //Assume 26/09 2002
{
sday = p + 1;
sdaylen = plen - 1;
smonthlen = p-smonth;
}
else if (p-smonth==4)
{
direntry.date.year = static_cast<int>(strntoi64(smonth, p-smonth));
direntry.date.hasyear = TRUE;
sday = pos2 + 1;
sdaylen = smonthlen - (pos2 - smonth) - 1;
smonthlen = pos2-smonth - (p-smonth) - 1;
smonth = p + 1;
/* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd
* Unfortunately we have to guess which one is the right if
* the month is < 12
*/
if (strntoi64(smonth, smonthlen) > 12)
{
const char *tmp = smonth;
smonth = sday;
sday = tmp;
int tmplen = smonthlen;
smonthlen = sdaylen;
sdaylen = tmplen;
}
}
else if (p-smonth==2)
{
direntry.date.year = static_cast<int>(strntoi64(pos2+1, tokenlen - (pos2-smonth) - 1));
direntry.date.hasyear = TRUE;
sday = smonth;
sdaylen = p - smonth;
smonthlen = pos2-smonth - (p-smonth) - 1;
smonth = p + 1;
/* Try to detect difference between yyyy/dd/mm and yyyy/mm/dd
* Unfortunately we have to guess which one is the right if
* the month is < 12
*/
if (strntoi64(smonth, smonthlen) > 12)
{
const char *tmp = smonth;
smonth = sday;
sday = tmp;
int tmplen = smonthlen;
smonthlen = sdaylen;
sdaylen = tmplen;
}
}
else
{
return FALSE;
}
}
else
{
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
sday = str;
sdaylen = tokenlen;
if (sdaylen && sday[sdaylen-1] == ',')
{
sdaylen--;
bCouldBeVShell = TRUE;
}
//Trim trailing characters
while (sdaylen && (sday[sdaylen-1]=='.' || sday[sdaylen-1]==','))
{
bCouldBeVShell = FALSE;
sdaylen--;
}
int i;
for (i = 0; i < sdaylen; i++)
if (sday[i] < '0' || sday[i] > '9')
break;
if (i && i < sdaylen)
{
if ((unsigned char)sday[i] > 127)
sdaylen = i;
}
if (!sdaylen)
{
return FALSE;
}
}
if (!strntoi64(sday, sdaylen)) //Day field invalid
{ //Maybe the server is sending a directory listing with localized date format.
//Try to fix this really bad behavior
bCouldBeVShell = FALSE;
const char *tmp = smonth;
smonth = sday;
sday = tmp;
int tmplen = smonthlen;
smonthlen = sdaylen;
sdaylen = tmplen;
}
//Time/Year
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
//Trim trailing characters
while (smonthlen && (smonth[smonthlen-1]=='.' || smonth[smonthlen-1]==','))
smonthlen--;
if (!smonthlen)
{
return FALSE;
}
char *lwr = new char[smonthlen + 1];
memcpy(lwr, smonth, smonthlen);
lwr[smonthlen] = 0;
_strlwr(lwr);
bool gotYear = false;
int year = (int)strntoi64(smonth, smonthlen);
if (year > 1000)
{
gotYear = true;
direntry.date.year = year;
direntry.date.hasyear = TRUE;
}
else
{
//Try if we can recognize the month name
iter = m_MonthNamesMap.find(A2T(lwr));
delete [] lwr;
if (iter == m_MonthNamesMap.end())
{
int i;
for (i = 0; i < smonthlen; i++)
if (smonth[i] < '0' || smonth[i] > '9')
break;
if (!i || i == smonthlen)
{
return false;
}
if ((unsigned char)smonth[i] < 128)
{
return false;
}
smonthlen = i;
direntry.date.month = (int)strntoi64(smonth, smonthlen);
if (!direntry.date.month || direntry.date.month > 12)
{
return false;
}
}
else
direntry.date.month = iter->second;
}
if (!gotYear)
{
direntry.date.day = static_cast<int>(strntoi64(sday, sdaylen));
if (direntry.date.day < 1 || direntry.date.day > 31)
{
return false;
}
}
else
{
direntry.date.month = static_cast<int>(strntoi64(sday, sdaylen));
if (direntry.date.month < 1 || direntry.date.month > 12)
{
return false;
}
}
const char *stimeyear = str;
int stimeyearlen = tokenlen;
//Parse the time/year token
const char *strpos = strnchr(stimeyear, stimeyearlen, ':');
if (!strpos)
strpos = strnchr(stimeyear, stimeyearlen, '.');
if (!strpos)
strpos = strnchr(stimeyear, stimeyearlen, '-');
if (strpos)
{
//stimeyear has delimiter, so it's a time
direntry.date.hour = static_cast<int>(strntoi64(stimeyear, strpos - stimeyear));
int stimeyearrem = stimeyearlen - (strpos - stimeyear) - 1;
const char *strpos2 = strnchr(strpos + 1, stimeyearrem, ':');
if (strpos2 == NULL)
{
direntry.date.minute = static_cast<int>(strntoi64(strpos + 1, stimeyearrem));
}
else
{
direntry.date.minute = static_cast<int>(strntoi64(strpos + 1, strpos2 - strpos - 1));
direntry.date.second = static_cast<int>(strntoi64(strpos2 + 1, stimeyearlen - (strpos2 - stimeyear) - 1));
direntry.date.hasseconds = TRUE;
}
direntry.date.hastime = TRUE;
GuessYearIfUnknown(direntry.date);
bCouldBeVShell = FALSE;
}
else
{
if (gotYear)
{
direntry.date.day = static_cast<int>(strntoi64(stimeyear, stimeyearlen));
if (direntry.date.day < 1 || direntry.date.day > 31)
{
return false;
}
}
else if (!direntry.date.year) // might use direntry.date.hasyear now?
{
//No delimiters -> year
direntry.date.hastime = FALSE;
direntry.date.year = static_cast<int>(strntoi64(stimeyear, stimeyearlen));
direntry.date.hasyear = TRUE;
}
else
{
// File has no time token and short date format
pos -= stimeyearlen;
}
}
if (!direntry.date.year) //Year 0? Really ancient file, this is invalid! might use direntry.date.hasyear now?
{
return FALSE;
}
// Check if server could still be one of the newer VShell servers
if (bCouldBeVShell)
{
int oldpos = pos;
//Get time
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
{
return FALSE;
}
const char *p = strnchr(str, tokenlen, ':');
if (pos && (p - str) == 2 && IsNumeric(str, 2) && IsNumeric(str + 3, 2))
{
//stimeyear has delimiter, so it's a time
int hour = static_cast<int>(strntoi64(str, 2));
int minute = static_cast<int>(strntoi64(str + 3, 2));
if (hour >= 0 && hour < 24 && minute >= 0 && minute < 60)
{
direntry.date.hour = hour;
direntry.date.minute = minute;
direntry.date.hastime = TRUE;
}
else
pos = oldpos;
}
else
pos = oldpos;
}
//Get filename
str = GetNextToken(line, linelen, tokenlen, pos, 1);
if (!str)
{
return FALSE;
}
//Trim link data from filename
if (direntry.bLink)
{
const char *pos = strnstr(str, tokenlen, " -> ");
if (pos)
{
copyStr(direntry.linkTarget, 0, pos + 4, tokenlen - (pos - str) - 4);
tokenlen = pos - str;
}
if (!tokenlen)
{
return FALSE;
}
}
//Trim indicators, some server add those to mark special files
if (str[tokenlen - 1] == '*' ||
str[tokenlen - 1] == '/' ||
// str[tokenlen - 1] == '=' || //Don't trim this char, it would cause problems on certain servers
//This char just marks sockets, so it will never appear as indicator
//However it is valid as character for filenames on some systems
str[tokenlen - 1] == '|')
tokenlen--;
copyStr(direntry.name, 0, str, tokenlen, true);
direntry.bUnsure = FALSE;
direntry.date.hasdate = TRUE;
return TRUE;
}
BOOL CFtpListResult::parseAsDos(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
int pos = 0;
int tokenlen = 0;
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
//Check the first token
if (str[0]=='b' ||
str[0]=='c' ||
str[0]=='d' ||
str[0]=='l' ||
str[0]=='p' ||
str[0]=='s' ||
str[0]=='-')
return FALSE;
if (IsNumeric(str, tokenlen))
return FALSE;
//It's a NT server with MSDOS directory format
if (!ParseShortDate(str, tokenlen, direntry.date))
return FALSE;
//Extract time
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!parseTime(str, tokenlen, direntry.date))
return FALSE;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (tokenlen == 5 && !memcmp(str, "<DIR>", 5))
{
direntry.dir = TRUE;
}
else
{
char * buffer = new char[tokenlen];
int i, j;
for (i = 0, j = 0; i < tokenlen; i++)
{
if (str[i] != ',')
buffer[j++] = str[i];
}
direntry.dir = FALSE;
direntry.size = strntoi64(buffer, j);
delete [] buffer;
}
str = GetNextToken(line, linelen, tokenlen, pos, 1);
if (!str)
return FALSE;
copyStr(direntry.name, 0, str, tokenlen, true);
direntry.bUnsure = FALSE;
return TRUE;
}
void CFtpListResult::TimeTToDate(time_t TimeT, t_directory::t_direntry::t_date & Date) const
{
tm * sTime = gmtime(&TimeT);
Date.year = sTime->tm_year + 1900;
Date.hasyear = TRUE;
Date.month = sTime->tm_mon+1;
Date.day = sTime->tm_mday;
Date.hour = sTime->tm_hour;
Date.minute = sTime->tm_min;
Date.hasdate = Date.hastime = TRUE;
}
BOOL CFtpListResult::parseAsOther(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
int pos = 0;
int tokenlen = 0;
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
//Check the first token
if (str[0]=='b' ||
str[0]=='c' ||
str[0]=='d' ||
str[0]=='l' ||
str[0]=='p' ||
str[0]=='s' ||
str[0]=='-')
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
//Could be numerical Unix style format or VShell format
//or even an OS2 server
const char *skipped = str;
int skippedtokenlen = tokenlen;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
//If next token is numerical, than it's the numerical Unix style format,
//else it's the VShell or OS/2 format
if (IsNumeric(str, tokenlen))
{
copyStr(direntry.permissionstr, 0, skipped, skippedtokenlen);
if (skippedtokenlen >= 2 && skipped[1] == '4')
direntry.dir = TRUE;
else
direntry.dir = FALSE;
//Unused token
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
//Size
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
direntry.size = strntoi64(str, tokenlen);
//Date/Time
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
time_t secsSince1970 = static_cast<long>(strntoi64(str, tokenlen));
TimeTToDate(secsSince1970, direntry.date);
str = GetNextToken(line, linelen, tokenlen, pos, 1);
if (!str)
return FALSE;
copyStr(direntry.name, 0, str, tokenlen, true);
}
else
{
std::map<CString, int>::const_iterator iter;
//Get size
direntry.size = strntoi64(skipped, skippedtokenlen);
//Get date, month first
if (tokenlen >= 15)
return FALSE;
char buffer[15] = {0};
memcpy(buffer, str, tokenlen);
strlwr(buffer);
USES_CONVERSION;
iter = m_MonthNamesMap.find(A2T(buffer));
if (iter == m_MonthNamesMap.end())
{
direntry.dir = FALSE;
while (str)
{
//Could be an OS/2 server
if (tokenlen == 3 && !memcmp(str, "dir", 3))
direntry.dir = TRUE;
else if (tokenlen == 3 && !memcmp(str, "DIR", 3))
direntry.dir = TRUE;
else if (strnchr(str, tokenlen, '-'))
break;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
}
if (!str)
return FALSE;
if (!ParseShortDate(str, tokenlen, direntry.date))
return FALSE;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
//Parse the time token
const char *strpos = strnchr(str, tokenlen, ':');
if (!strpos)
return FALSE;
if (strpos)
{
//stimeyear has delimiter, so it's a time
direntry.date.hour = static_cast<int>(strntoi64(str, strpos - str));
direntry.date.minute = static_cast<int>(strntoi64(strpos+1, tokenlen - (strpos - str) - 1));
direntry.date.hastime = TRUE;
GuessYearIfUnknown(direntry.date);
}
str = GetNextToken(line, linelen, tokenlen, pos, 1);
if (!str)
return FALSE;
if (tokenlen > 6)
{
if (!strnicmp(str + tokenlen - 5, "<DIR>", 5))
{
direntry.dir = TRUE;
tokenlen -= 5;
while (tokenlen && (str[tokenlen - 1] == ' ' || str[tokenlen - 1] == '\t'))
tokenlen--;
}
if (!tokenlen)
return FALSE;
}
copyStr(direntry.name, 0, str, tokenlen, true);
}
else
{
direntry.date.month = iter->second;
//Day
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (str[tokenlen-1]==',')
tokenlen--;
if (!tokenlen)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
direntry.date.day = static_cast<int>(strntoi64(str, tokenlen));
if (direntry.date.day < 1 || direntry.date.day > 31)
return FALSE;
//Year
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
direntry.date.year = static_cast<int>(strntoi64(str, tokenlen));
direntry.date.hasyear = TRUE;
if (direntry.date.year < 50)
direntry.date.year += 2000;
else if (direntry.date.year < 1000)
direntry.date.year += 1900;
direntry.date.hasdate = TRUE;
//Now get the time
str = GetNextToken(line, linelen, tokenlen, pos, 0);
const char *p = strnchr(str, tokenlen, ':');
if (!p)
return FALSE;
if (p==str || !IsNumeric(str, p-str) || (p-str + 1) >= tokenlen || !IsNumeric(p+1, tokenlen - (p-str) - 1))
return FALSE;
direntry.date.hour = static_cast<int>(strntoi64(str, p-str));
direntry.date.minute = static_cast<int>(strntoi64(p+1, tokenlen - (p-str) - 1));
if (direntry.date.hour < 0 || direntry.date.hour > 24)
return FALSE;
if (direntry.date.minute < 0 || direntry.date.minute > 59)
return FALSE;
direntry.date.hastime = TRUE;
str = GetNextToken(line, linelen, tokenlen, pos, 1);
if (!str)
return FALSE;
if (tokenlen > 1 && (str[tokenlen-1] == '\\' || str[tokenlen-1] == '/'))
{
direntry.dir = TRUE;
tokenlen--;
}
else
direntry.dir = FALSE;
if (!tokenlen)
return FALSE;
copyStr(direntry.name, 0, str, tokenlen, true);
}
}
direntry.bUnsure = FALSE;
direntry.date.hasdate = TRUE;
return TRUE;
}
_int64 CFtpListResult::strntoi64(const char *str, int len) const
{
_int64 res = 0;
const char *p = str;
while ((p-str) < len)
{
if (*p < '0' || *p > '9')
break;
res *= 10;
res += *p++ - '0';
}
return res;
}
const char *CFtpListResult::GetNextToken(const char *line, const int linelen, int &len, int &pos, int type) const
{
const char *p = line + pos;
if ((p - line) >= linelen)
return NULL;
while ((p - line) < linelen && (!p || *p==' ' || *p=='\t'))
p++;
if ((p - line) >= linelen)
return NULL;
const char *res = p;
if (type)
{
pos = linelen;
len = linelen - (p - line);
}
else
{
while ((p - line) < linelen && *p && *p != ' ' && *p!='\t')
p++;
len = p - res;
pos = p - line;
}
return res;
}
const char * CFtpListResult::strnchr(const char *str, int len, char c) const
{
if (!str)
return NULL;
const char *p = str;
while (len > 0)
{
if (!*p)
return NULL;
if (*p == c)
return p;
p++;
len--;
}
return NULL;
}
const char * CFtpListResult::strnstr(const char *str, int len, const char *c) const
{
if (!str)
return NULL;
if (!c)
return NULL;
int clen = strlen(c);
const char *p = str;
while (len > 0)
{
if (!*p)
return NULL;
if (*p == *c)
{
if (clen == 1)
return p;
else if (len >= clen)
{
if (!memcmp(p + 1, c+1, clen-1))
return p;
}
else
return NULL;
}
p++;
len--;
}
return NULL;
}
void CFtpListResult::copyStr(CString &target, int pos, const char *source, int len, bool mayInvalidateUTF8 /*=false*/)
{
USES_CONVERSION;
char *p = new char[len + 1];
memcpy(p, source, len);
p[len] = '\0';
if (m_bUTF8 && *m_bUTF8)
{
// convert from UTF-8 to ANSI
if (DetectUTF8Encoding(RawByteString(p, len)) == etANSI)
{
if (mayInvalidateUTF8 && m_server.nUTF8 != 1)
{
LogMessage(FZ_LOG_WARNING, L"Server does not send proper UTF-8, falling back to local charset");
*m_bUTF8 = false;
}
target = target.Left(pos) + A2CT(p);
}
else
{
// convert from UTF-8 to ANSI
int len = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)p, -1, NULL, 0);
if (len != 0)
{
LPWSTR p1 = new WCHAR[len + 1];
MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)p, -1 , (LPWSTR)p1, len + 1);
target = target.Left(pos) + W2CT(p1);
delete [] p1;
}
else
target = target.Left(pos) + A2CT(p);
}
}
else
target = target.Left(pos) + A2CT(p);
delete [] p;
}
BOOL CFtpListResult::parseAsIBM(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
int pos = 0;
int tokenlen = 0;
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
copyStr(direntry.ownergroup , 0, str, tokenlen);
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
direntry.size = strntoi64(str, tokenlen);
//Date
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!ParseShortDate(str, tokenlen, direntry.date))
return FALSE;
//Time
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!parseTime(str, tokenlen, direntry.date))
return FALSE;
//Unused Token
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
//Name
str = GetNextToken(line, linelen, tokenlen, pos, 1);
if (!str)
return FALSE;
if (str[tokenlen-1] == '/')
{
direntry.dir = TRUE;
if (!--tokenlen)
return FALSE;
}
else
direntry.dir = FALSE;
copyStr(direntry.name, 0, str, tokenlen, true);
direntry.bUnsure = FALSE;
return true;
}
BOOL CFtpListResult::parseAsIBMMVS(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
int pos = 0;
int tokenlen = 0;
// volume
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (strncmp(str, "Migrated", tokenlen) != 0)
{
// unit
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (strncmp(str, "Tape", tokenlen) != 0)
{
//Referred Date
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!ParseShortDate(str, tokenlen, direntry.date))
{
// Perhaps of the following type:
// TSO004 3390 VSAM FOO.BAR
if (tokenlen != 4 || strncmp(str, "VSAM", tokenlen))
return FALSE;
str = GetNextToken(line, linelen, tokenlen, pos, 1);
if (!str)
return FALSE;
if (strnchr(str, tokenlen, ' '))
return FALSE;
copyStr(direntry.name, 0, str, tokenlen, true);
direntry.dir = false;
return true;
}
// ext
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
int prevLen = tokenlen;
// used
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (IsNumeric(str, tokenlen))
{
// recfm
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (IsNumeric(str, tokenlen))
return false;
}
else
{
if (prevLen < 6)
return false;
}
// lrecl
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
// blksize
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
// dsorg
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (tokenlen == 2 && !memcmp(str, "PO", 2))
{
direntry.dir = TRUE;
}
else
{
direntry.dir = FALSE;
direntry.size = 100;
}
}
}
// name of dataset or sequential name
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
copyStr(direntry.name, 0, str, tokenlen, true);
direntry.bUnsure = FALSE;
return true;
}
BOOL CFtpListResult::parseAsIBMMVSPDS(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
int pos = 0;
int tokenlen = 0;
// pds member name
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
copyStr(direntry.name, 0, str, tokenlen);
// vv.mm
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
// creation date
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
// change date
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!ParseShortDate(str, tokenlen, direntry.date))
return FALSE;
// change time
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!parseTime(str, tokenlen, direntry.date))
return FALSE;
// size
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
direntry.size = strntoi64(str, tokenlen);
// init
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
// mod
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
// id
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
direntry.dir = FALSE;
direntry.bUnsure = FALSE;
return true;
}
BOOL CFtpListResult::parseAsIBMMVSPDS2(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
// Use this one only on MVS servers, as it will cause problems on other servers
if (!(m_server.nServerType & (FZ_SERVERTYPE_SUB_FTP_MVS | FZ_SERVERTYPE_SUB_FTP_BS2000)))
return false;
int pos = 0;
int tokenlen = 0;
direntry.bUnsure = FALSE;
direntry.dir = FALSE;
direntry.bLink = FALSE;
direntry.ownergroup = L"";
direntry.permissionstr = L"";
direntry.date.hasdate = direntry.date.hastime = FALSE;
// pds member name
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (m_server.nServerType & FZ_SERVERTYPE_SUB_FTP_BS2000 &&
IsNumeric(str, tokenlen))
{
int prevlen = tokenlen;
const char* prev = str;
int oldpos = pos;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (str[0] != ':')
{
str = prev;
tokenlen = prevlen;
pos = oldpos;
}
}
copyStr(direntry.name, 0, str, tokenlen);
if (m_server.nServerType & FZ_SERVERTYPE_SUB_FTP_BS2000)
{
int pos = direntry.name.ReverseFind(L':');
if (pos != -1)
direntry.name = direntry.name.Mid(pos + 1);
if (direntry.name[0] == L'$')
{
int pos = direntry.name.Find(L'.');
if (pos != -1)
direntry.name = direntry.name.Mid(pos + 1);
}
if (direntry.name == L"")
return FALSE;
}
// Hexadecimal filesize
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
// We can return true since sometimes MVS servers send pure filenames
return true;
direntry.size = 0;
const char *end = str + tokenlen;
const char *p = str;
while (p < end)
{
direntry.size *= 16;
if (*p >= '0' && *p <= '9')
direntry.size += *p - '0';
else if (*p >= 'A' && *p <= 'F')
direntry.size += *p - 'A' + 10;
else
return false;
p++;
}
// Unused hexadecimal token
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return false;
end = str + tokenlen;
p = str;
while (p < end)
{
if ((*p < '0' || *p > '9') && (*p < 'A' || *p > 'F'))
return false;
p++;
}
// Unused token
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return false;
if (!IsNumeric(str, tokenlen))
return false;
const char* prevprev = 0;
const char* prev = 0;
int prevprevlen = 0;
int prevlen = 0;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return false;
while (str)
{
if (prevprev)
{
for (int i = 0; i < prevprevlen; i++)
if (prevprev[i] < 'A' || prevprev[i] > 'Z')
return false;
}
prevprev = prev;
prevprevlen = prevlen;
prev = str;
prevlen = tokenlen;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
}
if (!prev || !prevprev)
return false;
if (!IsNumeric(prev, prevlen) && (prevlen != 3 || memcmp(prev, "ANY", 3)))
return false;
if (!IsNumeric(prevprev, prevprevlen) && (prevprevlen != 3 || memcmp(prevprev, "ANY", 3)))
return false;
return true;
}
bool CFtpListResult::parseTime(const char *str, int len, t_directory::t_direntry::t_date &date) const
{
int i = 0;
//Extract the hour
date.hastime = TRUE;
date.hour = 0;
while (str[i] != ':')
{
if (str[i] < '0' || str[i] > '9')
return false;
date.hour *= 10;
date.hour += str[i] - '0';
if (date.hour > 24)
return false;
i++;
if (i == len)
return false;
}
// Make sure we did read at least one digit
if (!i)
return false;
i++;
if (i == len)
return false;
//Extract the minute
date.minute = 0;
while (str[i] >= '0' && str[i] <= '9')
{
date.minute *= 10;
date.minute += str[i] - '0';
if (date.minute > 59)
return false;
i++;
if (i == len)
break;
}
//Convert to 24h format
//I really wish we would have the following system:
//one year->ten months->ten days->ten hours->ten minutes->ten seconds and so on...
//I should modifiy the earth rotation to force everyone to use this system *g*
if (i != len)
{
if (str[i]=='P')
{
if (date.hour < 12)
date.hour += 12;
}
else
if (date.hour == 12)
date.hour = 0;
}
date.hastime = TRUE;
return true;
}
BOOL CFtpListResult::parseAsWfFtp(const char *line, const int linelen, t_directory::t_direntry &direntry)
{
int pos = 0;
int tokenlen = 0;
const char *str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
copyStr(direntry.name, 0, str, tokenlen);
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!IsNumeric(str, tokenlen))
return FALSE;
direntry.size = strntoi64(str, tokenlen);
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!ParseShortDate(str, tokenlen, direntry.date))
return FALSE;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (str[tokenlen - 1] != '.')
return FALSE;
str = GetNextToken(line, linelen, tokenlen, pos, 0);
if (!str)
return FALSE;
if (!parseTime(str, tokenlen, direntry.date))
return FALSE;
direntry.bUnsure = FALSE;
direntry.dir = false;
direntry.bLink = false;
direntry.permissionstr = L"";
direntry.ownergroup = L"";
return TRUE;
}
bool CFtpListResult::ParseSize(const char* str, int len, __int64 &size) const
{
if (len < 1)
return false;
if (IsNumeric(str, len))
{
size = strntoi64(str, len);
return true;
}
size = 0;
char last = str[--len];
int delimiter = -1;
for (int i = 0; i < len; i++)
{
char c = str[i];
if (c >= '0' && c <= '9')
{
size *= 10;
size += c - '0';
}
else if (c == '.')
delimiter = len - i;
else
return false;
}
// Check for digit before or after delimiterr
if (!delimiter || delimiter == len)
return false;
switch (last)
{
case 'k':
case 'K':
size *= 1 << 10;
break;
case 'm':
case 'M':
size *= 1 << 20;
break;
case 'g':
case 'G':
size *= 1 << 30;
break;
case 't':
case 'T':
size *= (__int64)1 << 40;
break;
default:
return false;
}
if (delimiter == -1)
return true;
while (delimiter--)
size /= 10;
return true;
}