diff options
Diffstat (limited to 'kftpgrabber/src/engine/ftpdirectoryparser.cpp')
-rw-r--r-- | kftpgrabber/src/engine/ftpdirectoryparser.cpp | 1144 |
1 files changed, 1144 insertions, 0 deletions
diff --git a/kftpgrabber/src/engine/ftpdirectoryparser.cpp b/kftpgrabber/src/engine/ftpdirectoryparser.cpp new file mode 100644 index 0000000..074328c --- /dev/null +++ b/kftpgrabber/src/engine/ftpdirectoryparser.cpp @@ -0,0 +1,1144 @@ +/* + * 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 <qvaluevector.h> +#include <qstringlist.h> +#include <qdatetime.h> + +#include <time.h> +#include <sys/stat.h> + +namespace KFTPEngine { + +class DToken { +public: + enum TokenTypeInfo { + Unknown, + Yes, + No + }; + + DToken() + : m_token(QString::null), + m_valid(false) + { + } + + DToken(const QString &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; + } + + QString getToken() + { + return m_token; + } + + int getLength() + { + return m_length; + } + + QString getString(int type = 0) + { + switch (type) { + case 0: return m_token; break; + case 1: { + if (!isRightNumeric() || isNumeric()) + return QString::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 QString::null; + + int len = 0; + while (m_token[len] >= '0' && m_token[len] <= '9') + len++; + + return m_token.mid(0, len); + break; + } + } + + return QString::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; + } + + QChar operator[](unsigned int n) const + { + return m_token[n]; + } +private: + QString 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 QString &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: + QStringList m_stringList; + QValueVector<DToken> m_tokens; + QValueVector<DToken> m_endLineTokens; + QString 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 QString &line) +{ + QString 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(QString::fromAscii(data, len)); + + int pos; + while ((pos = m_buffer.find('\n')) > -1) { + DirectoryEntry entry; + QString line = m_buffer.mid(0, pos).stripWhiteSpace(); + line = m_socket->remoteEncoding()->decode(QCString(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 QString &line, DirectoryEntry &entry) +{ + QStringList facts = QStringList::split(';', line); + QStringList::Iterator end = facts.end(); + + for (QStringList::Iterator i = facts.begin(); i != end; ++i) { + if ((*i).contains('=')) { + QString key = (*i).section('=', 0, 0).lower(); + QString 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; +} + +bool FtpDirectoryParser::parseUnixPermissions(const QString &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 QString &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(QString::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; + + QString 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(QString::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 + QString 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(); + + QMap<QString, 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; + + QString 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 = QDate::currentDate().year(); + int now = QDate::currentDate().day() + 31 * QDate::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; + + QString 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 + QString dateMonth = token.getString().mid(0, pos); + dateMonth = dateMonth.lower(); + + QMap<QString, 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 + QString dateMonth = token.getString().mid(pos + 1, pos2 - pos - 1); + dateMonth = dateMonth.lower(); + + QMap<QString, 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; +} + +} |