Monday, November 15, 2010

Uploading a file using FTP and the WebClient

Note: more detailed documentation for the .NET 4.0 explains the reason for requiring the additional slash (/). See the remarks section for the FtpWebRequest class. If you are using .NET 4.0, this class is probably a better bet.

I ran into some trouble uploading a file using the System.Net.WebClient. Looking at examples in the web, it seemed quite straightforward, but I found that I needed to include an additional forward slash in order to get the transfer to work correctly.

At first, I struggled to upload a file with this call to my WebClient instance:

client.UploadFile("ftp://sssssss/folder/folder/folder/t.txt", @"c:\temp\t.txt");

I was able to download a file in that location with Internet Explorer using the URI above, but not upload to it using the WebClient.

This dump from Wireshark illustrates the problem (note line 14), which seems to be a bug in the WebClient to me:

220 sssssss FTP server (Version 1.1.214.4(PHNE_38458) Tue Jul 29 07:36:52 GMT 2008) ready.
USER uuuuuuu
331 Password required for uuuuuuu.
PASS ppppppp
230 User uuuuuuulogged in.
OPTS utf8 on
500 'OPTS utf8 on': command not understood.
PWD
257 "/home/fld/uuuuuuu" is current directory.
TYPE I
200 Type set to I.
PASV
227 Entering Passive Mode (xx,xx,xx,xx,xx,xx)
STOR folder/folder/folder/t.txt
553 Could not determine cwdir: No such file or directory.
221 You could at least say goodbye.

Noticing that the STOR command was providing a relative path (not starting with /), I assume that the server was trying to save the file into the current folder i.e. /home/fld/uuuuuuu/folder/folder/folder/t.txt. To test this theory, I added a slash at the beginning ot the path:

client.UploadFile("ftp://sssssss//folder/folder/folder/t.txt", @"c:\temp\t.txt");

I am not sure if this is a valid URI, but it seems to work with the WebClient, although I am not sure why the RETR command (line 19) is executed as in the following Wireshark dump:

220 sssssss FTP server (Version 1.1.214.4(PHNE_38458) Tue Jul 29 07:36:52 GMT 2008) ready.
USER uuuuuuu
331 Password required for uuuuuuu.
PASS ppppppp
230 User uuuuuuu logged in.
OPTS utf8 on
500 'OPTS utf8 on': command not understood.
PWD
257 "/home/fld/uuuuuuu" is current directory.
TYPE I
200 Type set to I.
PASV
227 Entering Passive Mode (xx,xx,xx,xx,xx,xx)
STOR /folder/folder/folder/t.txt
150 Opening BINARY mode data connection for /folder/folder/folder/t.txt.
226 Transfer complete.
PASV
227 Entering Passive Mode (xx,xx,xx,xx,xx,xx)
RETR folder/folder/folder/t.txt
550 folder/folder/folder/t.txt: No such file or directory.
221 You could at least say goodbye.


This is the final code that seems to work:

        public static void SendFile(string sourcePath, string server, string userName, string password, string remoteFolder, string remoteFileName)
        {
            using (WebClient client = new WebClient())
            {
                string dest = string.Format(CultureInfo.InvariantCulture, "ftp://{0}/{1}/{2}", server, remoteFolder, remoteFileName);
                client.Credentials = new NetworkCredential(userName, password);
                client.Proxy = null;
                client.UploadFile(dest, sourcePath);
            }
        }

And the corresponding integration test:

        [TestMethod]
        public void ShouldSubmitFileToFtpServer()
        {
            Guid uniqueId = Guid.NewGuid();

            string tempFile = Path.Combine(Path.GetTempPath(), "FtpTestFile.txt");
            File.WriteAllText(tempFile, "Test file - safe to delete. " + uniqueId.ToString());

            string server = "sssssss";
            string userName = "uuuuuuu";
            string password = "ppppppp";
            string folder = "/folder/folder/folder";
            string fileName = "t.txt";
            Ftp.SendFile(tempFile, server, userName, password, folder, fileName);

            string tempFileDown = tempFile + ".down";

            if(File.Exists(tempFileDown)) File.Delete(tempFileDown);

            using (WebClient client = new WebClient())
            {
                client.Credentials = new NetworkCredential(userName, password);
                string ftpLink = String.Format(CultureInfo.InvariantCulture, "ftp://{0}/{1}/{2}", server, folder, fileName);
                client.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
                client.DownloadFile(ftpLink, tempFileDown);
            }

            string content = File.ReadAllText(tempFileDown);

            Assert.IsTrue(content.Contains(uniqueId.ToString()));
        }

1 comment:

  1. Thank you for taking the time to post this

    ReplyDelete