/// <summary> /// Parses LIST format listings /// </summary> /// <param name="buf">A line from the listing</param> /// <param name="capabilities">Server capabilities</param> /// <returns>FtpListItem if the item is able to be parsed</returns> static FtpListItem ParseUnixList(string buf, FtpCapability capabilities) { string regex = @"(?<permissions>.+)\s+" + @"(?<objectcount>\d+)\s+" + @"(?<user>.+)\s+" + @"(?<group>.+)\s+" + @"(?<size>\d+)\s+" + @"(?<modify>\w+\s+\d+\s+\d+:\d+|\w+\s+\d+\s+\d+)\s" + @"(?<name>.*)$"; FtpListItem item = new FtpListItem(); Match m; if (!(m = Regex.Match(buf, regex, RegexOptions.IgnoreCase)).Success) { return(null); } // if this field is missing we can't determine // what the object is. if (m.Groups["permissions"].Value.Length == 0) { return(null); } switch (m.Groups["permissions"].Value[0]) { case 'd': item.Type = FtpFileSystemObjectType.Directory; break; case '-': case 's': item.Type = FtpFileSystemObjectType.File; break; case 'l': item.Type = FtpFileSystemObjectType.Link; break; default: return(null); } // if we can't determine a file name then // we are not considering this a successful parsing operation. if (m.Groups["name"].Value.Length < 1) { return(null); } item.Name = m.Groups["name"].Value; switch (item.Type) { case FtpFileSystemObjectType.Directory: // ignore these... if (item.Name == "." || item.Name == "..") { return(null); } break; case FtpFileSystemObjectType.Link: if (!item.Name.Contains(" -> ")) { return(null); } item.LinkTarget = item.Name.Remove(0, item.Name.IndexOf("-> ") + 3); item.Name = item.Name.Remove(item.Name.IndexOf(" -> ")); break; } // for date parser testing only //capabilities = ~(capabilities & FtpCapability.MDTM); //// // Ignore the Modify times sent in LIST format for files // when the server has support for the MDTM command // because they will never be as accurate as what can be had // by using the MDTM command. MDTM does not work on directories // so if a modify time was parsed from the listing we will try // to convert it to a DateTime object and use it for directories. //// if (((capabilities & FtpCapability.MDTM) != FtpCapability.MDTM || item.Type == FtpFileSystemObjectType.Directory) && m.Groups["modify"].Value.Length > 0) { item.Modified = m.Groups["modify"].Value.GetFtpDate(DateTimeStyles.AssumeLocal); if (item.Modified == DateTime.MinValue) { FtpTrace.WriteLine("GetFtpDate() failed on {0}", m.Groups["modify"].Value); } } else { if (m.Groups["modify"].Value.Length == 0) { FtpTrace.WriteLine("RegEx failed to parse modified date from {0}.", buf); } else if (item.Type == FtpFileSystemObjectType.Directory) { FtpTrace.WriteLine("Modified times of directories are ignored in UNIX long listings."); } else if ((capabilities & FtpCapability.MDTM) == FtpCapability.MDTM) { FtpTrace.WriteLine("Ignoring modified date because MDTM feature is present. If you aren't already, pass FtpListOption.Modify or FtpListOption.SizeModify to GetListing() to retrieve the modification time."); } } if (m.Groups["size"].Value.Length > 0) { long size; if (long.TryParse(m.Groups["size"].Value, out size)) { item.Size = size; } } if (m.Groups["permissions"].Value.Length > 0) { Match perms = Regex.Match(m.Groups["permissions"].Value, @"[\w-]{1}(?<owner>[\w-]{3})(?<group>[\w-]{3})(?<others>[\w-]{3})", RegexOptions.IgnoreCase); if (perms.Success) { if (perms.Groups["owner"].Value.Length == 3) { if (perms.Groups["owner"].Value[0] == 'r') { item.OwnerPermissions |= FtpPermission.Read; } if (perms.Groups["owner"].Value[1] == 'w') { item.OwnerPermissions |= FtpPermission.Write; } if (perms.Groups["owner"].Value[2] == 'x' || perms.Groups["owner"].Value[2] == 's') { item.OwnerPermissions |= FtpPermission.Execute; } if (perms.Groups["owner"].Value[2] == 's' || perms.Groups["owner"].Value[2] == 'S') { item.SpecialPermissions |= FtpSpecialPermissions.SetUserID; } } if (perms.Groups["group"].Value.Length == 3) { if (perms.Groups["group"].Value[0] == 'r') { item.GroupPermissions |= FtpPermission.Read; } if (perms.Groups["group"].Value[1] == 'w') { item.GroupPermissions |= FtpPermission.Write; } if (perms.Groups["group"].Value[2] == 'x' || perms.Groups["group"].Value[2] == 's') { item.GroupPermissions |= FtpPermission.Execute; } if (perms.Groups["group"].Value[2] == 's' || perms.Groups["group"].Value[2] == 'S') { item.SpecialPermissions |= FtpSpecialPermissions.SetGroupID; } } if (perms.Groups["others"].Value.Length == 3) { if (perms.Groups["others"].Value[0] == 'r') { item.OthersPermissions |= FtpPermission.Read; } if (perms.Groups["others"].Value[1] == 'w') { item.OthersPermissions |= FtpPermission.Write; } if (perms.Groups["others"].Value[2] == 'x' || perms.Groups["others"].Value[2] == 't') { item.OthersPermissions |= FtpPermission.Execute; } if (perms.Groups["others"].Value[2] == 't' || perms.Groups["others"].Value[2] == 'T') { item.SpecialPermissions |= FtpSpecialPermissions.Sticky; } } } } return(item); }
/// <summary> /// Transfers a file from the source FTP Server to the destination FTP Server via the FXP protocol asynchronously. /// </summary> private async Task <bool> TransferFileFXPInternalAsync(string sourcePath, FtpClient remoteClient, string remotePath, bool createRemoteDir, FtpRemoteExists existsMode, IProgress <FtpProgress> progress, CancellationToken token, FtpProgress metaProgress) { FtpReply reply; long offset = 0; bool fileExists = false; long fileSize = 0; var ftpFxpSession = await OpenPassiveFXPConnectionAsync(remoteClient, progress != null, token); if (ftpFxpSession != null) { try { ftpFxpSession.SourceServer.ReadTimeout = (int)TimeSpan.FromMinutes(30.0).TotalMilliseconds; ftpFxpSession.TargetServer.ReadTimeout = (int)TimeSpan.FromMinutes(30.0).TotalMilliseconds; // check if the file exists, and skip, overwrite or append if (existsMode == FtpRemoteExists.AppendNoCheck) { offset = await remoteClient.GetFileSizeAsync(remotePath, token); if (offset == -1) { offset = 0; // start from the beginning } } else { fileExists = await remoteClient.FileExistsAsync(remotePath, token); switch (existsMode) { case FtpRemoteExists.Skip: if (fileExists) { LogStatus(FtpTraceLevel.Info, "Skip is selected => Destination file exists => skipping"); //Fix #413 - progress callback isn't called if the file has already been uploaded to the server //send progress reports if (progress != null) { progress.Report(new FtpProgress(100.0, 0, 0, TimeSpan.FromSeconds(0), sourcePath, remotePath, metaProgress)); } return(true); } break; case FtpRemoteExists.Overwrite: if (fileExists) { await remoteClient.DeleteFileAsync(remotePath, token); } break; case FtpRemoteExists.Append: if (fileExists) { offset = await remoteClient.GetFileSizeAsync(remotePath, token); if (offset == -1) { offset = 0; // start from the beginning } } break; } } fileSize = await GetFileSizeAsync(sourcePath, token); // ensure the remote dir exists .. only if the file does not already exist! if (createRemoteDir && !fileExists) { var dirname = remotePath.GetFtpDirectoryName(); if (!await remoteClient.DirectoryExistsAsync(dirname, token)) { await remoteClient.CreateDirectoryAsync(dirname, token); } } if (offset == 0 && existsMode != FtpRemoteExists.AppendNoCheck) { // send command to tell the source server to 'send' the file to the destination server if (!(reply = await ftpFxpSession.SourceServer.ExecuteAsync($"RETR {sourcePath}", token)).Success) { throw new FtpCommandException(reply); } //Instruct destination server to store the file if (!(reply = await ftpFxpSession.TargetServer.ExecuteAsync($"STOR {remotePath}", token)).Success) { throw new FtpCommandException(reply); } } else { //tell source server to restart / resume if (!(reply = await ftpFxpSession.SourceServer.ExecuteAsync($"REST {offset}", token)).Success) { throw new FtpCommandException(reply); } // send command to tell the source server to 'send' the file to the destination server if (!(reply = await ftpFxpSession.SourceServer.ExecuteAsync($"RETR {sourcePath}", token)).Success) { throw new FtpCommandException(reply); } //Instruct destination server to append the file if (!(reply = await ftpFxpSession.TargetServer.ExecuteAsync($"APPE {remotePath}", token)).Success) { throw new FtpCommandException(reply); } } var transferStarted = DateTime.Now; long lastSize = 0; var sourceFXPTransferReply = ftpFxpSession.SourceServer.GetReplyAsync(token); var destinationFXPTransferReply = ftpFxpSession.TargetServer.GetReplyAsync(token); // while the transfer is not complete while (!sourceFXPTransferReply.IsCompleted || !destinationFXPTransferReply.IsCompleted) { // send progress reports every 1 second if (ftpFxpSession.ProgressServer != null) { // send progress reports if (progress != null && fileSize != -1) { offset = await ftpFxpSession.ProgressServer.GetFileSizeAsync(remotePath, token); if (offset != -1 && lastSize <= offset) { long bytesProcessed = offset - lastSize; lastSize = offset; ReportProgress(progress, fileSize, offset, bytesProcessed, DateTime.Now - transferStarted, sourcePath, remotePath, metaProgress); } } } await Task.Delay(FXPProgressInterval); } FtpTrace.WriteLine(FtpTraceLevel.Info, $"FXP transfer of file {sourcePath} has completed"); await NoopAsync(token); await remoteClient.NoopAsync(token); ftpFxpSession.Dispose(); return(true); } // Fix: catch all exceptions and dispose off the FTP clients if one occurs catch (Exception ex) { ftpFxpSession.Dispose(); throw ex; } } else { FtpTrace.WriteLine(FtpTraceLevel.Error, "Failed to open FXP passive Connection"); return(false); } }
/// <summary> /// Parses a line from a file listing using the first successful match in the Parsers collection. /// </summary> /// <param name="path">The source path of the file listing</param> /// <param name="buf">A line from the file listing</param> /// <param name="capabilities">Server capabilities</param> /// <returns>A FtpListItem object representing the parsed line, null if the line was /// unable to be parsed. If you have encountered an unsupported list type add a parser /// to the public static Parsers collection of FtpListItem.</returns> public static FtpListItem Parse(string path, string buf, FtpCapability capabilities) { if (buf != null && buf.Length > 0) { FtpListItem item; foreach (Parser parser in Parsers) { if ((item = parser(buf, capabilities)) != null) { // if this is a vax/openvms file listing // there are no slashes in the path name if (parser == (new Parser(ParseVaxList))) { item.FullName = path + item.Name; } else { FtpTrace.WriteLine(item.Name); // remove globbing/wildcard from path if (path.GetFtpFileName().Contains("*")) { path = path.GetFtpDirectoryName(); } if (item.Name != null) { // absolute path? then ignore the path input to this method. if (item.Name.StartsWith("/") || item.Name.StartsWith("./") || item.Name.StartsWith("../")) { item.FullName = item.Name; item.Name = item.Name.GetFtpFileName(); } else if (path != null) { item.FullName = path.GetFtpPath(item.Name); //.GetFtpPathWithoutGlob(); } else { FtpTrace.WriteLine("Couldn't determine the full path of this object:{0}{1}", Environment.NewLine, item.ToString()); } } // if a link target is set and it doesn't include an absolute path // then try to resolve it. if (item.LinkTarget != null && !item.LinkTarget.StartsWith("/")) { if (item.LinkTarget.StartsWith("./")) { item.LinkTarget = path.GetFtpPath(item.LinkTarget.Remove(0, 2)); } else { item.LinkTarget = path.GetFtpPath(item.LinkTarget); } } } item.Input = buf; return(item); } } } return(null); }
/// <summary> /// Gets a file listing from the server. Each <see cref="FtpListItem"/> object returned /// contains information about the file that was able to be retrieved. /// </summary> /// <remarks> /// If a <see cref="DateTime"/> property is equal to <see cref="DateTime.MinValue"/> then it means the /// date in question was not able to be retrieved. If the <see cref="FtpListItem.Size"/> property /// is equal to 0, then it means the size of the object could also not /// be retrieved. /// </remarks> /// <param name="path">The path of the directory to list</param> /// <param name="options">Options that dictacte how a list is performed and what information is gathered.</param> /// <returns>An array of FtpListItem objects</returns> /// <example><code source="..\Examples\GetListing.cs" lang="cs" /></example> public FtpListItem[] GetListing(string path, FtpListOption options) { FtpTrace.WriteFunc("GetListing", new object[] { path, options }); FtpListItem item = null; List <FtpListItem> lst = new List <FtpListItem>(); List <string> rawlisting = new List <string>(); string listcmd = null; string buf = null; // read flags bool isIncludeSelf = (options & FtpListOption.IncludeSelfAndParent) == FtpListOption.IncludeSelfAndParent; bool isForceList = (options & FtpListOption.ForceList) == FtpListOption.ForceList; bool isNoPath = (options & FtpListOption.NoPath) == FtpListOption.NoPath; bool isNameList = (options & FtpListOption.NameList) == FtpListOption.NameList; bool isUseLS = (options & FtpListOption.UseLS) == FtpListOption.UseLS; bool isAllFiles = (options & FtpListOption.AllFiles) == FtpListOption.AllFiles; bool isRecursive = (options & FtpListOption.Recursive) == FtpListOption.Recursive && RecursiveList; bool isDerefLinks = (options & FtpListOption.DerefLinks) == FtpListOption.DerefLinks; bool isGetModified = (options & FtpListOption.Modify) == FtpListOption.Modify; bool isGetSize = (options & FtpListOption.Size) == FtpListOption.Size; // calc path to request path = GetAbsolutePath(path); // MLSD provides a machine readable format with 100% accurate information // so always prefer MLSD over LIST unless the caller of this method overrides it with the ForceList option bool machineList = false; if ((!isForceList || m_parser == FtpParser.Machine) && HasFeature(FtpCapability.MLSD)) { listcmd = "MLSD"; machineList = true; } else { if (isUseLS) { listcmd = "LS"; } else if (isNameList) { listcmd = "NLST"; } else { string listopts = ""; listcmd = "LIST"; if (isAllFiles) { listopts += "a"; } if (isRecursive) { listopts += "R"; } if (listopts.Length > 0) { listcmd += " -" + listopts; } } } if (!isNoPath) { listcmd = (listcmd + " " + path.GetFtpPath()); } #if !CORE14 lock (m_lock) { #endif Execute("TYPE I"); // read in raw file listing using (FtpDataStream stream = OpenDataStream(listcmd, 0)) { try { FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); while ((buf = stream.ReadLine(Encoding)) != null) { if (buf.Length > 0) { rawlisting.Add(buf); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + buf); } } FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } finally { stream.Close(); } } #if !CORE14 } #endif for (int i = 0; i < rawlisting.Count; i++) { buf = rawlisting[i]; if (isNameList) { // if NLST was used we only have a file name so // there is nothing to parse. item = new FtpListItem() { FullName = buf }; if (DirectoryExists(item.FullName)) { item.Type = FtpFileSystemObjectType.Directory; } else { item.Type = FtpFileSystemObjectType.File; } lst.Add(item); } else { // if this is a result of LIST -R then the path will be spit out // before each block of objects if (listcmd.StartsWith("LIST") && isRecursive) { if (buf.StartsWith("/") && buf.EndsWith(":")) { path = buf.TrimEnd(':'); continue; } } // if the next line in the listing starts with spaces // it is assumed to be a continuation of the current line if (i + 1 < rawlisting.Count && (rawlisting[i + 1].StartsWith("\t") || rawlisting[i + 1].StartsWith(" "))) { buf += rawlisting[++i]; } item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList); // FtpListItem.Parse() returns null if the line // could not be parsed if (item != null) { if (isIncludeSelf || !(item.Name == "." || item.Name == "..")) { lst.Add(item); } else { //FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Skipped self or parent item: " + item.Name); } } else { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse file listing: " + buf); } } // load extended information that wasn't available if the list options flags say to do so. if (item != null) { // try to dereference symbolic links if the appropriate list // option was passed if (item.Type == FtpFileSystemObjectType.Link && isDerefLinks) { item.LinkObject = DereferenceLink(item); } // if need to get file modified date if (isGetModified && HasFeature(FtpCapability.MDTM)) { // if the modified date was not loaded or the modified date is more than a day in the future // and the server supports the MDTM command, load the modified date. // most servers do not support retrieving the modified date // of a directory but we try any way. if (item.Modified == DateTime.MinValue || listcmd.StartsWith("LIST")) { DateTime modify; if (item.Type == FtpFileSystemObjectType.Directory) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Trying to retrieve modification time of a directory, some servers don't like this..."); } if ((modify = GetModifiedTime(item.FullName)) != DateTime.MinValue) { item.Modified = modify; } } } // if need to get file size if (isGetSize && HasFeature(FtpCapability.SIZE)) { // if no size was parsed, the object is a file and the server // supports the SIZE command, then load the file size if (item.Size == -1) { if (item.Type != FtpFileSystemObjectType.Directory) { item.Size = GetFileSize(item.FullName); } else { item.Size = 0; } } } } } return(lst.ToArray()); }
/// <summary> /// Connect to the specified host /// </summary> /// <param name="host">The host to connect to</param> /// <param name="port">The port to connect to</param> /// <param name="ipVersions">Internet Protocol versions to support durring the connection phase</param> public void Connect(string host, int port, FtpIpVersion ipVersions) { IAsyncResult ar = null; #if CORE IPAddress[] addresses = Dns.GetHostAddressesAsync(host).Result; #else IPAddress[] addresses = Dns.GetHostAddresses(host); #endif if (ipVersions == 0) { throw new ArgumentException("The ipVersions parameter must contain at least 1 flag."); } for (int i = 0; i < addresses.Length; i++) { #if DEBUG FtpTrace.WriteLine("{0}: {1}", addresses[i].AddressFamily.ToString(), addresses[i].ToString()); #endif // we don't need to do this check unless // a particular version of IP has been // omitted so we won't. if (ipVersions != FtpIpVersion.ANY) { switch (addresses[i].AddressFamily) { case AddressFamily.InterNetwork: if ((ipVersions & FtpIpVersion.IPv4) != FtpIpVersion.IPv4) { #if DEBUG FtpTrace.WriteLine("SKIPPED!"); #endif continue; } break; case AddressFamily.InterNetworkV6: if ((ipVersions & FtpIpVersion.IPv6) != FtpIpVersion.IPv6) { #if DEBUG FtpTrace.WriteLine("SKIPPED!"); #endif continue; } break; } } m_socket = new Socket(addresses[i].AddressFamily, SocketType.Stream, ProtocolType.Tcp); #if CORE m_socket.ConnectAsync(addresses[i], port).Wait(); #else ar = m_socket.BeginConnect(addresses[i], port, null, null); if (!ar.AsyncWaitHandle.WaitOne(m_connectTimeout, true)) { Close(); // check to see if we're out of addresses // and if we are throw a TimeoutException if (i + 1 == addresses.Length) { throw new TimeoutException("Timed out trying to connect!"); } } else { m_socket.EndConnect(ar); // we got a connection, break out // of the loop. break; } #endif } // make sure that we actually connected to // one of the addresses returned from GetHostAddresses() if (m_socket == null || !m_socket.Connected) { Close(); throw new IOException("Failed to connect to host."); } m_netStream = new NetworkStream(m_socket); m_lastActivity = DateTime.Now; }
/// <summary> /// Disposes the stream /// </summary> public new void Dispose() { FtpTrace.WriteLine("Disposing FtpSocketStream..."); Close(); }
/// <summary> /// Disposes the stream /// </summary> public new void Dispose() { FtpTrace.WriteLine(FtpTraceLevel.Debug, "Disposing FtpSocketStream..."); Close(); }