Skip to content

Commit bbd4e0e

Browse files
Adding Support for MDTM command (#86)
--------- Co-authored-by: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com>
1 parent a746b32 commit bbd4e0e

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

fineftp-server/src/filesystem.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <list>
77
#include <mutex> // IWYU pragma: keep
88
#include <sstream>
9+
#include <cmath>
910

1011
#include <chrono>
1112
#include <ctime>
@@ -29,6 +30,12 @@
2930

3031
#else // _WIN32
3132

33+
#if ((defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
34+
#define ST_MTIME_NSEC st_mtimespec.tv_nsec
35+
#elif defined(__unix__)
36+
#define ST_MTIME_NSEC st_mtim.tv_nsec
37+
#endif
38+
3239
#include <cerrno>
3340
#include <cstring>
3441
#include <dirent.h>
@@ -248,6 +255,94 @@ namespace Filesystem
248255
return month_names.at(file_timeinfo.tm_mon) + date.str();
249256
}
250257

258+
std::string FileStatus::generalizedTimeString() const
259+
{
260+
if (!is_ok_)
261+
return "19700101000000";
262+
263+
// The generalized time format is: YYYYMMDDHHMMSS
264+
// Example: 19970716210700
265+
266+
static constexpr auto tm_year_base_year = 1900;
267+
std::tm file_timeinfo{};
268+
int time_ms = 0;
269+
270+
#if defined(__unix__)
271+
int time_carryover_seconds = 0;
272+
if(file_status_.ST_MTIME_NSEC != 0)
273+
{
274+
time_ms = static_cast<time_t>(std::round(static_cast<double>(file_status_.ST_MTIME_NSEC) / 1000000));
275+
time_carryover_seconds = time_ms / 1000;
276+
time_ms = time_ms % 1000;
277+
}
278+
auto time_sec = file_status_.st_mtime + time_carryover_seconds;
279+
gmtime_r (&time_sec, &file_timeinfo);
280+
281+
#elif defined (_WIN32)
282+
// Query write time from WIN32 API, as the POSIX API does not reveal milliseconds on Windows
283+
284+
const std::wstring w_path = StrConvert::Utf8ToWide(path_);
285+
HANDLE file_handle = CreateFileW(w_path.c_str()
286+
, GENERIC_READ
287+
, FILE_SHARE_READ
288+
, NULL
289+
, OPEN_EXISTING
290+
, 0
291+
, NULL);
292+
293+
if(file_handle == INVALID_HANDLE_VALUE)
294+
{
295+
return "19700101000000";
296+
}
297+
298+
FILETIME filetime_create{}; // Unused
299+
FILETIME filetime_access{}; // Unused
300+
FILETIME filetime_write{};
301+
302+
// Retrieve the file times for the file.
303+
if (!GetFileTime(file_handle, &filetime_create, &filetime_access, &filetime_write))
304+
return "19700101000000";
305+
306+
CloseHandle(file_handle);
307+
308+
// Convert the last-write-time from a filetime to systemtime (in UTC).
309+
SYSTEMTIME file_write_time_utc{};
310+
FileTimeToSystemTime(&filetime_write, &file_write_time_utc);
311+
312+
// Fill the time info struct just as the POSIX api would have, but also set the milliseconds
313+
file_timeinfo.tm_year = file_write_time_utc.wYear - tm_year_base_year;
314+
file_timeinfo.tm_mon = file_write_time_utc.wMonth - 1;
315+
file_timeinfo.tm_mday = file_write_time_utc.wDay;
316+
file_timeinfo.tm_hour = file_write_time_utc.wHour;
317+
file_timeinfo.tm_min = file_write_time_utc.wMinute;
318+
file_timeinfo.tm_sec = file_write_time_utc.wSecond;
319+
time_ms = file_write_time_utc.wMilliseconds;
320+
321+
#else
322+
static std::mutex mtx;
323+
{
324+
std::lock_guard<std::mutex> lock(mtx);
325+
file_timeinfo = *std::gmtime (&file_status_.st_mtime);
326+
}
327+
#endif
328+
329+
std::stringstream date;
330+
date << std::setw( 4 ) << ( file_timeinfo.tm_year + tm_year_base_year )
331+
<< std::setw( 2 ) << std::setfill( '0' ) << ( file_timeinfo.tm_mon + 1 )
332+
<< std::setw( 2 ) << std::setfill( '0' ) << file_timeinfo.tm_mday
333+
<< std::setw( 2 ) << std::setfill( '0' ) << file_timeinfo.tm_hour
334+
<< std::setw( 2 ) << std::setfill( '0' ) << file_timeinfo.tm_min
335+
<< std::setw( 2 ) << std::setfill( '0' ) << file_timeinfo.tm_sec;
336+
337+
// Append milliseconds only if available
338+
if (time_ms > 0)
339+
{
340+
date << "." << std::setw( 3 ) << std::setfill( '0' ) << time_ms;
341+
}
342+
343+
return date.str();
344+
}
345+
251346
bool FileStatus::canOpenDir() const
252347
{
253348
if (!is_ok_)

fineftp-server/src/filesystem.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ namespace fineftp
5555

5656
std::string timeString() const;
5757

58+
std::string generalizedTimeString() const;
59+
5860
bool canOpenDir() const;
5961

6062

fineftp-server/src/ftp_session.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ namespace fineftp
273273
{ "FEAT", std::bind(&FtpSession::handleFtpCommandFEAT, this, std::placeholders::_1) },
274274
{ "OPTS", std::bind(&FtpSession::handleFtpCommandOPTS, this, std::placeholders::_1) },
275275
{ "SIZE", std::bind(&FtpSession::handleFtpCommandSIZE, this, std::placeholders::_1) },
276+
{ "MDTM", std::bind(&FtpSession::handleFtpCommandMDTM, this, std::placeholders::_1) }
276277
};
277278

278279
auto command_it = command_map.find(ftp_command);
@@ -1179,6 +1180,7 @@ namespace fineftp
11791180
ss << "211- Feature List:\r\n";
11801181
ss << " UTF8\r\n";
11811182
ss << " SIZE\r\n";
1183+
ss << " MDTM\r\n";
11821184
ss << " LANG EN\r\n";
11831185
ss << "211 END\r\n";
11841186

@@ -1199,6 +1201,38 @@ namespace fineftp
11991201
sendFtpMessage(FtpReplyCode::COMMAND_NOT_IMPLEMENTED_FOR_PARAMETER, "Unrecognized parameter");
12001202
}
12011203

1204+
void FtpSession::handleFtpCommandMDTM(const std::string& param) {
1205+
if (!logged_in_user_)
1206+
{
1207+
sendFtpMessage(FtpReplyCode::NOT_LOGGED_IN, "Not logged in");
1208+
return;
1209+
}
1210+
1211+
if (static_cast<int>(logged_in_user_->permissions_ & (Permission::FileRead | Permission::DirList)) == 0)
1212+
{
1213+
sendFtpMessage(FtpReplyCode::ACTION_NOT_TAKEN, "Permission denied");
1214+
return;
1215+
}
1216+
1217+
if (param.empty())
1218+
{
1219+
sendFtpMessage(FtpReplyCode::SYNTAX_ERROR_PARAMETERS, "No file path given");
1220+
return;
1221+
}
1222+
1223+
const std::string local_path = toLocalPath(param);
1224+
1225+
auto file_status = Filesystem::FileStatus(local_path);
1226+
1227+
if (!file_status.isOk() || file_status.type() != Filesystem::FileType::RegularFile)
1228+
{
1229+
sendFtpMessage(FtpReplyCode::ACTION_NOT_TAKEN, local_path + " is not retrievable");
1230+
return;
1231+
}
1232+
1233+
sendFtpMessage(FtpReplyCode::FILE_STATUS, file_status.generalizedTimeString());
1234+
}
1235+
12021236
////////////////////////////////////////////////////////
12031237
// FTP data-socket send
12041238
////////////////////////////////////////////////////////

fineftp-server/src/ftp_session.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ namespace fineftp
109109

110110
void handleFtpCommandOPTS(const std::string& param);
111111

112+
void handleFtpCommandMDTM(const std::string& param);
113+
112114
////////////////////////////////////////////////////////
113115
// FTP data-socket send
114116
////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)