7#include <QRegularExpression>
18 QVector<QFtpCompatDirEntry> result;
20 const QString listing = QString::fromUtf8(rawListing);
21 const QStringList lines = listing.split(QLatin1Char(
'\n'), Qt::SkipEmptyParts);
23 for (
const QString &rawLine : lines) {
24 const QString line = rawLine.trimmed();
30 QFtpCompatDirEntry entry;
32 if (line.contains(QLatin1Char(
'='))) {
34 entry = parseMlsd(line);
35 }
else if (!line.isEmpty() && line.at(0).isLetter()
36 && line.size() > 1 && line.at(1) == QLatin1Char(
'-')) {
38 entry = parseWindows(line);
41 entry = parseUnix(line);
44 if (!entry.
name().isEmpty()) {
47 qDebug() <<
"QFtpCompatDirEntry: could not parse listing line:" << line;
62 const int spaceIdx = line.indexOf(QLatin1Char(
' '));
68 e.m_name = line.mid(spaceIdx + 1);
70 const QStringList facts = line.left(spaceIdx).split(QLatin1Char(
';'), Qt::SkipEmptyParts);
71 for (
const QString &fact : facts) {
72 const int eq = fact.indexOf(QLatin1Char(
'='));
76 const QString key = fact.left(eq).toLower();
77 const QString value = fact.mid(eq + 1);
79 if (key == QLatin1String(
"type")) {
80 const QString vl = value.toLower();
81 e.m_isDirectory = (vl == QLatin1String(
"dir") || vl == QLatin1String(
"cdir")
82 || vl == QLatin1String(
"pdir"));
83 }
else if (key == QLatin1String(
"size")) {
84 e.m_size = value.toLongLong();
85 }
else if (key == QLatin1String(
"modify")) {
87 e.m_lastModified = QDateTime::fromString(value.left(14),
88 QStringLiteral(
"yyyyMMddHHmmss"));
89 e.m_lastModified.setTimeZone(QTimeZone::UTC);
90 }
else if (key == QLatin1String(
"unix.mode")) {
91 e.m_permissions = value;
92 }
else if (key == QLatin1String(
"unix.owner")) {
94 }
else if (key == QLatin1String(
"unix.group")) {
110 static const QRegularExpression re(
113 R
"(^([a-zA-Z\-]{10})\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+)\s+)"
115 R
"((\w{3})\s+(\d{1,2})\s+(\d{4}|\d{2}:\d{2})\s+(.+)$)"
119 const QRegularExpressionMatch m = re.match(line);
124 QFtpCompatDirEntry e;
125 e.m_permissions = m.captured(1);
126 e.m_owner = m.captured(3);
127 e.m_group = m.captured(4);
128 e.m_size = m.captured(5).toLongLong();
130 const QChar typeChar = e.m_permissions.isEmpty() ? QLatin1Char(
'-')
131 : e.m_permissions.at(0);
132 e.m_isDirectory = (typeChar == QLatin1Char(
'd'));
133 e.m_isSymLink = (typeChar == QLatin1Char(
'l'));
136 const QString month = m.captured(6);
137 const int day = m.captured(7).toInt();
138 const QString yearOrTime = m.captured(8);
140 static const QStringList monthNames = {
141 QStringLiteral(
"Jan"), QStringLiteral(
"Feb"), QStringLiteral(
"Mar"),
142 QStringLiteral(
"Apr"), QStringLiteral(
"May"), QStringLiteral(
"Jun"),
143 QStringLiteral(
"Jul"), QStringLiteral(
"Aug"), QStringLiteral(
"Sep"),
144 QStringLiteral(
"Oct"), QStringLiteral(
"Nov"), QStringLiteral(
"Dec")
146 const int monthNum = monthNames.indexOf(month) + 1;
148 if (yearOrTime.contains(QLatin1Char(
':'))) {
150 const QStringList parts = yearOrTime.split(QLatin1Char(
':'));
151 const int hour = parts.value(0).toInt();
152 const int minute = parts.value(1).toInt();
153 int year = QDate::currentDate().year();
154 if (QDate(year, monthNum, day) > QDate::currentDate()) {
157 e.m_lastModified = QDateTime(QDate(year, monthNum, day),
159 QTimeZone::LocalTime);
161 const int year = yearOrTime.toInt();
162 e.m_lastModified = QDateTime(QDate(year, monthNum, day),
164 QTimeZone::LocalTime);
168 QString
name = m.captured(9);
170 const int arrowIdx = name.indexOf(QStringLiteral(
" -> "));
172 e.m_linkTarget = name.mid(arrowIdx + 4);
173 name = name.left(arrowIdx);
189 static const QRegularExpression re(
192 R
"(^(\d{2}-\d{2}-\d{2})\s+(\d{2}:\d{2}[AP]M)\s+(<DIR>|\d+)\s+(.+)$)"
196 const QRegularExpressionMatch m = re.match(line);
201 QFtpCompatDirEntry e;
203 const QString dateStr = m.captured(1);
204 const QString timeStr = m.captured(2);
205 const QString sizeOrDir = m.captured(3);
206 e.m_name = m.captured(4);
208 e.m_isDirectory = (sizeOrDir == QLatin1String(
"<DIR>"));
209 if (!e.m_isDirectory) {
210 e.m_size = sizeOrDir.toLongLong();
214 const QStringList dateParts = dateStr.split(QLatin1Char(
'-'));
215 if (dateParts.size() == 3) {
216 int year = dateParts.at(2).toInt();
217 year += (year < 70) ? 2000 : 1900;
218 const int month = dateParts.at(0).toInt();
219 const int day = dateParts.at(1).toInt();
222 const bool isPm = timeStr.endsWith(QLatin1String(
"PM"), Qt::CaseInsensitive);
223 const QStringList tp = timeStr.left(5).split(QLatin1Char(
':'));
224 int hour = tp.value(0).toInt();
225 if (isPm && hour != 12) { hour += 12; }
226 if (!isPm && hour == 12) { hour = 0; }
227 const int minute = tp.value(1).toInt();
229 e.m_lastModified = QDateTime(QDate(year, month, day),
231 QTimeZone::LocalTime);
243 return QStringLiteral(
"[%1] %2 size=%3 modified=%4 owner=%5:%6")
244 .arg(m_isDirectory ? QLatin1String(
"DIR ") : QLatin1String(
"FILE"),
246 QString::number(m_size),
247 m_lastModified.toString(Qt::ISODate),
Represents a single entry from an FTP LIST response.
QString name() const noexcept
File or directory name (never includes the path).
QString toString() const
Returns a human-readable summary for debug output.
static QVector< QFtpCompatDirEntry > parseList(const QByteArray &rawListing)
Parses a full LIST / MLSD response body into directory entries.
FTP directory entry returned by QFtpCompat::list().