/// <summary> /// Calculates the permissions flags from the CHMOD value /// </summary> public static void CalculateUnixPermissions(this FtpListItem item, string permissions) { var perms = Regex.Match(permissions, @"[\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; } } CalculateChmod(item); } }
/// <summary> /// Get the full path of a given FTP Listing entry /// </summary> public static void CalculateFullFtpPath(this FtpListItem item, FtpClient client, string path, bool isVMS) { // EXIT IF NO DIR PATH PROVIDED if (path == null) { // check if the path is absolute if (IsAbsolutePath(item.Name)) { item.FullName = item.Name; item.Name = item.Name.GetFtpFileName(); } return; } // ONLY IF DIR PATH PROVIDED // if this is a vax/openvms file listing // there are no slashes in the path name if (isVMS) { item.FullName = path + item.Name; } else { //this.client.LogStatus(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 (IsAbsolutePath(item.Name)) { item.FullName = item.Name; item.Name = item.Name.GetFtpFileName(); } else if (path != null) { item.FullName = path.GetFtpPath(item.Name); //.GetFtpPathWithoutGlob(); } else { client.LogStatus(FtpTraceLevel.Warn, "Couldn't determine the full path of this object: " + 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)).Trim(); } else { item.LinkTarget = path.GetFtpPath(item.LinkTarget).Trim(); } } } }
/// <summary> /// Calculates the CHMOD value from the permissions flags /// </summary> public static void CalculateChmod(this FtpListItem item) { item.Chmod = CalcChmod(item.OwnerPermissions, item.GroupPermissions, item.OthersPermissions); }
/// <summary> /// Returns true if the file passes all the rules /// </summary> private bool FilePassesRules(FtpResult result, List <FtpRule> rules, bool useLocalPath, FtpListItem item = null) { if (rules != null && rules.Count > 0) { var passes = FtpRule.IsAllAllowed(rules, item ?? result.ToListItem(useLocalPath)); if (!passes) { LogStatus(FtpTraceLevel.Info, "Skipped file due to rule: " + (useLocalPath ? result.LocalPath : result.RemotePath)); // mark that the file was skipped due to a rule result.IsSkipped = true; result.IsSkippedByRule = true; // skip uploading the file return(false); } } return(true); }
/// <summary> /// Gets a file listing from the server asynchronously. 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 to list</param> /// <param name="options">Options that dictate how the list operation is performed</param> /// <returns>An array of items retrieved in the listing</returns> public async Task <FtpListItem[]> GetListingAsync(string path, FtpListOption options) { //TODO: Add cancellation support FtpTrace.WriteFunc(nameof(GetListingAsync), 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 = await GetAbsolutePathAsync(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()); } await ExecuteAsync("TYPE I"); // read in raw file listing using (FtpDataStream stream = await OpenDataStreamAsync(listcmd, 0)) { try { FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); if (this.BulkListing) { // increases performance of GetListing by reading multiple lines of the file listing at once foreach (var line in await stream.ReadAllLinesAsync(Encoding, this.BulkListingLength)) { if (!FtpExtensions.IsNullOrWhiteSpace(line)) { rawlisting.Add(line); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + line); } } } else { // GetListing will read file listings line-by-line (actually byte-by-byte) while ((buf = await stream.ReadLineAsync(Encoding)) != null) { if (buf.Length > 0) { rawlisting.Add(buf); FtpTrace.WriteLine(FtpTraceLevel.Verbose, "Listing: " + buf); } } } FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } finally { stream.Close(); } } 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 (await DirectoryExistsAsync(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]; } try { item = m_listParser.ParseSingleLine(path, buf, m_caps, machineList); } catch (FtpListParser.CriticalListParseException) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Restarting parsing from first entry in list"); i = -1; lst.Clear(); continue; } // 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 = await DereferenceLinkAsync(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 = await GetModifiedTimeAsync(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 = await GetFileSizeAsync(item.FullName); } else { item.Size = 0; } } } } } return(lst.ToArray()); }
/// <summary> /// Returns information about a file system object. Returns null if the server response can't /// be parsed or the server returns a failure completion code. The error for a failure /// is logged with FtpTrace. No exception is thrown on error because that would negate /// the usefulness of this method for checking for the existence of an object. /// </summary> /// <param name="path">The path of the file or folder</param> /// <param name="dateModified">Get the accurate modified date using another MDTM command</param> /// <returns>A FtpListItem object</returns> public FtpListItem GetObjectInfo(string path, bool dateModified = false) { // verify args if (path.IsBlank()) { throw new ArgumentException("Required parameter is null or blank.", "path"); } FtpTrace.WriteFunc("GetObjectInfo", new object[] { path, dateModified }); FtpReply reply; string[] res; bool supportsMachineList = (Capabilities & FtpCapability.MLSD) == FtpCapability.MLSD; FtpListItem result = null; if (supportsMachineList) { // USE MACHINE LISTING TO GET INFO FOR A SINGLE FILE if ((reply = Execute("MLST " + path)).Success) { res = reply.InfoMessages.Split('\n'); if (res.Length > 1) { string info = ""; for (int i = 1; i < res.Length; i++) { info += res[i]; } result = m_listParser.ParseSingleLine(null, info, m_caps, true); } } else { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " with error " + reply.ErrorMessage); } } else { // USE GETLISTING TO GET ALL FILES IN DIR .. SLOWER BUT AT LEAST IT WORKS string dirPath = path.GetFtpDirectoryName(); FtpListItem[] dirItems = GetListing(dirPath); foreach (var dirItem in dirItems) { if (dirItem.FullName == path) { result = dirItem; break; } } FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to get object info for path " + path + " since MLST not supported and GetListing() fails to list file/folder."); } // Get the accurate date modified using another MDTM command if (result != null && dateModified && HasFeature(FtpCapability.MDTM)) { result.Modified = GetModifiedTime(path); } return(result); }
/// <summary> /// Retrieve the permissions of the given file/folder as an integer in the CHMOD format. /// Throws FtpCommandException if there is an issue. /// Returns 0 if the server did not specify a permission value. /// Use `GetFilePermissions` if you required the permissions in the FtpPermission format. /// </summary> /// <param name="path">The full or relative path to the item</param> /// <param name="token">The token that can be used to cancel the entire process</param> public async Task <int> GetChmodAsync(string path, CancellationToken token = default(CancellationToken)) { FtpListItem item = await GetFilePermissionsAsync(path, token); return(item != null ? item.Chmod : 0); }
/// <summary> /// Parse raw file from server into a file object, using the currently active parser. /// </summary> public FtpListItem ParseSingleLine(string path, string file, List <FtpCapability> caps, bool isMachineList) { FtpListItem result = null; // force machine listing if it is if (isMachineList) { result = FtpMachineListParser.Parse(file, caps, client); } else { // use custom parser if given if (m_customParser != null) { result = m_customParser(file, caps, client); } else { if (IsWrongParser()) { ValidateParser(new[] { file }); } // use one of the in-built parsers switch (CurrentParser) { case FtpParser.Legacy: result = ParseLegacy(path, file, caps, client); break; case FtpParser.Machine: result = FtpMachineListParser.Parse(file, caps, client); break; case FtpParser.Windows: result = FtpWindowsParser.Parse(client, file); break; case FtpParser.Unix: result = FtpUnixParser.Parse(client, file); break; case FtpParser.UnixAlt: result = FtpUnixParser.ParseUnixAlt(client, file); break; case FtpParser.VMS: result = FtpVMSParser.Parse(client, file); break; case FtpParser.IBM: result = FtpIBMParser.Parse(client, file); break; case FtpParser.NonStop: result = FtpNonStopParser.Parse(client, file); break; } } } // if parsed file successfully if (result != null) { // apply time difference between server/client if (HasTimeOffset) { result.Modified = result.Modified - TimeOffset; } // calc absolute file paths result.CalculateFullFtpPath(client, path, false); } return(result); }