You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kftpgrabber/kftpgrabber/src/engine/ftpdirectoryparser.cpp

1145 lines
27 KiB

/*
* This file is part of the KFTPGrabber project
*
* Copyright (C) 2003-2004 by the KFTPGrabber developers
* Copyright (C) 2003-2004 Jernej Kos <kostko@jweb-network.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
* NON-INFRINGEMENT. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*/
#include "ftpdirectoryparser.h"
#include "ftpsocket.h"
#include <ntqvaluevector.h>
#include <ntqstringlist.h>
#include <ntqdatetime.h>
#include <time.h>
#include <sys/stat.h>
namespace KFTPEngine {
class DToken {
public:
enum TokenTypeInfo {
Unknown,
Yes,
No
};
DToken()
: m_token(TQString::null),
m_valid(false)
{
}
DToken(const TQString &token, int start = 0)
: m_token(token),
m_length(token.length()),
m_start(start),
m_valid(true),
m_numeric(Unknown),
m_leftNumeric(Unknown),
m_rightNumeric(Unknown)
{
}
int getStart()
{
return m_start;
}
TQString getToken()
{
return m_token;
}
int getLength()
{
return m_length;
}
TQString getString(int type = 0)
{
switch (type) {
case 0: return m_token; break;
case 1: {
if (!isRightNumeric() || isNumeric())
return TQString::null;
int pos = m_length - 1;
while (m_token[pos] >= '0' && m_token[pos] <= '9')
pos--;
return m_token.mid(0, pos + 1);
break;
}
case 2: {
if (!isLeftNumeric() || isNumeric())
return TQString::null;
int len = 0;
while (m_token[len] >= '0' && m_token[len] <= '9')
len++;
return m_token.mid(0, len);
break;
}
}
return TQString::null;
}
int find(const char *chr, unsigned int start = 0) const
{
if (!chr)
return -1;
for (unsigned int i = start; i < m_length; i++) {
for (int c = 0; chr[c]; c++) {
if (m_token[i] == chr[c])
return i;
}
}
return -1;
}
unsigned long long getInteger()
{
return m_token.toULongLong();
}
unsigned long long getInteger(unsigned int start, int len)
{
return m_token.mid(start, len).toULongLong();
}
bool isValid()
{
return m_valid;
}
bool isNumeric()
{
if (m_numeric == Unknown) {
bool ok;
(void) m_token.toInt(&ok);
m_numeric = ok ? Yes : No;
}
return m_numeric == Yes;
}
bool isNumeric(unsigned int start, unsigned int len)
{
len = start + len < m_length ? start + len : m_length;
for (unsigned int i = start; i < len; i++) {
if (m_token[i] < '0' || m_token[i] > '9')
return false;
}
return true;
}
bool isLeftNumeric()
{
if (m_leftNumeric == Unknown) {
if (m_length < 2)
m_leftNumeric = No;
else if (m_token[0] < '0' || m_token[0] > '9')
m_leftNumeric = No;
else
m_leftNumeric = Yes;
}
return m_leftNumeric == Yes;
}
bool isRightNumeric()
{
if (m_rightNumeric == Unknown) {
if (m_length < 2)
m_rightNumeric = No;
else if (m_token[m_length - 1] < '0' || m_token[m_length - 1] > '9')
m_rightNumeric = No;
else
m_rightNumeric = Yes;
}
return m_rightNumeric == Yes;
}
TQChar operator[](unsigned int n) const
{
return m_token[n];
}
private:
TQString m_token;
unsigned int m_length;
int m_start;
bool m_valid;
TokenTypeInfo m_numeric;
TokenTypeInfo m_leftNumeric;
TokenTypeInfo m_rightNumeric;
};
class DLine {
public:
DLine(const TQString &line)
: m_line(line.stripWhiteSpace()),
m_parsePos(0)
{
}
bool getToken(unsigned int index, DToken &token, bool toEnd = false)
{
if (!toEnd) {
if (m_tokens.count() > index) {
token = m_tokens[index];
return true;
}
unsigned int start = m_parsePos;
while (m_parsePos < m_line.length()) {
if (m_line[m_parsePos] == ' ') {
m_tokens.append(DToken(m_line.mid(start, m_parsePos - start), start));
while (m_line[m_parsePos] == ' ' && m_parsePos < m_line.length())
m_parsePos++;
if (m_tokens.count() > index) {
token = m_tokens[index];
return true;
}
start = m_parsePos;
}
m_parsePos++;
}
if (m_parsePos != start) {
m_tokens.append(DToken(m_line.mid(start, m_parsePos - start), start));
}
if (m_tokens.count() > index) {
token = m_tokens[index];
return true;
}
return false;
} else {
if (m_endLineTokens.count() > index) {
token = m_endLineTokens[index];
return true;
}
if (m_tokens.count() <= index && !getToken(index, token))
return false;
for (unsigned int i = m_endLineTokens.count(); i <= index; i++) {
m_endLineTokens.append(DToken(m_line.mid(m_tokens[i].getStart())));
}
token = m_endLineTokens[index];
return true;
}
}
private:
TQStringList m_stringList;
TQValueVector<DToken> m_tokens;
TQValueVector<DToken> m_endLineTokens;
TQString m_line;
unsigned int m_parsePos;
};
FtpDirectoryParser::FtpDirectoryParser(FtpSocket *socket)
: m_socket(socket),
m_listing(DirectoryListing(socket->getCurrentDirectory()))
{
// Populate month names as they appear in the listing
m_monthNameMap["jan"] = 1;
m_monthNameMap["feb"] = 2;
m_monthNameMap["mar"] = 3;
m_monthNameMap["apr"] = 4;
m_monthNameMap["may"] = 5;
m_monthNameMap["jun"] = 6;
m_monthNameMap["june"] = 6;
m_monthNameMap["jul"] = 7;
m_monthNameMap["july"] = 7;
m_monthNameMap["aug"] = 8;
m_monthNameMap["sep"] = 9;
m_monthNameMap["sept"] = 9;
m_monthNameMap["oct"] = 10;
m_monthNameMap["nov"] = 11;
m_monthNameMap["dec"] = 12;
m_monthNameMap["1"] = 1;
m_monthNameMap["01"] = 1;
m_monthNameMap["2"] = 2;
m_monthNameMap["02"] = 2;
m_monthNameMap["3"] = 3;
m_monthNameMap["03"] = 3;
m_monthNameMap["4"] = 4;
m_monthNameMap["04"] = 4;
m_monthNameMap["5"] = 5;
m_monthNameMap["05"] = 5;
m_monthNameMap["6"] = 6;
m_monthNameMap["06"] = 6;
m_monthNameMap["7"] = 7;
m_monthNameMap["07"] = 7;
m_monthNameMap["8"] = 8;
m_monthNameMap["08"] = 8;
m_monthNameMap["9"] = 9;
m_monthNameMap["09"] = 9;
m_monthNameMap["10"] = 10;
m_monthNameMap["11"] = 11;
m_monthNameMap["12"] = 12;
}
void FtpDirectoryParser::addDataLine(const TQString &line)
{
TQString tmp(line);
tmp.append("\n");
addData(tmp.ascii(), tmp.length());
}
void FtpDirectoryParser::addData(const char *data, int len)
{
// Append new data to the buffer and check for any new lines
m_buffer.append(TQString::fromAscii(data, len));
int pos;
while ((pos = m_buffer.find('\n')) > -1) {
DirectoryEntry entry;
TQString line = m_buffer.mid(0, pos).stripWhiteSpace();
line = m_socket->remoteEncoding()->decode(TQCString(line.ascii()));
if (parseLine(line, entry) && !entry.filename().isEmpty()) {
if (entry.type() == '-')
entry.setType('f');
m_listing.addEntry(entry);
}
// Remove what we just parsed
m_buffer.remove(0, pos + 1);
}
}
bool FtpDirectoryParser::parseMlsd(const TQString &line, DirectoryEntry &entry)
{
TQStringList facts = TQStringList::split(';', line);
TQStringList::Iterator end = facts.end();
for (TQStringList::Iterator i = facts.begin(); i != end; ++i) {
if ((*i).contains('=')) {
TQString key = (*i).section('=', 0, 0).lower();
TQString value = (*i).section('=', 1, 1);
if (key == "type") {
if (value == "file")
entry.setType('f');
else if (value == "dir")
entry.setType('d');
} else if (key == "size") {
entry.setSize(value.toULongLong());
} else if (key == "modify") {
struct tm dt;
dt.tm_year = value.left(4).toInt() - 1900;
dt.tm_mon = value.mid(4, 2).toInt() - 1;
dt.tm_mday = value.mid(6, 2).toInt();
dt.tm_hour = value.mid(8, 2).toInt();
dt.tm_min = value.mid(10, 2).toInt();
dt.tm_sec = value.mid(12, 2).toInt();
entry.setTime(mktime(&dt));
} else if (key == "unix.mode") {
entry.setPermissions(value.toInt(0, 8));
} else if (key == "unix.uid") {
entry.setOwner(value);
} else if (key == "unix.gid") {
entry.setGroup(value);
}
} else {
entry.setFilename((*i).stripWhiteSpace());
}
}
return true;
}
void FtpDirectoryParser::parseUnixPermissions(const TQString &permissions, DirectoryEntry &entry)
{
int p = 0;
if (permissions[1] == 'r') p |= S_IRUSR;
if (permissions[2] == 'w') p |= S_IWUSR;
if (permissions[3] == 'x' || permissions[3] == 's') p |= S_IXUSR;
if (permissions[4] == 'r') p |= S_IRGRP;
if (permissions[5] == 'w') p |= S_IWGRP;
if (permissions[6] == 'x' || permissions[6] == 's') p |= S_IXGRP;
if (permissions[7] == 'r') p |= S_IROTH;
if (permissions[8] == 'w') p |= S_IWOTH;
if (permissions[9] == 'x' || permissions[9] == 't') p |= S_IXOTH;
if (permissions[3] == 's' || permissions[3] == 'S') p |= S_ISUID;
if (permissions[6] == 's' || permissions[6] == 'S') p |= S_ISGID;
if (permissions[9] == 't' || permissions[9] == 'T') p |= S_ISVTX;
entry.setPermissions(p);
}
bool FtpDirectoryParser::parseLine(const TQString &line, DirectoryEntry &entry)
{
DLine *tLine = new DLine(line);
bool done = false;
// Invalidate timestamp
entry.setTime(-1);
entry.timeStruct.tm_year = 0;
entry.timeStruct.tm_mon = 0;
entry.timeStruct.tm_hour = 0;
entry.timeStruct.tm_mday = 0;
entry.timeStruct.tm_min = 0;
entry.timeStruct.tm_sec = 0;
entry.timeStruct.tm_wday = 0;
entry.timeStruct.tm_yday = 0;
entry.timeStruct.tm_isdst = 0;
// Attempt machine friendly format first, when socket supports MLSD
if (m_socket->getConfigInt("feat.mlsd"))
done = parseMlsd(line, entry);
if (!done)
done = parseUnix(tLine, entry);
if (!done)
done = parseDos(tLine, entry);
if (!done)
done = parseVms(tLine, entry);
if (done) {
// Convert datetime to UNIX epoch
if (entry.time() == -1) {
// Correct format for mktime
entry.timeStruct.tm_year -= 1900;
entry.timeStruct.tm_mon -= 1;
entry.setTime(mktime(&entry.timeStruct));
}
// Add symlink if any
if (entry.filename().contains(" -> ") > 0) {
int pos = entry.filename().findRev(" -> ");
entry.setLink(entry.filename().mid(pos + 4));
entry.setFilename(entry.filename().mid(0, pos));
}
// Parse owner into group/owner
if (entry.owner().contains(" ") > 0) {
int pos = entry.owner().find(" ");
entry.setGroup(entry.owner().mid(pos + 1));
entry.setOwner(entry.owner().mid(0, pos));
}
// Remove unwanted names
if (entry.filename() == "." || entry.filename() == "..") {
entry.setFilename(TQString::null);
}
}
delete tLine;
return done;
}
bool FtpDirectoryParser::parseUnix(DLine *line, DirectoryEntry &entry)
{
int index = 0;
DToken token;
if (!line->getToken(index, token))
return false;
char chr = token[0];
if (chr != 'b' &&
chr != 'c' &&
chr != 'd' &&
chr != 'l' &&
chr != 'p' &&
chr != 's' &&
chr != '-')
return false;
TQString permissions = token.getString();
entry.setType(chr);
// Check for netware servers, which split the permissions into two parts
bool netware = false;
if (token.getLength() == 1) {
if (!line->getToken(++index, token))
return false;
permissions += " " + token.getString();
netware = true;
}
parseUnixPermissions(permissions, entry);
int numOwnerGroup = 3;
if (!netware) {
// Filter out groupid, we don't need it
if (!line->getToken(++index, token))
return false;
if (!token.isNumeric())
index--;
}
// Repeat until numOwnerGroup is 0 since not all servers send every possible field
int startindex = index;
do {
// Reset index
index = startindex;
entry.setOwner(TQString::null);
for (int i = 0; i < numOwnerGroup; i++) {
if (!line->getToken(++index, token))
return false;
if (i)
entry.setOwner(entry.owner() + " ");
entry.setOwner(entry.owner() + token.getString());
}
if (!line->getToken(++index, token))
return false;
// Check for concatenated groupname and size fields
filesize_t size;
if (!parseComplexFileSize(token, size)) {
if (!token.isRightNumeric())
continue;
entry.setSize(token.getInteger());
} else {
entry.setSize(size);
}
// Append missing group to ownerGroup
if (!token.isNumeric() && token.isRightNumeric()) {
if (!entry.owner().isEmpty())
entry.setOwner(entry.owner() + " ");
entry.setOwner(entry.owner() + token.getString(1));
}
if (!parseUnixDateTime(line, index, entry))
continue;
// Get the filename
if (!line->getToken(++index, token, true))
continue;
entry.setFilename(token.getString());
// Filter out cpecial chars at the end of the filenames
chr = token[token.getLength() - 1];
if (chr == '/' ||
chr == '|' ||
chr == '*')
entry.setFilename(entry.filename().mid(0, entry.filename().length() - 1));
return true;
} while (--numOwnerGroup);
return false;
}
bool FtpDirectoryParser::parseUnixDateTime(DLine *line, int &index, DirectoryEntry &entry)
{
DToken token;
// Get the month date field
TQString dateMonth;
if (!line->getToken(++index, token))
return false;
// Some servers use the following date formats:
// 26-05 2002, 2002-10-14, 01-jun-99
// slashes instead of dashes are also possible
int pos = token.find("-/");
if (pos != -1) {
int pos2 = token.find("-/", pos + 1);
if (pos2 == -1) {
// something like 26-05 2002
int day = token.getInteger(pos + 1, token.getLength() - pos - 1);
if (day < 1 || day > 31)
return false;
entry.timeStruct.tm_mday = day;
dateMonth = token.getString().left(pos);
} else if (!parseShortDate(token, entry)) {
return false;
}
} else {
dateMonth = token.getString();
}
bool bHasYearAndTime = false;
if (!entry.timeStruct.tm_mday) {
// Get day field
if (!line->getToken(++index, token))
return false;
int dateDay;
// Check for non-numeric day
if (!token.isNumeric() && !token.isLeftNumeric()) {
if (dateMonth.right(1) == ".")
dateMonth.remove(dateMonth.length() - 1, 1);
bool tmp;
dateDay = dateMonth.toInt(&tmp);
if (!tmp)
return false;
dateMonth = token.getString();
} else {
dateDay = token.getInteger();
if (token[token.getLength() - 1] == ',')
bHasYearAndTime = true;
}
if (dateDay < 1 || dateDay > 31)
return false;
entry.timeStruct.tm_mday = dateDay;
}
if (!entry.timeStruct.tm_mon) {
// Check month name
if (dateMonth.right(1) == "," || dateMonth.right(1) == ".")
dateMonth.remove(dateMonth.length() - 1, 1);
dateMonth = dateMonth.lower();
TQMap<TQString, int>::iterator iter = m_monthNameMap.find(dateMonth);
if (iter == m_monthNameMap.end())
return false;
entry.timeStruct.tm_mon = iter.data();
}
// Get time/year field
if (!line->getToken(++index, token))
return false;
pos = token.find(":.-");
if (pos != -1) {
// token is a time
if (!pos || static_cast<size_t>(pos) == (token.getLength() - 1))
return false;
TQString str = token.getString();
bool tmp;
int hour = str.left(pos).toInt(&tmp);
if (!tmp)
return false;
int minute = str.mid(pos + 1).toInt(&tmp);
if (!tmp)
return false;
if (hour < 0 || hour > 23)
return false;
if (minute < 0 || minute > 59)
return false;
entry.timeStruct.tm_hour = hour;
entry.timeStruct.tm_min = minute;
// Some servers use times only for files nweer than 6 months,
int year = TQDate::currentDate().year();
int now = TQDate::currentDate().day() + 31 * TQDate::currentDate().month();
int file = entry.timeStruct.tm_mon * 31 + entry.timeStruct.tm_mday;
if (now >= file)
entry.timeStruct.tm_year = year;
else
entry.timeStruct.tm_year = year - 1;
} else if (!entry.timeStruct.tm_year) {
// token is a year
if (!token.isNumeric() && !token.isLeftNumeric())
return false;
int year = token.getInteger();
if (year > 3000)
return false;
if (year < 1000)
year += 1900;
entry.timeStruct.tm_year = year;
if (bHasYearAndTime) {
if (!line->getToken(++index, token))
return false;
if (token.find(":") == 2 && token.getLength() == 5 && token.isLeftNumeric() && token.isRightNumeric()) {
int pos = token.find(":");
// token is a time
if (!pos || static_cast<size_t>(pos) == (token.getLength() - 1))
return false;
TQString str = token.getString();
bool tmp;
long hour = str.left(pos).toInt(&tmp);
if (!tmp)
return false;
long minute = str.mid(pos + 1).toInt(&tmp);
if (!tmp)
return false;
if (hour < 0 || hour > 23)
return false;
if (minute < 0 || minute > 59)
return false;
entry.timeStruct.tm_hour = hour;
entry.timeStruct.tm_min = minute;
} else {
index--;
}
}
} else {
index--;
}
return true;
}
bool FtpDirectoryParser::parseShortDate(DToken &token, DirectoryEntry &entry)
{
if (token.getLength() < 1)
return false;
bool gotYear = false;
bool gotMonth = false;
bool gotDay = false;
bool gotMonthName = false;
int value = 0;
int pos = token.find("-./");
if (pos < 1)
return false;
if (!token.isNumeric(0, pos)) {
// Seems to be monthname-dd-yy
// Check month name
TQString dateMonth = token.getString().mid(0, pos);
dateMonth = dateMonth.lower();
TQMap<TQString, int>::iterator iter = m_monthNameMap.find(dateMonth);
if (iter == m_monthNameMap.end())
return false;
entry.timeStruct.tm_mon = iter.data();
gotMonth = true;
gotMonthName = true;
} else if (pos == 4) {
// Seems to be yyyy-mm-dd
int year = token.getInteger(0, pos);
if (year < 1900 || year > 3000)
return false;
entry.timeStruct.tm_year = year;
gotYear = true;
} else if (pos <= 2) {
int value = token.getInteger(0, pos);
if (token[pos] == '.') {
// Maybe dd.mm.yyyy
if (value < 1900 || value > 3000)
return false;
entry.timeStruct.tm_mday = value;
gotDay = true;
} else {
// Detect mm-dd-yyyy or mm/dd/yyyy and
// dd-mm-yyyy or dd/mm/yyyy
if (value < 1)
return false;
if (value > 12) {
if (value > 31)
return false;
entry.timeStruct.tm_mday = value;
gotDay = true;
} else {
entry.timeStruct.tm_mon = value;
gotMonth = true;
}
}
} else {
return false;
}
int pos2 = token.find("-./", pos + 1);
if (pos2 == -1 || (pos2 - pos) == 1)
return false;
if (static_cast<size_t>(pos2) == (token.getLength() - 1))
return false;
// If we already got the month and the second field is not numeric,
// change old month into day and use new token as month
if (!token.isNumeric(pos + 1, pos2 - pos - 1) && gotMonth) {
if (gotMonthName)
return false;
if (gotDay)
return false;
gotDay = true;
gotMonth = false;
entry.timeStruct.tm_mday = entry.timeStruct.tm_mon;
}
if (gotYear || gotDay) {
// Month field in yyyy-mm-dd or dd-mm-yyyy
// Check month name
TQString dateMonth = token.getString().mid(pos + 1, pos2 - pos - 1);
dateMonth = dateMonth.lower();
TQMap<TQString, int>::iterator iter = m_monthNameMap.find(dateMonth);
if (iter == m_monthNameMap.end())
return false;
entry.timeStruct.tm_mon = iter.data();
gotMonth = true;
} else {
int value = token.getInteger(pos + 1, pos2 - pos - 1);
// Day field in mm-dd-yyyy
if (value < 1 || value > 31)
return false;
entry.timeStruct.tm_mday = value;
gotDay = true;
}
value = token.getInteger(pos2 + 1, token.getLength() - pos2 - 1);
if (gotYear) {
// Day field in yyy-mm-dd
if (!value || value > 31)
return false;
entry.timeStruct.tm_mday = value;
gotDay = true;
} else {
if (value < 0)
return false;
if (value < 50) {
value += 2000;
} else if (value < 1000) {
value += 1900;
}
entry.timeStruct.tm_year = value;
gotYear = true;
}
if (!gotMonth || !gotDay || !gotYear)
return false;
return true;
}
bool FtpDirectoryParser::parseDos(DLine *line, DirectoryEntry &entry)
{
int index = 0;
DToken token;
// Get first token, has to be a valid date
if (!line->getToken(index, token))
return false;
if (!parseShortDate(token, entry))
return false;
// Extract time
if (!line->getToken(++index, token))
return false;
if (!parseTime(token, entry))
return false;
// If next token is <DIR>, entry is a directory
// else, it should be the filesize.
if (!line->getToken(++index, token))
return false;
if (token.getString() == "<DIR>") {
entry.setType('d');
entry.setSize(0);
} else if (token.isNumeric() || token.isLeftNumeric()) {
// Convert size, filter out separators
unsigned long size = 0;
int len = token.getLength();
for (int i = 0; i < len; i++) {
char chr = token[i];
if (chr == ',' || chr == '.')
continue;
if (chr < '0' || chr > '9')
return false;
size *= 10;
size += chr - '0';
}
entry.setSize(size);
entry.setType('f');
} else {
return false;
}
// Extract filename
if (!line->getToken(++index, token, true))
return false;
entry.setFilename(token.getString());
return true;
}
bool FtpDirectoryParser::parseTime(DToken &token, DirectoryEntry &entry)
{
int pos = token.find(":");
if (pos < 1 || static_cast<unsigned int>(pos) >= (token.getLength() - 1))
return false;
int hour = token.getInteger(0, pos);
if (hour < 0 || hour > 23)
return false;
int minute = token.getInteger(pos + 1, 2);
if (minute < 0 || minute > 59)
return false;
// Convert to 24h format
if (!token.isRightNumeric()) {
if (token[token.getLength() - 2] == 'P') {
if (hour < 12) {
hour += 12;
}
} else if (hour == 12) {
hour = 0;
}
}
entry.timeStruct.tm_hour = hour;
entry.timeStruct.tm_min = minute;
return true;
}
bool FtpDirectoryParser::parseVms(DLine *line, DirectoryEntry &entry)
{
DToken token;
int index = 0;
if (!line->getToken(index, token))
return false;
int pos = token.find(";");
if (pos == -1)
return false;
if (pos > 4 && token.getString().mid(pos - 4, 4) == ".DIR") {
entry.setType('d');
entry.setFilename(token.getString().left(pos - 4) + token.getString().mid(pos));
} else {
entry.setType('f');
entry.setFilename(token.getString());
}
// Get size
if (!line->getToken(++index, token))
return false;
if (!token.isNumeric() && !token.isLeftNumeric())
return false;
entry.setSize(token.getInteger());
// Get date
if (!line->getToken(++index, token))
return false;
if (!parseShortDate(token, entry))
return false;
// Get time
if (!line->getToken(++index, token))
return true;
if (!parseTime(token, entry)) {
int len = token.getLength();
if (token[0] == '[' && token[len] != ']')
return false;
if (token[0] == '(' && token[len] != ')')
return false;
if (token[0] != '[' && token[len] == ']')
return false;
if (token[0] != '(' && token[len] == ')')
return false;
index--;
}
// Owner / group
while (line->getToken(++index, token)) {
int len = token.getLength();
if (len > 2 && token[0] == '(' && token[len - 1] == ')')
entry.setPermissions(0);
else if (len > 2 && token[0] == '[' && token[len - 1] == ']')
entry.setOwner(token.getString().mid(1, len - 2));
else
entry.setPermissions(0);
}
return true;
}
bool FtpDirectoryParser::parseComplexFileSize(DToken &token, filesize_t &size)
{
if (token.isNumeric()) {
size = token.getInteger();
return true;
}
int len = token.getLength() - 1;
char last = token[len];
if (last == 'B' || last == 'b') {
char c = token[len];
if (c < '0' || c > '9') {
last = token[len];
len--;
}
}
size = 0;
int dot = -1;
for (int i = 0; i < len; i++) {
char c = token[i];
if (c >= '0' && c <= '9') {
size *= 10;
size += c - '0';
} else if (c == '.') {
if (dot != -1)
return false;
dot = len - i - 1;
} else {
return false;
}
}
switch (last) {
case 'k':
case 'K': {
size *= 1000;
break;
}
case 'm':
case 'M': {
size *= 1000 * 1000;
break;
}
case 'g':
case 'G': {
size *= 1000 * 1000 * 1000;
break;
}
case 't':
case 'T': {
size *= 1000 * 1000;
size *= 1000 * 1000;
break;
}
case 'b':
case 'B': break;
default: return false;
}
while (dot-- > 0)
size /= 10;
return true;
}
}