As I described in
my previous post, I use the Windows CE Ftp File Server for remote browsing the
device's file system. I also use it to update the device's firmware.
Some time ago I wrote .NET C# code to initiate the
firmware upgrade from a desktop Windows PC. I decided to use the .NET System.Net.FtpWebRequest library.
At first everything seemed to work quite well and the
code was easy to use. Until at a certain date the firmware upgrade failed. The
WireShark trace revealed "an unrecognized command" during the file
transfer from the CE device back to the desktop Windows side during the System.Net.WebRequestMethods.Ftp.DownloadFile
call. (You might wonder why DownloadFile? Well, I verify what I uploaded before to the device
with System.Net.WebRequestMethods.Ftp.UploadFile)
It turned out in the - long - end that when your desktop Windows
side has a different locale set other than US English, the System.Net.WebRequestMethods.Ftp.DownloadFile
underlying issues an additional ftp "MDTM" (file MoDificationTiMe) request. Even when System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore) was
specified in the webRequest.CachePolicy
call.
The Windows CE sample Ftp File Server has always been conforming
to RFC959. But the "MDTM" command is a later extension specified in
RFC3659, which was not understood by the Ftp File Server. Hence the
“unrecognized command”.
So by adding the missing RFC3659 commands in the Windows
CE Ftp File Server implementation, I could make the desktop C# FtpWebRequest code work again.
Here are my ftpsession.cpp changes
WCHAR *pszCmdList[] =
{
// RFC 959
L"USER",
...
// RFC 3659 additions
L"MDTM", L"SIZE"
};
PFUNCCOMMAND pfuncCommand[] =
{
// RFC 959
CFtpSession::cmdUSER,
...
// RFC 3659 additions
CFtpSession::cmdMDTM,
CFtpSession::cmdSIZE
};
And my cmds.cpp additions
DWORD CFtpSession::cmdMDTM(WCHAR *pszArg)
{
VALIDATEARG(pszArg)
WCHAR
szFile[MAX_PATH];
DWORD dwReply =
FILE_UNAVAILABLE;
if (0 == GetAbsFileName(pszArg, szFile, MAX_PATH))
{
Reply(_sock,
FILE_ACTION_NOT_TAKEN);
return FALSE;
}
HANDLE hFile =
NULL;
if ( (hFile = CreateFile(szFile, GENERIC_READ,
FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL))
==
INVALID_HANDLE_VALUE)
{
Reply(_sock,
FILE_UNAVAILABLE);
return FALSE;
}
FILETIME
CreationTime;
FILETIME
LastAccessTime;
FILETIME
LastWriteTime;
if (0 == GetFileTime(hFile, &CreationTime,
&LastAccessTime, &LastWriteTime))
{
Reply(_sock,
FILE_UNAVAILABLE);
return FALSE;
}
FILETIME ft;
SYSTEMTIME st;
WCHAR
szLastModifiedTime[20] = L"";
FileTimeToLocalFileTime(&LastWriteTime,
&ft);
FileTimeToSystemTime(&ft,
&st);
StringCchPrintfW(szLastModifiedTime,
SVSUTIL_ARRLEN(szLastModifiedTime), L"%04d%02d%02d%02d%02d%02d",
st.wYear,
st.wMonth,st.wDay,st.wHour,st.wMinute, st.wSecond); // make
YYYYMMDDhhmmss reply
// Note we adjusted the FILE_STATUS resource string to
accommodate the 'szLastModifiedTime' reply.
// As FILE_STATUS is nowhere else used in the FTP code,
this should be no problem.
Reply(_sock,
FILE_STATUS, szLastModifiedTime);
return TRUE;
}
DWORD CFtpSession::cmdSIZE(WCHAR *pszArg)
{
VALIDATEARG(pszArg);
WCHAR
szFile[MAX_PATH];
DWORD dwReply =
FILE_UNAVAILABLE;
if (0 == GetAbsFileName(pszArg, szFile, MAX_PATH))
{
Reply(_sock,
FILE_ACTION_NOT_TAKEN);
return FALSE;
}
HANDLE hFile =
NULL;
if ( (hFile = CreateFile(szFile, GENERIC_READ,
FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL))
==
INVALID_HANDLE_VALUE)
{
Reply(_sock,
FILE_UNAVAILABLE);
return FALSE;
}
DWORD FileSizeHigh
= 0;
DWORD FileSizeLow
= GetFileSize(hFile, &FileSizeHigh);
if (0xFFFFFFFF == FileSizeLow)
{
Reply(_sock,
FILE_UNAVAILABLE);
return FALSE;
}
WCHAR
szFileSize[20] = L"";
StringCchPrintfW(szFileSize,
SVSUTIL_ARRLEN(szFileSize), L"%d",
FileSizeLow); //
we limit to 4Gb?
// Note we adjusted the FILE_STATUS resource string to
accommodate the 'szFileSize' reply.
// As FILE_STATUS is nowhere else used in the FTP code,
this should be no problem.
Reply(_sock,
FILE_STATUS, szFileSize);
return TRUE;
}
I only added the “MDTM” and “SIZE” commands, not the 2
other RFC3659 commands “MLST” and “MLSD”. These were not needed for working
with the System.Net.FtpWebRequest class, to my knowledge.
The Ftp File Server also doesn’t support the Compressed
and Block data transfer mode, only Stream mode is supported. This made the
“SIZE” implementation pretty straightforward.
To test this code, you can also invoke the “MDTM” and
“SIZE” methods directly from the System.Net.FtpWebRequest class by calling System.Net.WebRequestMethods.Ftp.GetDateTimestamp and System.Net.WebRequestMethods.Ftp.GetFileSize
respectively.
If you would call System.Net.WebRequestMethods.Ftp.UploadFile from a non US English locale
desktop Windows, part of the communication will do exactly what System.Net.WebRequestMethods.Ftp.GetDateTimestamp
does.
The .NET C# test code
private Exception
GetLastModifiedTime(string uri, out DateTime
lastModified)
{
lastModified = DateTime.MinValue;
try
{
System.Net.FtpWebRequest webRequest = (System.Net.FtpWebRequest)System.Net.FtpWebRequest.Create(uri);
webRequest.Credentials = Credentials;
webRequest.Method = System.Net.WebRequestMethods.Ftp.GetDateTimestamp; // "MDTM"
command
webRequest.Proxy = null;
webRequest.CachePolicy
= new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
webRequest.Timeout = 5000;
webRequest.KeepAlive = false;
webRequest.UseBinary = true;
webRequest.ConnectionGroupName = "FTPClient";
using (System.Net.FtpWebResponse
response = (System.Net.FtpWebResponse)webRequest.GetResponse())
{
using (System.IO.Stream
responseStream = response.GetResponseStream())
{
using (System.IO.StreamReader
reader = new System.IO.StreamReader(responseStream))
{
string input = reader.ReadToEnd();
lastModified = response.LastModified;
}
}
}
return null;
}
catch (Exception
ex)
{
return ex;
}
}
private Exception
GetFileSize(string uri, out long fileSize)
{
fileSize = -1;
try
{
System.Net.FtpWebRequest webRequest = (System.Net.FtpWebRequest)System.Net.FtpWebRequest.Create(uri);
webRequest.Credentials = Credentials;
webRequest.Method = System.Net.WebRequestMethods.Ftp.GetFileSize;
// "SIZE" command
webRequest.Proxy = null;
webRequest.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
webRequest.Timeout = 5000;
webRequest.KeepAlive = false;
webRequest.UseBinary = true;
webRequest.ConnectionGroupName = "FTPClient";
using (System.Net.FtpWebResponse
response = (System.Net.FtpWebResponse)webRequest.GetResponse())
{
using (System.IO.Stream
responseStream = response.GetResponseStream())
{
using (System.IO.StreamReader
reader = new System.IO.StreamReader(responseStream))
{
string input = reader.ReadToEnd();
fileSize = response.ContentLength;
}
}
}
return null;
}
catch (Exception
ex)
{
return ex;
}
}