Getting Started with the DAVServer component


Introduction

The DAVServer component provides a streamlined way to implement a WebDAV server that supports custom logic, granular security and authentication, and extending WebDAV to nonserver data storage. This article provides a conceptual and practical guide for extending the fully functional demo that comes packaged with the component to customize your WebDAV server's behavior.

Contents

Conceptual Overview

The DAVServer component receives inbound WebDAV client requests, parses the details of the request, and fires a corresponding set of component-level events. Your custom code goes in the event handler for each event. In other words, each time the DAVServer component receives a WebDAV client request, it executes the code you have written within each of the event handlers that are relevant to the client request.

Implementing Event Handlers

For example, when a client sends a WebDAV request that includes the COPY method, the DAVServer component fires the CopyFile event, and the details of the COPY request are provided through the CopyFile event parameters:

  • Path is the original server path for the source file
  • FileName is the original file name for the source file
  • DestFileName is the new file name for the destination file
  • DestParentPath is the path of the new parent directory for the destination file

Within the event handler for CopyFile, for each event, you can write custom logic to govern how the server behaves when a client makes a COPY request.

Basic Implementations

A basic server implementation is included in the DAVServer demo that comes packaged with the component. In the demo, the event handler for each event performs file operations on the local file system. This mirrors the behavior of standard WebDAV servers.

For example, when the demo server receives a COPY request from a WebDAV client, the CopyFile event fires and the demo event handler processes this event in a straightforward way: It uses the event parameters to find the original file, and then it creates a new identical file with the specified DestFileName.

The demo includes similarly basic implementations for each component event to help understand what kind of behavior is expected within these event handlers. The DAVServer demo should behave like a typical out-of-the-box WebDAV server solution.

Advanced Implementations

The power of DAVServer is evident in its ability to support more sophisticated event handler codes. Within each event, you have the opportunity to write custom logic that extends the standard WebDAV server behavior to cover additional use cases.

For example, you might be using DAVServer to extend your WebDAV support to include database data (i.e., the data within the database appears like it is stored in files on the WebDAV server). In this example, the CopyFile event handler code would not (exclusively) file manipulation, but rather it would issue database queries that copy one set of database data into a new set of similar data.

Other examples of advanced behavior within event handlers are as follows:

  • Granular access/permissions and security checks
  • Masking filenames and the true source of files
  • Including nonserver resources as part of the "available" files
  • Screening file contents before returning them to clients
  • Logging statistics and monitoring info

Any code can be executed within these events, so the extent to which you can customize the server behavior is essentially unlimited.

Starting the Server

The DAVServer component can operate in three different ways:

  • Hosted by an embedded web server
  • Integrated into an external web server implementation
  • Offline mode

Embedded Web Server

DAVServer includes a fully functional web server that can be used for listening for inbound DAV requests. When using the component in this mode, the ServerSettings property contains fields like LocalHost, LocalPort, and AllowedAuthModes that help configure the embedded server. To launch the embedded web server, simply call the StartListening method. To stop the server, call StopListening.

For example:

davserver.ServerSettings.LocalHost = "LocalHost"; davserver.ServerSettings.LocalPort = 8080; davserver.ServerSettings.AllowedAuthModes = 1; //basic HTTP auth davserver.StartListening();

Before starting the server, each relevant DAV event should be implemented by attaching an appropriate event handler (e.g., OnListDirectory, OnCreateFile).

External Web Servers

DAVServer can seamlessly integrate with a broader server implementation. In this case, because the external server implementation provides hosting functionality, the embedded web server is not used.

In this mode, DAV requests are received by the external server implementation and passed to the component through the ProcessRequest method. This method expects an HTTPContext as a parameter, which includes the HTTP-level details of the received request.

For example:

HttpContext context = externalServer.getHTTPContext(); //fetching HTTP context implemented externally davserver.ProcessRequest(context);

Before processing requests, each relevant DAV event should be implemented by attaching an appropriate event handler (e.g., OnListDirectory, OnCreateFile).

Offline Mode

In offline mode, DAVServer interacts with WebDAV requests through the Request, RequestHeaders, Response, and ResponseHeaders properties.

Setting the Request and RequestHeaders properties simulates the component receiving an inbound WebDAV request with the specified body and headers. The component processes the request normally (firing appropriate events), and then generates the HTTP response and populates the Response property with the response body and ResponseHeaders with the response headers.

Security

When using DAVServer in Embedded Server mode, the component supports over-the-wire encryption (through a secure HTTPS connection) by setting the ServerCert property and client authentication by setting the ServerSettings.AllowedAuthModes property. When using DAVServer as part of a broader external server implementation, encryption and standard user authentication is handled at the server level rather than at the component level.

The flexibility of the event-driven architecture, however, means that additional security measures, specifically those concerning access and user permissions, can be implemented through custom code within event handlers. Implementing specific access and permission paradigms through custom code is outside of the scope of this article, but following are some examples of how security mechanisms can be implemented within event handlers:

  • Enforcing read-only permissions by checking authentication more strictly in "write" events
  • Preventing write operations in specific folders
  • Allowing a user access only after checking an external security condition
  • Passing a user's credentials along to a database or other external storage to confirm external access
  • Checking file size, file date, or other metadata before accepting an operation

Specific Event Handlers

The DAVServer component hides the details of the WebDAV protocol by translating WebDAV requests into the component's event-driven API. The following sections discuss the primary considerations to take into account when implementing the component's event handlers.

Many of the listed events expose a ResultCode parameter, which is used to communicate the success or failure of the operation to the component and connection. This parameter is always 0 when relevant events fire. If the event, or operation, cannot be handled successfully, this parameter should be set to a non-zero value.

Please refer to the documentation for more specific information about each event.

Listing Directory Contents

The OnListDirectory event is fired when a client attempts to list the contents of a directory identified by the ParentPath parameter.

To provide this directory's contents to the server, your event handler should call the ListFile or ListFolder method for each discovered file or subdirectory in the requested directory. The RequestId parameter should be passed along to each of these method calls. If the directory contains multiple files or subdirectories, call this method in a loop for each item.

Note: The OnListDirectory event includes filters through the _Mask_, _StartAt_, and StartAfter parameters and ordering requirements through the OrderBy and ReverseOrder parameters. These parameters should be checked and obeyed before providing results to the server.

The following is an example of a ListDirectory event handler when the server is listing a single folder and a mask is not used:

string realPath = ConvertToRealPath(e.ParentPath); if (Directory.Exists(realPath)) { string[] files = Directory.GetFiles(realPath); foreach (string file in files) { int attributes = 0; string itemPath = ""; string parentId = ""; string fileName = ""; DateTime CreationTime = DateTime.FromFileTimeUtc(0); DateTime LastAccessTime = DateTime.FromFileTimeUtc(0); DateTime LastWriteTime = DateTime.FromFileTimeUtc(0); // Return the information desired in the request. FileInfo fileInfo = new FileInfo(file); itemPath = RealPathToItemPath(file);
if ((requestedInfo & Constants.ITEM_INFO_PARENTID) != 0) parentId = GetItemParentPath(itemPath);
if ((requestedInfo & Constants.ITEM_INFO_NAME) != 0) fileName = GetItemNameFromPath(itemPath);
if ((requestedInfo & Constants.ITEM_INFO_TIMES) != 0) { CreationTime = fileInfo.CreationTime; LastAccessTime = fileInfo.LastAccessTime; LastWriteTime = fileInfo.LastWriteTime; }
if ((requestedInfo & Constants.ITEM_INFO_ATTR) != 0) { attributes += Constants.ITEM_ATTR_FILE; if ((fileInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) attributes += Constants.ITEM_ATTR_READONLY; } // Here, we specify the current user as an owner. In real applications, the owner should be taken from the storage. server.ListFile(e.RequestId, e.ParentId, fileName, attributes, fileInfo.Length, CreationTime, LastAccessTime, LastWriteTime, e.OwnerId); } e.ResultCode = DAVSDK_ERR_OK; } else { e.ResultCode = Constants.DAVSDK_ERR_FILE_NOT_FOUND; }

Lock and Unlock Files

For many client requests like read, write, and delete requests, the first event that fires is LockFile. This is intended to provide the client with exclusive access to the file while performing an operation to preclude collisions and other concurrency issues. At the end of the operation, UnlockFile fires to indicate that the lock can be cleared.

Implementing these events often involves maintaining a data structure that stores an identifier for each currently locked resource and associates that lock with a SessionId. SessionId is a parameter in all relevant events and can be used as a unique identifier for the purposes of checking for exclusive access.

Create Files and Directories

The CreateFile event is fired when a client attempts to create a new file, and the CreateDirectory event is fired when a client attempts to create a new directory. The event parameters provide information on the path and name of the file/directory to be created.

If your DAV server's behavior is restricted to pure file operations, handling these events requires you to perform the corresponding file operations on the file system. In cases in which your DAV server is extending DAV access to nonfile data sources, this may involve creating other resources like S3 buckets, database rows, and unstructured data blobs.

Example:

string realPath = Path.Combine(e.ParentPath, e.FileName); if (File.Exists(realPath) || Directory.Exists(realPath) && !e.OverwriteExisting) { e.ResultCode = Constants.DAVSDK_ERR_ALREADY_EXISTS; return; } else { File.Create(realPath) }

File Read Operations

When a client sends file read requests, the component first fires the GetFile event. The location of the requested file is derived from combining the Path and FileName parameters. If the LocalPath parameter is set in the handler for this event, the component will open and read the contents of this path (which should include a file name) without needing to perform file operations manually. The contents of this file will be served as the response to the read request. If LocalPath is set in this event, the next step is skipped.

The following is a simple example of a GetFile event handler that normalizes paths and services files on disk:

string realPath = ConvertToRealPath(e.Path); e.LocalPath = realPath;

If a LocalPath is not set in the GetFile event, then the ReadFile event fires. In this event, you can set the Buffer parameter to the raw data bytes that the server should return in response to the read request, and BytesRead to the number of bytes being returned. This event must be used when data are being read from nondata file sources like databases, and optionally, it can be used even with file-based data sources, if more control is desired.

The following is an example of using the ReadFile event in a context where the data may or may not actually reside in a file.

string targetResource = Combine(e.Path, e.FileName); if (resourceExists(targetResource)) { // Read the contents of the target resource into memory MemoryStream stream = new MemoryStream(getDataBytes(targetResource)); // Seek to appropriate offset; important if this event fires more than once for one resource if (e.Offset >= stream.Length) { e.BytesRead = 0; } else { stream.Seek(e.Offset, SeekOrigin.Begin);
// Read bytes equal to Length, or as many bytes as possible up to Length if (e.Offset + e.Length > stream.Length) { e.BytesRead = stream.Read(e.Buffer, 0, (int)(stream.Length - e.Offset)); } else { e.BytesRead = stream.Read(e.Buffer, 0, e.Length); }
// Report successful result e.ResultCode = Constants.DAVSDK_ERR_OK; } } else { e.ResultCode = Constants.DAVSDK_ERR_FILE_NOT_FOUND; }

File Write Operations

When a client sends file write requests, the component first fires the PutFile event. The intended location of the new or updated file is derived by combining the Path and FileName parameters. If the LocalPath parameter is set in the handler for this event, the component will take the contents in the WebDAV request and write them to the file specified by LocalPath (which should include a file name) without needing to perform the file operations manually. If this is done, the next step is skipped.

The following is a simple example of a PutFile event handler that normalizes paths and creates files on disk:

string realPath = ConvertToRealPath(e.Path); e.LocalPath = realPath;

If a LocalPath is not set in the PutFile event, then the WriteFile event fires. In this event, the raw data bytes provided in the write request by the WebDAV client are available in the Data parameter, and the length of this content is provided in the Length parameter. It is up to the developer to decide where to put this data (e.g., insert the data into a database, add the data to an S3 bucket). This event must be used instead of PutFile when data are being written to non-file data sources like databases, and optionally, it can be used even with file-based data sources, if more control is desired.

The following is simple, abstracted example of using the WriteFile event in a context where the data may or may not actually reside in a file.

string targetResource = Combine(e.Path, e.FileName); MemoryStream stream = new MemoryStream(targetResource); stream.Seek(e.Offset, SeekOrigin.Begin); stream.Write(e.Data, 0, e.Length); e.ResultCode = Constants.DAVSDK_ERR_OK;

Deletion

When clients request to delete files and directories, the DeleteFile event fire. The location of the resource to be deleted is obtained by combining the Path and FileName parameters.

The Recursive parameter specifies whether directory contents may be removed if the item is a directory and it contains any child resources. If this parameter is false and the directory contains subdirectories or files, the deletion should be stopped and the DAVSDK_ERR_DIR_NOT_EMPTY error should be reported. If this parameter is true, all contents including subdirectories and their contents are deleted recursively.

We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@callback.com.