/// <summary> /// Creates <see cref="IHierarchyItemAsync"/> instance by path. /// </summary> /// <param name="path">Item relative path including query string.</param> /// <returns>Instance of corresponding <see cref="IHierarchyItemAsync"/> or null if item is not found.</returns> public override async Task <IHierarchyItemAsync> GetHierarchyItemAsync(string path) { path = path.Trim(new[] { ' ', '/' }); //remove query string. int ind = path.IndexOf('?'); if (ind > -1) { path = path.Remove(ind); } IHierarchyItemAsync item = null; item = await DavFolder.GetFolderAsync(this, path); if (item != null) { return(item); } item = await DavFile.GetFileAsync(this, path); if (item != null) { return(item); } Logger.LogDebug("Could not find item that corresponds to path: " + path); return(null); // no hierarchy item that corresponds to path parameter was found in the repository }
/// <summary> /// Handles GET and HEAD request. /// </summary> /// <param name="context">Instace of <see cref="DavContextBaseAsync"/>.</param> /// <param name="item">Instance of <see cref="IHierarchyItemAsync"/> which was returned by /// <see cref="DavContextBaseAsync.GetHierarchyItemAsync"/> for this request.</param> public async Task ProcessRequestAsync(DavContextBaseAsync context, IHierarchyItemAsync item) { string urlPath = context.Request.RawUrl.Substring(context.Request.ApplicationPath.TrimEnd('/').Length); if (item is IItemCollectionAsync) { // In case of GET requests to WebDAV folders we serve a web page to display // any information about this server and how to use it. // Remember to call EnsureBeforeResponseWasCalledAsync here if your context implementation // makes some useful things in BeforeResponseAsync. await context.EnsureBeforeResponseWasCalledAsync(); string htmlName = "MyCustomHandlerPage.html"; using (TextReader reader = File.OpenText(Path.Combine(htmlPath, htmlName))) { string html = await reader.ReadToEndAsync(); html = html.Replace("_webDavServerRoot_", context.Request.ApplicationPath.TrimEnd('/')); html = html.Replace("_webDavServerVersion_", typeof(DavEngineAsync).GetTypeInfo().Assembly.GetName().Version.ToString()); await WriteFileContentAsync(context, html, htmlName); } } else { await OriginalHandler.ProcessRequestAsync(context, item); } }
/// <summary> /// Called when this folder is being moved or renamed. /// </summary> /// <param name="destFolder">Destination folder.</param> /// <param name="destName">New name of this folder.</param> /// <param name="multistatus">Information about child items that failed to move.</param> public override async Task MoveToAsync(IItemCollectionAsync destFolder, string destName, MultistatusException multistatus) { // in this function we move item by item, because we want to check if each item is not locked. if (!(destFolder is DavFolder)) { throw new DavException("Target folder doesn't exist", DavStatus.CONFLICT); } DavFolder targetFolder = (DavFolder)destFolder; if (IsRecursive(targetFolder)) { throw new DavException("Cannot move folder to its subtree.", DavStatus.FORBIDDEN); } string newDirPath = System.IO.Path.Combine(targetFolder.FullPath, destName); string targetPath = targetFolder.Path + EncodeUtil.EncodeUrlPart(destName); try { // Remove item with the same name at destination if it exists. IHierarchyItemAsync item = await context.GetHierarchyItemAsync(targetPath); if (item != null) { await item.DeleteAsync(multistatus); } await targetFolder.CreateFolderAsync(destName); } catch (DavException ex) { // Continue the operation but report error with destination path to client. multistatus.AddInnerException(targetPath, ex); return; } // Move child items. bool movedSuccessfully = true; IFolderAsync createdFolder = (IFolderAsync)await context.GetHierarchyItemAsync(targetPath); foreach (DavHierarchyItem item in (await GetChildrenAsync(new PropertyName[0], null, null, new List <OrderProperty>())).Page) { try { await item.MoveToAsync(createdFolder, item.Name, multistatus); } catch (DavException ex) { // Continue the operation but report error with child item to client. multistatus.AddInnerException(item.Path, ex); movedSuccessfully = false; } } if (movedSuccessfully) { await DeleteAsync(multistatus); } }
/// <summary> /// Performs file system operation with translating exceptions to those expected by WebDAV engine. /// </summary> /// <param name="item">Item on which operation is performed.</param> /// <param name="func">The action to be performed.</param> /// <param name="privilege">Privilege which is needed to perform the operation. /// If <see cref="UnauthorizedAccessException"/> is thrown this method will convert it to /// <see cref="NeedPrivilegesException"/> exception and specify this privilege in it.</param> /// <typeparam name="T">Type of operation's result.</typeparam> /// <returns>Result returned by <paramref name="func"/>.</returns> public T FileOperation <T>(IHierarchyItemAsync item, Func <T> func, Privilege privilege) { try { using (impersonate()) { return(func()); } } catch (UnauthorizedAccessException) { NeedPrivilegesException ex = new NeedPrivilegesException("Not enough privileges"); ex.AddRequiredPrivilege(item.Path, privilege); throw ex; } catch (IOException ex) { int hr = Marshal.GetHRForException(ex); if (hr == ERROR_DISK_FULL) { throw new InsufficientStorageException(); } throw new DavException(ex.Message, DavStatus.CONFLICT); } }
/// <summary> /// Handles GET and HEAD request. /// </summary> /// <param name="context">Instace of <see cref="DavContextBaseAsync"/>.</param> /// <param name="item">Instance of <see cref="IHierarchyItemAsync"/> which was returned by /// <see cref="DavContextBaseAsync.GetHierarchyItemAsync"/> for this request.</param> public async Task ProcessRequestAsync(DavContextBaseAsync context, IHierarchyItemAsync item) { string urlPath = context.Request.RawUrl.Substring(context.Request.ApplicationPath.TrimEnd('/').Length); if (item is IItemCollectionAsync) { // In case of GET requests to WebDAV folders we serve a web page to display // any information about this server and how to use it. // Remember to call EnsureBeforeResponseWasCalledAsync here if your context implementation // makes some useful things in BeforeResponseAsync. await context.EnsureBeforeResponseWasCalledAsync(); IHttpAsyncHandler page = (IHttpAsyncHandler)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath( "~/MyCustomHandlerPage.aspx", typeof(MyCustomHandlerPage)); if (Type.GetType("Mono.Runtime") != null) { page.ProcessRequest(HttpContext.Current); } else { // Here we call BeginProcessRequest instead of ProcessRequest to start an async page execution and be able to call RegisterAsyncTask if required. // To call APM method (Begin/End) from TAP method (Task/async/await) the Task.FromAsync must be used. await Task.Factory.FromAsync(page.BeginProcessRequest, page.EndProcessRequest, HttpContext.Current, null); } } else { await OriginalHandler.ProcessRequestAsync(context, item); } }
/// <summary> /// Called when children of this folder are being listed. /// </summary> /// <param name="propNames">List of properties to retrieve with the children. They will be queried by the engine later.</param> /// <returns>Children of this folder.</returns> public virtual async Task <IEnumerable <IHierarchyItemAsync> > GetChildrenAsync(IList <PropertyName> propNames) { // Enumerates all child files and folders. // You can filter children items in this implementation and // return only items that you want to be visible for this // particular user. IList <IHierarchyItemAsync> children = new List <IHierarchyItemAsync>(); FileSystemInfo[] fileInfos = null; fileInfos = dirInfo.GetFileSystemInfos(); foreach (FileSystemInfo fileInfo in fileInfos) { string childPath = Path + EncodeUtil.EncodeUrlPart(fileInfo.Name); IHierarchyItemAsync child = await context.GetHierarchyItemAsync(childPath); if (child != null) { children.Add(child); } } return(children); }
/// <summary> /// Copies this folder to another folder with option to rename it. /// </summary> /// <param name="destFolder">Folder to copy this folder to.</param> /// <param name="destName">New name of this folder.</param> /// <param name="deep">Whether children shall be copied.</param> /// <param name="multistatus">Container for errors. We put here errors which occur with /// individual items being copied.</param> public override async Task CopyToAsync( IItemCollectionAsync destFolder, string destName, bool deep, MultistatusException multistatus) { DavFolder destDavFolder = destFolder as DavFolder; if (destFolder == null) { throw new DavException("Destination folder doesn't exist", DavStatus.CONFLICT); } if (!await destDavFolder.ClientHasTokenAsync()) { throw new LockedException("Doesn't have token for destination folder."); } if (isRecursive(destDavFolder)) { throw new DavException("Cannot copy folder to its subtree", DavStatus.FORBIDDEN); } IHierarchyItemAsync destItem = await destDavFolder.FindChildAsync(destName); if (destItem != null) { try { await destItem.DeleteAsync(multistatus); } catch (DavException ex) { multistatus.AddInnerException(destItem.Path, ex); return; } } DavFolder newDestFolder = await CopyThisItemAsync(destDavFolder, null, destName); // copy children if (deep) { foreach (IHierarchyItemAsync child in (await GetChildrenAsync(new PropertyName[0], null, null, null)).Page) { var dbchild = child as DavHierarchyItem; try { await dbchild.CopyToAsync(newDestFolder, child.Name, deep, multistatus); } catch (DavException ex) { multistatus.AddInnerException(dbchild.Path, ex); } } } await Context.socketService.NotifyRefreshAsync(newDestFolder.Path); }
/// <summary> /// Called when this file is being moved or renamed. /// </summary> /// <param name="destFolder">Destination folder.</param> /// <param name="destName">New name of this file.</param> /// <param name="multistatus">Information about items that failed to move.</param> public override async Task MoveToAsync(IItemCollectionAsync destFolder, string destName, MultistatusException multistatus) { DavFolder targetFolder = (DavFolder)destFolder; if (targetFolder == null || !Directory.Exists(targetFolder.FullPath)) { throw new DavException("Target directory doesn't exist", DavStatus.CONFLICT); } string newDirPath = System.IO.Path.Combine(targetFolder.FullPath, destName); string targetPath = targetFolder.Path + EncodeUtil.EncodeUrlPart(destName); // If an item with the same name exists in target directory - remove it. try { IHierarchyItemAsync item = await context.GetHierarchyItemAsync(targetPath); if (item != null) { await item.DeleteAsync(multistatus); } } catch (DavException ex) { // Report exception to client and continue with other items by returning from recursion. multistatus.AddInnerException(targetPath, ex); return; } // Move the file. try { File.Move(fileSystemInfo.FullName, newDirPath); var newFileInfo = new FileInfo(newDirPath); if (FileSystemInfoExtension.IsUsingFileSystemAttribute) { await fileSystemInfo.MoveExtendedAttributes(newFileInfo); } // Locks should not be copied, delete them. if (await newFileInfo.HasExtendedAttributeAsync("Locks")) { await newFileInfo.DeleteExtendedAttributeAsync("Locks"); } } catch (UnauthorizedAccessException) { // Exception occurred with the item for which MoveTo was called - fail the operation. NeedPrivilegesException ex = new NeedPrivilegesException("Not enough privileges"); ex.AddRequiredPrivilege(targetPath, Privilege.Bind); string parentPath = System.IO.Path.GetDirectoryName(Path); ex.AddRequiredPrivilege(parentPath, Privilege.Unbind); throw ex; } }
/// <summary> /// Called when this file is being copied. /// </summary> /// <param name="destFolder">Destination folder.</param> /// <param name="destName">New file name.</param> /// <param name="deep">Whether children items shall be copied. Ignored for files.</param> /// <param name="multistatus">Information about items that failed to copy.</param> public override async Task CopyToAsync(IItemCollectionAsync destFolder, string destName, bool deep, MultistatusException multistatus) { DavFolder targetFolder = (DavFolder)destFolder; if (targetFolder == null || !Directory.Exists(targetFolder.FullPath)) { throw new DavException("Target directory doesn't exist", DavStatus.CONFLICT); } string newFilePath = System.IO.Path.Combine(targetFolder.FullPath, destName); string targetPath = targetFolder.Path + EncodeUtil.EncodeUrlPart(destName); // If an item with the same name exists - remove it. try { IHierarchyItemAsync item = await context.GetHierarchyItemAsync(targetPath); if (item != null) { await item.DeleteAsync(multistatus); } } catch (DavException ex) { // Report error with other item to client. multistatus.AddInnerException(targetPath, ex); return; } // Copy the file togather with all extended attributes (custom props and locks). try { File.Copy(fileSystemInfo.FullName, newFilePath); var newFileSystemInfo = new FileInfo(newFilePath); if (FileSystemInfoExtension.IsUsingFileSystemAttribute) { await fileSystemInfo.CopyExtendedAttributes(newFileSystemInfo); } // Locks should not be copied, delete them. if (await fileSystemInfo.HasExtendedAttributeAsync("Locks")) { await newFileSystemInfo.DeleteExtendedAttributeAsync("Locks"); } } catch (UnauthorizedAccessException) { // Fail NeedPrivilegesException ex = new NeedPrivilegesException("Not enough privileges"); string parentPath = System.IO.Path.GetDirectoryName(Path); ex.AddRequiredPrivilege(parentPath, Privilege.Bind); throw ex; } await context.socketService.NotifyRefreshAsync(targetFolder.Path); }
/// <summary> /// Creates <see cref="IHierarchyItemAsync"/> instance by path. /// </summary> /// <param name="path">Item relative path including query string.</param> /// <returns>Instance of corresponding <see cref="IHierarchyItemAsync"/> or null if item is not found.</returns> public override async Task <IHierarchyItemAsync> GetHierarchyItemAsync(string path) { path = path.Trim(new[] { ' ', '/' }); //remove query string. int ind = path.IndexOf('?'); if (ind > -1) { path = path.Remove(ind); } IHierarchyItemAsync item = null; // Return items from [DAVLocation]/acl/ folder and subfolders. item = await AclFactory.GetAclItemAsync(this, path); if (item != null) { return(item); } // Return items from [DAVLocation]/calendars/ folder and subfolders. item = CalDavFactory.GetCalDavItem(this, path); if (item != null) { return(item); } // Return folder that corresponds to [DAVLocation] path. If no DavLocation is defined in config file this is a website root. item = DavLocationFolder.GetDavLocationFolder(this, path); if (item != null) { return(item); } item = await DavFolder.GetFolderAsync(this, path); if (item != null) { return(item); } item = await DavFile.GetFileAsync(this, path); if (item != null) { return(item); } Logger.LogDebug("Could not find item that corresponds to path: " + path); return(null); // no hierarchy item that corresponds to path parameter was found in the repository }
/// <summary> /// Resolves path to instance of <see cref="IHierarchyItem"/>. /// This method is called by WebDAV engine to resolve paths it encounters /// in request. /// </summary> /// <param name="path">Relative path to the item including query string.</param> /// <returns><see cref="IHierarchyItem"/> instance if item is found, <c>null</c> otherwise.</returns> public override async Task <IHierarchyItemBaseAsync> GetHierarchyItemAsync(string path) { path = path.Trim(new[] { ' ', '/' }); //remove query string. int ind = path.IndexOf('?'); if (ind > -1) { path = path.Remove(ind); } IHierarchyItemAsync item = null; // Return items from [DAVLocation]/acl/ folder and subfolders. item = await AclFactory.GetAclItemAsync(this, path); if (item != null) { return(item); } // Return items from [DAVLocation]/addressbooks/ folder and subfolders. item = await CardDavFactory.GetCardDavItemAsync(this, path); if (item != null) { return(item); } // Return folder that corresponds to [DAVLocation] path. If no DavLocation section is defined in web.config/app.config [DAVLocation] is a website root. string davLocation = DavLocationFolder.DavLocationFolderPath.Trim('/'); if (davLocation.Equals(path, StringComparison.InvariantCultureIgnoreCase)) { return(new DavLocationFolder(this)); } // Return any folders above [DAVLocation] path. // Root folder with ICalendarDiscovery/IAddressbookDiscovery implementation is required for calendars and address books discovery by CalDAV/CardDAV clients. // All other folders are returned just for folders structure browsing convenience using WebDAV clients during dev time, not required by CalDAV/CardDAV clients. if (davLocation.StartsWith(path, StringComparison.InvariantCultureIgnoreCase)) { int childFolderPathLength = (davLocation + "/").IndexOf('/', path.Length + 1); string childFolderPath = davLocation.Substring(0, childFolderPathLength); return(new LogicalFolder(this, path, new [] { new LogicalFolder(this, childFolderPath) })); } Logger.LogDebug("Could not find item that corresponds to path: " + path); return(null); // no hierarchy item that corresponds to path parameter was found in the repository }
/// <summary>Initializes a new instance of the <see cref="ItemMetadata"/> class.</summary> /// <param name="identifier">The identifier.</param> /// <param name="parentIdentifier">The parent identifier.</param> /// <param name="name">The name.</param> /// <param name="localItem">The local item.</param> /// <param name="serverItem">The server item.</param> protected ItemMetadata( string identifier, string parentIdentifier, string name, LocalItem localItem, IHierarchyItemAsync serverItem = null) { this.Identifier = identifier; this.ParentIdentifier = parentIdentifier; this.Name = name; this.LocalItem = localItem; this.ServerItem = serverItem; }
/// <summary> /// Writes iOS / OS X CalDAV/CardDAV profile. /// </summary> /// <param name="context">Instace of <see cref="DavContextBaseAsync"/>.</param> /// <param name="item">ICalendarFolderAsync or IAddressbookFolderAsync item.</param> /// <returns></returns> private async Task WriteProfileAsync(DavContextBaseAsync context, IHierarchyItemAsync item, string htmlPath) { string mobileconfigFileName = null; string decription = null; if (item is ICalendarFolderAsync) { mobileconfigFileName = "CalDAV.AppleProfileTemplete.mobileconfig"; decription = (item as ICalendarFolderAsync).CalendarDescription; } decription = !string.IsNullOrEmpty(decription) ? decription : item.Name; string templateContent = null; using (TextReader reader = new StreamReader(Path.Combine(htmlPath, mobileconfigFileName))) { templateContent = await reader.ReadToEndAsync(); } Uri url = new Uri(context.Request.UrlPrefix); string payloadUUID = item.Path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Last(); // PayloadUUID string profile = string.Format(templateContent , url.Host // host name , item.Path // CalDAV / CardDAV Principal URL. Here we can return (await (item as ICurrentUserPrincipalAsync).GetCurrentUserPrincipalAsync()).Path if needed. , (context as DavContext).UserName // user name , url.Port // port , (url.Scheme == "https").ToString().ToLower() // SSL , decription // CardDAV / CardDAV Account Description , Assembly.GetAssembly(this.GetType()).GetName().Version.ToString() , Assembly.GetAssembly(typeof(DavEngineAsync)).GetName().Version.ToString() , payloadUUID ); byte[] profileBytes = SignProfile(context, profile); context.Response.ContentType = "application/x-apple-aspen-config"; context.Response.AddHeader("Content-Disposition", "attachment; filename=profile.mobileconfig"); context.Response.ContentLength = profileBytes.Length; using (BinaryWriter writer = new BinaryWriter(context.Response.OutputStream)) { writer.Write(profileBytes); } }
/// <summary> /// Handles GET and HEAD request. /// </summary> /// <param name="context">Instace of <see cref="DavContextBaseAsync"/>.</param> /// <param name="item">Instance of <see cref="IHierarchyItemAsync"/> which was returned by /// <see cref="DavContextBaseAsync.GetHierarchyItemAsync"/> for this request.</param> public async Task ProcessRequestAsync(DavContextBaseAsync context, IHierarchyItemAsync item) { if (item is IItemCollectionAsync) { // In case of GET requests to WebDAV folders we serve a web page to display // any information about this server and how to use it. // Remember to call EnsureBeforeResponseWasCalledAsync here if your context implementation // makes some useful things in BeforeResponseAsync. await context.EnsureBeforeResponseWasCalledAsync(); string htmlName = "MyCustomHandlerPage.html"; string html = await getFileContent(htmlName); html = html.Replace("_webDavServerRoot_", context.Request.ApplicationPath.TrimEnd('/')); html = html.Replace("_webDavServerVersion_", typeof(DavEngineAsync).GetTypeInfo().Assembly.GetName().Version.ToString()); await WriteFileContentAsync(context, html, htmlName); } else if (context.Request.RawUrl.StartsWith("/wwwroot/")) { // The "/wwwroot/" is not a WebDAV folder. It can be used to store client script files, // images, static HTML files or any other files that does not require access via WebDAV. // Any request to the files in this folder will just serve them to client. await context.EnsureBeforeResponseWasCalledAsync(); string relativeFilePath = context.Request.RawUrl.TrimStart('/').Replace('/', Path.DirectorySeparatorChar); // Remove query string. int queryIndex = relativeFilePath.LastIndexOf('?'); if (queryIndex > -1) { relativeFilePath = relativeFilePath.Remove(queryIndex); } await WriteFileContentAsync(context, await getFileContent(relativeFilePath), relativeFilePath); } else { await OriginalHandler.ProcessRequestAsync(context, item); } }
protected static async Task FindLocksDownAsync(IHierarchyItemAsync root, bool skipShared) { IFolderAsync folder = root as IFolderAsync; if (folder != null) { foreach (IHierarchyItemAsync child in (await folder.GetChildrenAsync(new PropertyName[0], null, null, null)).Page) { DavHierarchyItem dbchild = child as DavHierarchyItem; if (await dbchild.ItemHasLockAsync(skipShared)) { MultistatusException mex = new MultistatusException(); mex.AddInnerException(dbchild.Path, new LockedException()); throw mex; } await FindLocksDownAsync(child, skipShared); } } }
/// <summary> /// Called when children of this folder with paging information are being listed. /// </summary> /// <param name="propNames">List of properties to retrieve with the children. They will be queried by the engine later.</param> /// <param name="offset">The number of children to skip before returning the remaining items. Start listing from from next item.</param> /// <param name="nResults">The number of items to return.</param> /// <param name="orderProps">List of order properties requested by the client.</param> /// <returns>Items requested by the client and a total number of items in this folder.</returns> public virtual async Task <PageResults> GetChildrenAsync(IList <PropertyName> propNames, long?offset, long?nResults, IList <OrderProperty> orderProps) { // Enumerates all child files and folders. // You can filter children items in this implementation and // return only items that you want to be visible for this // particular user. IList <IHierarchyItemAsync> children = new List <IHierarchyItemAsync>(); FileSystemInfo[] fileInfos = null; long totalItems = 0; fileInfos = dirInfo.GetFileSystemInfos(); totalItems = fileInfos.Length; // Apply sorting. fileInfos = SortChildren(fileInfos, orderProps); // Apply paging. if (offset.HasValue && nResults.HasValue) { fileInfos = fileInfos.Skip((int)offset.Value).Take((int)nResults.Value).ToArray(); } foreach (FileSystemInfo fileInfo in fileInfos) { string childPath = Path + EncodeUtil.EncodeUrlPart(fileInfo.Name); IHierarchyItemAsync child = await context.GetHierarchyItemAsync(childPath); if (child != null) { children.Add(child); } } return(new PageResults(children, totalItems)); }
/// <summary> /// Gets CalDAV items. /// </summary> /// <param name="path">Relative path requested.</param> /// <param name="context">Instance of <see cref="DavContext"/> class.</param> /// <returns>Object implementing various CalDAV items or null if no object corresponding to path is found.</returns> internal static IHierarchyItemAsync GetCalDavItem(DavContext context, string path) { IHierarchyItemAsync item = null; item = CalendarsRootFolder.GetCalendarsRootFolder(context, path); if (item != null) { return(item); } item = CalendarFolder.GetCalendarFolder(context, path); if (item != null) { return(item); } item = CalendarFile.GetCalendarFile(context, path); if (item != null) { return(item); } return(null); }
/// <summary> /// Gets CardDAV items. /// </summary> /// <param name="path">Relative path requested.</param> /// <param name="context">Instance of <see cref="DavContext"/> class.</param> /// <returns>Object implementing various CardDAV items or null if no object corresponding to path is found.</returns> internal static IHierarchyItemAsync GetCardDavItem(DavContext context, string path) { IHierarchyItemAsync item = null; item = AddressbooksRootFolder.GetAddressbooksRootFolder(context, path); if (item != null) { return(item); } item = AddressbookFolder.GetAddressbookFolder(context, path); if (item != null) { return(item); } item = CardFile.GetCardFile(context, path); if (item != null) { return(item); } return(null); }
/// <summary> /// Handles GET and HEAD request. /// </summary> /// <param name="context">Instace of <see cref="DavContextBaseAsync"/>.</param> /// <param name="item">Instance of <see cref="IHierarchyItemAsync"/> which was returned by /// <see cref="DavContextBaseAsync.GetHierarchyItemAsync"/> for this request.</param> public async Task ProcessRequestAsync(DavContextBaseAsync context, IHierarchyItemAsync item) { string urlPath = context.Request.RawUrl.Substring(context.Request.ApplicationPath.TrimEnd('/').Length); if (item is IItemCollectionAsync) { // In case of GET requests to WebDAV folders we serve a web page to display // any information about this server and how to use it. // Remember to call EnsureBeforeResponseWasCalledAsync here if your context implementation // makes some useful things in BeforeResponseAsync. await context.EnsureBeforeResponseWasCalledAsync(); string htmlName = "MyCustomHandlerPage.html"; using (TextReader reader = File.OpenText(Path.Combine(htmlPath, htmlName))) { string html = await reader.ReadToEndAsync(); html = html.Replace("_webDavServerRoot_", context.Request.ApplicationPath.TrimEnd('/')); html = html.Replace("_webDavServerVersion_", typeof(DavEngineAsync).GetTypeInfo().Assembly.GetName().Version.ToString()); await WriteFileContentAsync(context, html, htmlName); } } else if (urlPath.StartsWith("/AjaxFileBrowser/") || urlPath.StartsWith("/wwwroot/")) { // The "/AjaxFileBrowser/" are not a WebDAV folders. They can be used to store client script files, // images, static HTML files or any other files that does not require access via WebDAV. // Any request to the files in this folder will just serve them to the client. await context.EnsureBeforeResponseWasCalledAsync(); string filePath = Path.Combine(htmlPath, urlPath.TrimStart('/').Replace('/', Path.DirectorySeparatorChar)); // Remove query string. int queryIndex = filePath.LastIndexOf('?'); if (queryIndex > -1) { filePath = filePath.Remove(queryIndex); } if (!File.Exists(filePath)) { throw new DavException("File not found: " + filePath, DavStatus.NOT_FOUND); } Encoding encoding = context.Engine.ContentEncoding; // UTF-8 by default context.Response.ContentType = string.Format("{0}; charset={1}", MimeType.GetMimeType(Path.GetExtension(filePath)) ?? "application/octet-stream", encoding.WebName); // Return file content in case of GET request, in case of HEAD just return headers. if (context.Request.HttpMethod == "GET") { using (FileStream fileStream = File.OpenRead(filePath)) { context.Response.ContentLength = fileStream.Length; await fileStream.CopyToAsync(context.Response.OutputStream); } } } else { await OriginalHandler.ProcessRequestAsync(context, item); } }
/// <summary> /// Called when this folder is being moved or renamed. /// </summary> /// <param name="destFolder">Destination folder.</param> /// <param name="destName">New name of this folder.</param> /// <param name="multistatus">Information about child items that failed to move.</param> public override async Task MoveToAsync(IItemCollectionAsync destFolder, string destName, MultistatusException multistatus) { await RequireHasTokenAsync(); DavFolder targetFolder = destFolder as DavFolder; if (targetFolder == null) { throw new DavException("Target folder doesn't exist", DavStatus.CONFLICT); } if (IsRecursive(targetFolder)) { throw new DavException("Cannot move folder to its subtree.", DavStatus.FORBIDDEN); } string newDirPath = System.IO.Path.Combine(targetFolder.FullPath, destName); string targetPath = targetFolder.Path + EncodeUtil.EncodeUrlPart(destName); try { // Remove item with the same name at destination if it exists. IHierarchyItemAsync item = await context.GetHierarchyItemAsync(targetPath); if (item != null) { await item.DeleteAsync(multistatus); } await targetFolder.CreateFolderAsync(destName); } catch (DavException ex) { // Continue the operation but report error with destination path to client. multistatus.AddInnerException(targetPath, ex); return; } // Move child items. bool movedSuccessfully = true; IFolderAsync createdFolder = (IFolderAsync)await context.GetHierarchyItemAsync(targetPath); foreach (DavHierarchyItem item in await GetChildrenAsync(new PropertyName[0])) { try { await item.MoveToAsync(createdFolder, item.Name, multistatus); } catch (DavException ex) { // Continue the operation but report error with child item to client. multistatus.AddInnerException(item.Path, ex); movedSuccessfully = false; } } if (movedSuccessfully) { await DeleteAsync(multistatus); } // Refresh client UI. await context.socketService.NotifyDeleteAsync(Path); await context.socketService.NotifyRefreshAsync(GetParentPath(targetPath)); }
/// <summary>Calculate item's name.</summary> /// <param name="itemIdentifier">The item identifier.</param> /// <param name="serverItem">The server item.</param> /// <returns>The <see cref="string"/>.</returns> private string GetItemDisplayName(string itemIdentifier, IHierarchyItemAsync serverItem) { return(serverItem?.DisplayName ?? this.LocationMapper.GetNameFromIdentifier(itemIdentifier)); }
/// <summary> /// This handler shall only be invoked for <see cref="IFolderAsync"/> items or if original handler (which /// this handler substitutes) shall be called for the item. /// </summary> /// <param name="item">Instance of <see cref="IHierarchyItemAsync"/> which was returned by /// <see cref="DavContextBaseAsync.GetHierarchyItemAsync"/> for this request.</param> /// <returns>Returns <c>true</c> if this handler can handler this item.</returns> public bool AppliesTo(IHierarchyItemAsync item) { return(item is IFolderAsync || OriginalHandler.AppliesTo(item)); }
/// <summary> /// Gets a user file system item info from the remote storage data. /// </summary> /// <param name="remoteStorageItem">Remote storage item info.</param> /// <returns>User file system item info.</returns> public static FileSystemItemMetadataExt GetUserFileSystemItemMetadata(IHierarchyItemAsync remoteStorageItem) { FileSystemItemMetadataExt userFileSystemItem; if (remoteStorageItem is IFileAsync) { userFileSystemItem = new FileMetadataExt(); } else { userFileSystemItem = new FolderMetadataExt(); } userFileSystemItem.Name = remoteStorageItem.DisplayName; userFileSystemItem.Attributes = FileAttributes.Normal; userFileSystemItem.CreationTime = remoteStorageItem.CreationDate; userFileSystemItem.LastWriteTime = remoteStorageItem.LastModified; userFileSystemItem.LastAccessTime = remoteStorageItem.LastModified; userFileSystemItem.ChangeTime = remoteStorageItem.LastModified; // If the item is locked by another user, set the LockedByAnotherUser to true. // This will set read-only arrtibute on files. // Note that read-only attribute is a convenience feature, it does not protect files from modification. userFileSystemItem.LockedByAnotherUser = remoteStorageItem.ActiveLocks.Length > 0; if (remoteStorageItem is IFileAsync) { // We send the ETag to // the server inside If-Match header togeter with updated content from client. // This will make sure the changes on the server is not overwritten. ((FileMetadataExt)userFileSystemItem).ETag = ((IFileAsync)remoteStorageItem).Etag; ((FileMetadataExt)userFileSystemItem).Length = ((IFileAsync)remoteStorageItem).ContentLength; } ; // Set custom columns to be displayed in file manager. // We create property definitions when registering the sync root with corresponding IDs. List <FileSystemItemPropertyData> customProps = new List <FileSystemItemPropertyData>(); LockInfo lockInfo = remoteStorageItem.ActiveLocks.FirstOrDefault(); if (lockInfo != null) { customProps.AddRange( new ServerLockInfo() { LockToken = lockInfo.LockToken.LockToken, Owner = lockInfo.Owner, Exclusive = lockInfo.LockScope == LockScope.Exclusive, LockExpirationDateUtc = DateTimeOffset.Now.Add(lockInfo.TimeOut) }.GetLockProperties(Path.Combine(Program.Settings.IconsFolderPath, "LockedByAnotherUser.ico")) ); } if (remoteStorageItem is IFileAsync) { customProps.Add(new FileSystemItemPropertyData((int)CustomColumnIds.ETag, ((IFileAsync)remoteStorageItem).Etag)); } ; userFileSystemItem.CustomProperties = customProps; return(userFileSystemItem); }
/// <summary> /// Searches files and folders in current folder using search phrase and options. /// </summary> /// <param name="searchString">A phrase to search.</param> /// <param name="options">Search options.</param> /// <param name="propNames"> /// List of properties to retrieve with each item returned by this method. They will be requested by the /// Engine in <see cref="IHierarchyItemAsync.GetPropertiesAsync(IList{PropertyName}, bool)"/> call. /// </param> /// <returns>List of <see cref="IHierarchyItemAsync"/> satisfying search request.</returns> public async Task <IEnumerable <IHierarchyItemAsync> > SearchAsync(string searchString, SearchOptions options, List <PropertyName> propNames) { bool includeSnippet = propNames.Any(s => s.Name == snippetProperty); // search both in file name and content string commandText = @"SELECT System.ItemPathDisplay" + (includeSnippet ? " ,System.Search.AutoSummary" : string.Empty) + " FROM SystemIndex " + @"WHERE scope ='file:@Path' AND (System.ItemNameDisplay LIKE '@Name' OR FREETEXT('""@Content""')) " + @"ORDER BY System.Search.Rank DESC"; commandText = PrepareCommand(commandText, "@Path", this.dirInfo.FullName, "@Name", searchString, "@Content", searchString); Dictionary <string, string> foundItems = new Dictionary <string, string>(); try { // Sending SQL request to Windows Search. To get search results file system indexing must be enabled. // To find how to enable indexing follow this link: http://windows.microsoft.com/en-us/windows/improve-windows-searches-using-index-faq using (OleDbConnection connection = new OleDbConnection(windowsSearchProvider)) using (OleDbCommand command = new OleDbCommand(commandText, connection)) { connection.Open(); using (OleDbDataReader reader = command.ExecuteReader()) { while (await reader.ReadAsync()) { string snippet = string.Empty; if (includeSnippet) { snippet = reader.GetValue(1) != DBNull.Value ? reader.GetString(1) : null; } foundItems.Add(reader.GetString(0), snippet); } } } } catch (OleDbException ex) // explaining OleDbException { context.Logger.LogError(ex.Message, ex); switch (ex.ErrorCode) { case -2147217900: throw new DavException("Illegal symbols in search phrase.", DavStatus.CONFLICT); default: throw new DavException("Unknown error.", DavStatus.INTERNAL_ERROR); } } IList <IHierarchyItemAsync> subtreeItems = new List <IHierarchyItemAsync>(); foreach (string path in foundItems.Keys) { IHierarchyItemAsync item = await context.GetHierarchyItemAsync(GetRelativePath(path)); if (includeSnippet && item is DavFile) { (item as DavFile).Snippet = HighlightKeywords(searchString.Trim('%'), foundItems[path]); } subtreeItems.Add(item); } return(subtreeItems); }
/// <summary> /// Searches files and folders in current folder using search phrase, offset, nResults and options. /// </summary> /// <param name="searchString">A phrase to search.</param> /// <param name="options">Search options.</param> /// <param name="propNames"> /// List of properties to retrieve with each item returned by this method. They will be requested by the /// Engine in <see cref="IHierarchyItemAsync.GetPropertiesAsync(IList{PropertyName}, bool)"/> call. /// </param> /// <param name="offset">The number of children to skip before returning the remaining items. Start listing from from next item.</param> /// <param name="nResults">The number of items to return.</param> /// <returns>List of <see cref="IHierarchyItemAsync"/> satisfying search request.</returns>1 /// <returns>Items satisfying search request and a total number.</returns> public async Task <PageResults> SearchAsync(string searchString, SearchOptions options, List <PropertyName> propNames, long?offset, long?nResults) { bool includeSnippet = propNames.Any(s => s.Name == snippetProperty); // search both in file name and content string commandText = @"SELECT System.ItemPathDisplay" + (includeSnippet ? " ,System.Search.AutoSummary" : string.Empty) + " FROM SystemIndex " + @"WHERE scope ='file:@Path' AND (System.ItemNameDisplay LIKE '@Name' OR FREETEXT('""@Content""')) " + @"ORDER BY System.Search.Rank DESC"; commandText = PrepareCommand(commandText, "@Path", this.dirInfo.FullName, "@Name", searchString, "@Content", searchString); Dictionary <string, string> foundItems = new Dictionary <string, string>(); try { // Sending SQL request to Windows Search. To get search results file system indexing must be enabled. // To find how to enable indexing follow this link: http://windows.microsoft.com/en-us/windows/improve-windows-searches-using-index-faq await using (OleDbConnection connection = new OleDbConnection(context.Config.WindowsSearchProvider)) await using (OleDbCommand command = new OleDbCommand(commandText, connection)) { connection.Open(); await using (OleDbDataReader reader = command.ExecuteReader()) { while (await reader.ReadAsync()) { string snippet = string.Empty; if (includeSnippet) { snippet = reader.GetValue(1) != DBNull.Value ? reader.GetString(1) : null; // XML does not support control characters or permanently undefined Unicode characters. Removing them from snippet. https://www.w3.org/TR/xml/#charsets if (!string.IsNullOrEmpty(snippet) && invalidXmlCharsPattern.IsMatch(snippet)) { snippet = invalidXmlCharsPattern.Replace(snippet, String.Empty); } } foundItems.Add(reader.GetString(0), snippet); } } } } catch (OleDbException ex) // explaining OleDbException { context.Logger.LogError(ex.Message, ex); switch (ex.ErrorCode) { case -2147217900: throw new DavException("Illegal symbols in search phrase.", DavStatus.CONFLICT); default: throw new DavException("Unknown error.", DavStatus.INTERNAL_ERROR); } } IList <IHierarchyItemAsync> subtreeItems = new List <IHierarchyItemAsync>(); foreach (string path in foundItems.Keys) { IHierarchyItemAsync item = await context.GetHierarchyItemAsync(GetRelativePath(path)) as IHierarchyItemAsync; if (item == null) { continue; } if (includeSnippet && item is DavFile) { (item as DavFile).Snippet = HighlightKeywords(searchString.Trim('%'), foundItems[path]); } subtreeItems.Add(item); } return(new PageResults(offset.HasValue && nResults.HasValue ? subtreeItems.Skip((int)offset.Value).Take((int)nResults.Value) : subtreeItems, subtreeItems.Count)); }