Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / IIS

Using .NET and PHP to create an extensible WebDAV server

5.00/5 (18 votes)
31 Oct 2007CPOL32 min read 1  
Shows how a .NET based WebDAV server can be created by combining two Open Source projects. The example implementation returns files from a file system, but you can extend it to return resources from any repository.

Overview

What!? .NET? PHP? In the same sentence? Yes! My requirement has been to build a WebDAV server for .NET, and the most successful option has been to combine .NET and PHP. No, not interpreted, slow PHP, but PHP running at compiled code speed and able to use .NET assemblies.

Click on this link to access both the "how-to" presentations referenced in the article as well as the binary and source code files.

If you download the binaries, let me know if you have any difficulties installing a server instance. If you create an interface for another repository, it will be great to hear about your experiences.

Introduction

This article is about implementing a WebDAV server. If PHP and/or WebDAV is not on your radar, then this article will not be for you. Equally, if all you want to do is share files using WebDAV, then use IIS because this facility is built in.

The purpose of this article is to show how a flexible WebDAV server can be built using .NET, one that can be extended to return documents from any type of repository, or can be extended to introduce additional document properties or support more granular access control.

Table of contents

Files

The binary files available from the link associated with this article are the ones you need to install, setup, and run the WebDAV server discussed in this article. To install the server, unzip the binary files and then create a virtual directory in IIS using a wildcard map. For detailed information, see the installation note below. The possible omission is that if the files are loaded on to PC running vanilla Windows 2003 or Window 2000, then one of the VS2005 C++ run-time files may be needed also. This issue is reviewed in this Phalanger forum note. The C++ run-time files installer can be found here. These files will almost certainly be installed if you have installed the .NET 2.0 SDK.

The source code files include only those changes and additions you need to apply to the Phalanger source code and which are reviewed in the source code notes below. Go to Phanalger project on CodePlex for the full Phalanger source. The PHP source code is included with the binaries as these files really form part of the WebDAV server.

Note: Do not use the binaries associated with this article with Phalanger. You may have already or maybe planning to install the Phalanger binaries and Visual Studio extensions. In case you do, and to prevent namespace clashes with the Phalanger assemblies, the assemblies in the binary files have been renamed from their original Phalanger counterparts. The extensions have also been modified to work with the renamed file, so they will not work with Phalanger binaries.

The potential to clash occurs because Phalanger installs its assemblies into the GAC, assemblies which .NET may use preferentially. This is a potential problem because to implement the WebDAV server as a handler (see the Architecture notes), modifications to one of the source files of the Phalanger core must be made to make two methods public.

If you are not interested in understanding how the server works and just want to get on with it, download the binaries and follow the instruction in the installation section below. Let me know how you get on.

Background

In my scenario, .NET support is imperative because the server must be able to access properties exposed by a suite of existing .NET assemblies. If the target platform is Java, then there are plenty of choices, but repeated web searches revealed very few options for the .NET platform. There seems to be just one commercial offering, and there's a DAV framework project called Sphorium on SourceForge, but that seems to be about it. The commercial option might be possible; though, at nearly $3K for a redistributable license, it's not cheap. The Sphorium framework looks promising, but it doesn't implement a couple of really important elements of the DAV specification. A few people have blogged about creating a DAV server for .NET, but none have been willing or able to release code.

Just as I was preparing to raid the piggy-bank and buy a licence to the commercial option, I came across two projects. One is a PHP implementation of a WebDAV server. Its part of the PEAR project. It's a reasonably complete implementation of a DAV 1 and 2 server, and comes with a sample implementation that shows how to allow controlled access to files on a server. However, it's PHP, and using PHP doesn't easily meet the requirement to have access to .NET assemblies, and this is where the second project comes in.

Phalanger

Phalanger is a PHP compiler for .NET implemented in .NET 2.0. That is, it will take PHP scripts and compile them so they can be used from ASP.NET or as a standalone application. I'm not going to go into details about Phalanger except where it becomes necessary to explain the DAV server, because there's lots of information on the Phalanger website, on CodePlex, and here on CodeProject. Microsoft has hired two of the founders of the Phalanger project and, given the news that Microsoft has created the Dynamic Language Run-time (open sourced) to facilitate dynamic languages, I'm hopeful that in due course we'll see PherrousPHP alongside IronPython, IronRuby, JScript, and VBScript.

What is WebDAV?

If you are reading this, you probably have a good idea what WebDAV is, so there's no point going into exhaustive detail. You may already be using it in the form of Web Folder in Windows. DAV is short for Distributed Authoring and Versioning. It is a specification of the Internet Engineering Task Force (IETF) that extends HTTP so that a client and a server can communicate about the documents to be managed as well as retrieve and store those documents. The specification is RFC 2518, and can be found here: http://www.ietf.org/rfc/rfc2518.txt. Using the protocol, clients can also request properties about documents and ask for or release locks on a document. The server may be responsible for returning files managed by the server's file system but, equally, it may be returning documents recorded in a database.

HTTP defines verbs like GET and PUT. In the DAV specification, these verbs are retained so, for example, a PUT is a valid verb in DAV as it is in HTTP. The semantics are pretty much the same though there are important and fairly obvious differences. In DAV, a resource being uploaded (a document is referred to as a resource) may be locked by another client, so the server may need to prevent the upload taking place and return a specified HTTP status code that represents this condition. DAV then specifies new status codes and verbs, such as OPTIONS (allows a client to discover which WebDAV features are supported), PROPFIND (find out about available documents and their properties), LOCK, UNLOCK, MKCOL (directories are referred to as collections), DELETE, COPY, MOVE, and so on. Of course, it's not as simple as just creating a request and specifying a verb. Most verbs will expect additional information as headers or as XML in the request body. Servers respond with lots of information, maybe as headers, but probably as XML in the response body. All of the potential interactions are described in the 94 page specification.

So why DAV?

If all you want to be able to do is allow users access to a set of files, then HTTP will meet your needs. If you configure your Web Server to allow directory browsing, then users are able to list, download, and upload documents. However, if you want to:

  • Implement access control;
  • Allow clients to be able to review arbitrary and application specific document properties;
  • Serve documents from a database or other repository, not just from a file system;
  • Collaborate on documents over a secure IP connection,

then DAV is one option to consider.

A question you might ask is: What sort of repositories might you present via WebDAV?

Microsoft Exchange is a WebDAV server, and a typical repository is a user's Inbox. Microsoft SharePoint is also a WebDAV server. Someone has created a WebDAV server (in Java) to interface with the Amazon S3 facility. Sub-version, the popular source code control service, supports WebDAV. There's a list of WebDAV service applications on www.webdav.org, but any repository of things that can represented as a set of documents and folders is capable of being presented by a WebDAV service.

WebDAV clients

There are already many WebDAV clients including Windows Explorer and all of the Microsoft Office programs. That is, Windows Explorer, Word, and the other Office programs will treat a WebDAV service just like any other networked repository of files. Windows Explorer allows you to assign a WebDAV service to a drive letter or keep it as a network place. This means, you can implement a WebDAV server knowing that users will be able to access the service with software that is in use every day and is already supported by IT departments.

A quick aside here about WebDAV support in Windows, because if I don't point it out, someone will: Windows clients (Explorer or Office) are not 100% compliant - no surprise there. All the Microsoft clients including Windows Explorer and the Office suite also, and preferentially, support FrontPage extensions. Because these Microsoft clients will default to FrontPage, they have to be told that the server is really a WebDAV server, and this is where the spec difference occurs. WebDAV services that are to be consumed by Windows clients have to return an additional Windows specific header in response to any request using the OPTIONS verb. This detail is covered in the server implemented in the downloadable files associated with this article.

Why PHP?

The practical reason is that the WebDAV server I found is written in PHP. Beyond that, it's a language I know well. PHP is a well regarded scripting language used to create pages by millions of websites including mine. Now that Ruby and Python are being supported for .NET, I could look for a server written in one of these languages, but I don't know either sufficiently well to tackle a project to generate a WebDAV service. The WebDAV RFC (2518) runs to 94 pages of exacting specification, so anyone has to be motivated by the prospect of using an existing project that provides a head start. By the way, if you are a Python coder, there is a WebDAV server written in Python which can be found on SourceForge.

With the ability to combine PHP and .NET, I think I get the best of two worlds. I get the option to work with a compiled application that is going to have the performance characteristics of a native .NET application. Plus, if issues arise on a client site, I can revert to the scripts and so be able to debug the problem and even change the code without the need to have Visual Studio installed and then recompile the scripts to restore performance.

Why the PHP server code?

A WebDAV server has to respond to the possible combinations of verbs, request headers, and XML defined to be legal in the specification with response codes, headers, and XML also defined by the specification.

What the PHP code does is to implement a base class that hides all the management of verbs, headers, response code, and XML, and allows derived classes to implement functions that return arrays of information and a success/fail response. The implementation of the derived class does not need to know how the specification demands that the request or response XML be formatted or what response codes and headers the specification requires or allows.

The image blow shows the base class as it looks from the derived class. The methods in capitals are abstract and can be overridden. They correspond to the verbs specified in the webDAV specification, and are (optionally) implemented in the derived class to provide support for that verb. Using the PHP base class, the implementor can focus on implementing the logic required to support the chosen repository.

Screenshot - Server.png

The most important and complex verb to support is PROPFIND. PROPFIND is the verb a client will use to query the server for information about the resources and collections available. The request is a mixture of headers and, optionally, XML, that together act somewhat like a SQL Where clause. A request might ask:

  • List all the resources in the root folder and list the properties supported
  • List all the resources and return details of properties A, B, and C

It looks straightforward but, as ever, the devil is in the details. The PHP base class takes care of the details, and requires only that the derived class return a structured array containing details when requested.

The architecture

WebDAV servers have to handle all request verbs and all query paths. This is important because the path requested by a user might be a directory or a source code file. This is unfamiliar territory when working with IIS. IIS selects an application (ASP.NET, PHP, Perl, etc.) based on the file extension mappings associated with a virtual directory. Even when IIS works out that a request should be handled by ASP.NET for example, this is then further refined by the .NET run-time based on the handlers associated with a file extension map defined in machine.config and web.config.

To ensure that the WebDAV server will receive all requests, it is implemented as a handler; that is, the "main" class is derived from IHttpHandler. This handler is specified in web.config, and it is defined to respond to all verbs and all paths. It's also necessary to make sure that the web site or virtual directory hosting the WebDAV server uses a wildcard mapping (see the installation section for more information).

The WebDAV handler is responsible for setting up details of the AppDomain and for invoking the Phalanger RequestContext class which is ultimately responsible for parsing the PHP scripts, pre-compiling, and then executing the generated code to produce a response for the client.

The ASP.NET IHttpHandler interface specifies one property, IsReusable(), and one method, ProcessRequest(). This method is called by the ASP.NET run-time, and is passed a HttpContext instance for the current request.

Here's the implementation of this method used in the binaries:

C#
public void ProcessRequest(HttpContext context)
{
    if (context == null)
        throw new ArgumentNullException("context");

    // disables ASP.NET timeout if possible:
    try { context.Server.ScriptTimeout = Int32.MaxValue; } catch (HttpException) { }

    // ensure that Session ID is created
    RequestContext.EnsureSessionId();
            
    // default culture:
    Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
    RequestContext request_context = 
      RequestContext.Initialize(ApplicationContext.Default, context);

    PHP.Core.Debug.WriteLine("REQUEST", "Processing request");

    if (IsPHP(context.Request.Path))
    {
        Process(request_context, context);
    }
    else if (IsWebDAV(context.Request.Path))
    {
        Process(request_context, context, "Scripts/webdav.php");
    }
    else
    {
        context.Response.WriteFile(context.Request.PhysicalPath);
    }

    context.Response.End();

    if (request_context != null) request_context.Dispose();
}

The method as implemented is pretty straightforward:

  1. The context provided by ASP.NET is checked;
  2. The static Phalanger method RequestContext.EnsureSessionId() is called to setup PHP session information (Phalanger translates PHP session management into ASP.NET session equivalents);
  3. A Phalanger RequestContext instance is created; and
  4. Processing is passed to one of two private classes.

My implementation assumes that any PHP file (.php) is to be executed as a PHP script rather than regarded as a resource to be accessed or updated, because this helps me with debugging. But there's no inherent reason why these cannot be regarded as resources to be transferred to the client.

If the extension of the requested resource is not .PHP, then the handler is hard coded to execute the script "Scripts/webdav.php" which implements the WebDAV logic. Phalanger is responsible for working out if the requested script has been pre-compiled and for using the correct assembly if it is, or for compiling the script dynamically.

C#
void Process(RequestContext request_context, HttpContext context, string scriptFilename)
{
    PhpSourceFile requestFile = new PhpSourceFile(
            new FullPath(HttpRuntime.AppDomainAppPath),
            new FullPath(HttpRuntime.AppDomainAppPath + scriptFilename)
        );

    if (request_context.ScriptContext.Config.Session.AutoStart)
        request_context.StartSession();

    Type script = null;

    try
    {
        script = request_context.GetCompiledScript(requestFile);

        if (script != null)
        {
            request_context.IncludeScript(context.Request.PhysicalPath, script);
        }
    }
    catch (PHP.Core.ScriptDiedException)
    {
        Console.WriteLine("Died");
    }
    catch(Exception ex)
    {
        ReportStatus(ex.Message, 0);
        System.Console.WriteLine(ex.Message);
        // A user code or compiler have reported a fatal error.
        // We don't want to propagate the exception to web server.
    }
}

This private method is called from ProcessRequest() to run the WebDAV code. The requested file is wrapped in an PhpSourceFile instance, then GetCompiledScript() is called to retrieve a pre-compiled version of the script or to compile the requested (and dependent) script.

Finally, the script is executed using the call to IncludeScript(). Woo hoo, that's it.

Configuration file

You may have noticed in the preceding script that the call to request_context.StartSession() is conditional upon the value of request_context.ScriptContext.Config.Session.AutoStart. This value is one of many values that Phalanger retrieves from web.config. Ordinarily, PHP uses a file called PHP.ini to store its properties. In Phalanger, this information is stored in a custom section of web.config. There a comprehensive example of the properties that can be setup on the Phalanger website.

In summary, Phalanger uses a custom configuration section called "phpNet". The WebDAV binaries associated with this article include a web.config file that serves the needs of the WebDAV server, and is a sample to compare with the more comprehensive example on the CodePlex website.

Among the important features you can enable or disable is run-time and compile time error capturing; this is information that can be captured in addition to information that is written to the ASP.NET trace collection. The Web.config included with the server enables the reporting of all run-time and compile-time errors, and directs them to a file called output.html.

The source code

To create a working server, it has been necessary to make modifications to both the Phalanger project and the PHP WebDAV scripts.

The most important change to the Phalanger code is to the file RequestContext.cs of the PhpNetCore project. The change to this file is to make the methods EnsureSessionId, GetCompiledScript(), and MultiScriptAssembly() public so that they can be called from an external assembly. Phalanger implements its own handler in the PhpNetCore project, but this assumes it will handle only requests for files with a .php extension, which, for a WebDAV server, is not sufficient. It is for this reason that a WebDAV handler has been created, and it is this handler that needs access to the modified methods of the PhpNetCore project.

HttpHeaders.cs of the PhpNetCore project has also been changed because I believe a bug has been introduced that prevents the header being written to the response stream correctly.

These changed files are included in the source code file associated with this article, and you should be able to apply them to the Phalanger source code. The changes included have been made to build 22713, so you may need to be careful applying them to later Phalanger builds.

The PHP portion of the WebDAV server is also included in the associated file, and comprises several script files:

FileComment
FileSystemAccess.php Contains the implementation of a base class, and a derived class called HTTP_WebDAV_Server_Filesystem that implements the WebDAV semantics. This implementation uses a JET (.mdb) to store lock information.
FileSystemMySQL.php Contains the implementation of a base class, and a derived class called HTTP_WebDAV_Server_Filesystem that implements the WebDAV semantics. This implementation uses a MySQL database to store lock information.
WebDAV.phpThis script checks user authentication and, if successful, instantiates the FileSystem class and calls the ServeRequest() method.
_parse_propfind.phpParses any XML associated with a PROPFIND request.
_parse_proppatch.phpParses any XML associated with a PROPPATCH (change to resource properties) request.
_parse_lockinfo.phpParses any XML associated with a LOCK request.
Auth.phpA class that implements Digest Authentication.

The remaining files provide miscellaneous support functions.

There are two additional files called Server.php and FileSystem.php. The files FileSystemAccess.php and FileSystemMySQL.php contain the contents of both Server.php and FileSystem.php. It turns out that the line number information generated by Phalanger for debugging within Visual Studio is not correct for the case of a derived class that is implemented in a different file to that of the base class. This makes it impossible to use Visual Studio to step through the PHP code in the file implementing the derived class. This problem does not arise when both the base and derived classes are in the same file. So the main implementations are in FileSystemAccess.php and FileSystemMySQL.php because they combine both the base and derived classes, while Server.php implements the base class alone, and FileSystem.php the derived class (MySQL) alone.

The PHP files have been modified quite a bit to use calls to ASP.NET classes when these make more sense, and they've been updated to add statements to write trace information which can be reviewed by looking at trace.axd. Also, the PEAR PHP implementation assumes WebDAV will be running from the root of a website, while I want the WebDAV server to be attached to a website root or to a specific virtual directory.

There's a significant amount of PHP code, so it's not practical to cover it all in this article, so I'll just review WebDAV.php in parts because it illustrates the integration of PHP and .NET:

PHP
import namespace System;
import namespace System:::Configuration;

$appSettings = ConfigurationManager::$AppSettings;

$realm = $appSettings->Get("Realm");
$DBHOST = $appSettings->Get("DBHOST");
$DB_WEBDAV = $appSettings->Get("DB_WEBDAV");
$DBUSER = $appSettings->Get("DBUSER");
$DBPWD = $appSettings->Get("DBPWD");
$SITEPATH = $appSettings->Get("SITEPATH");
$USERPASSWORDS = $appSettings->Get("Users");
$UseAuthentication = $appSettings->Get("UseAuthentication");
$ConnectionTimeout = $appSettings->Get("ConnectionTimeout");

System:::Web:::HttpContext::$Current->Trace->Write("REALM", $realm);

The WebDAV.php file begins by importing .NET namespaces in a similar way to C# or VB.

In C# and VB, the dot (.) is used to signify "member access", whether of a class or a namespace. In PHP, the dot is reserved as the concatenation operator. Instead, the Phalanger designers chose to use three colons to signify namespace access. The designers have chosen to re-use other operators including $ (signifies a field such as a variable or property), two colons (::) (static class or member access), and -> (dynamically allocated member access).

In principal, this is straightforward, but it does mean that you have to know how a class, method, or member is declared so that you can use the correct syntax. In C#, you use dot whether you are accessing a dynamic or static class member or property. Take the line:

PHP
$appSettings = ConfigurationManager::$AppSettings;

This returns access to the appSettings collection, but because AppSettings is defined as a property, it must be accessed using the $ prefix. Also, because the property is declared statically, it must be accessed using the :: operator rather than the -> operator.

The returned $appSettings is a regular Hashtable, so its members can be accessed using the -> operator. You can see this pattern repeated in the line:

PHP
System:::Web:::HttpContext::$Current->Trace->Write("REALM", $realm);

This line access the Trace class of the current context to write information to the trace log.

So far, the only code used has been .NET. Below, .NET and PHP are mixed to authenticate the current user based on user credentials specified in web.config.

PHP
if (isset($UseAuthentication) && strtolower($UseAuthentication) != "false")
{
    $users = array();

    if (strlen($USERPASSWORDS) > 0)
    {
        $userarray = explode(";", $USERPASSWORDS);
        foreach($userarray as $user)
        {
            if (strlen($user) == 0) continue;
            list($username, $password) = explode(":", $user);
            $md5 = md5($username.":".$realm.":".$password);

            $users[$username] = $md5;
            // System:::Web:::HttpContext::$Current->Trace->Warn("USER", 
            //       "$username:$realm:$password:$md5:" . $users[$username]);
        }

        if (count($users) > 0)
        {
            include_once("auth.php");

            $HTTPDigest =& new HTTPDigest($realm);

            if (!$authed = $HTTPDigest->authenticate($users)) {
                System:::Web:::HttpContext::$Current->Trace->Warn(
                                     "AUTHENTICATION", "Not logged in");
                $HTTPDigest->send();
            }
        }
    }

    if (count($users) == 0)
    {
        System:::Web:::HttpContext::$Current->Trace->Write("AUTHENTICATION", 
                       "No users have been defined");
        header('HTTP/1.0 401 No users');
        die('401 No users');
    }

    System:::Web:::HttpContext::$Current->Trace->Write("AUTHENTICATION", "Authenticated");
}

Finally, the WebDAV FileSystem class is instantiated, and the ServeRequest() method is called passing in the location of the file the WebDAV server will manage.

PHP
require_once "FilesystemNew.php";

$server = new HTTP_WebDAV_Server_Filesystem();
$server->db_host = $DBHOST;
$server->db_name = $DB_WEBDAV;
$server->db_user = $DBUSER;
$server->db_passwd = $DBPWD;

$server->ServeRequest($SITEPATH);

System:::Web:::HttpContext::$Current->Trace->Write("WEBDAV", "Completed");

Debugging

Finding and fixing issues in a client server application is always a challenge. To help with this process, Phalanger generates a log file. The name of the file created and the specific information written to the file is controlled by settings in Web.config.

I've also taken advantage of the ability to extend PHP with calls to .NET classes to add information into the ASP.NET trace log as the PHP code is executed. You can access this additional information by accessing the ASP.NET tracing page trace.axd. This assumes you have enabled tracing in Web.config. Of course, you can also edit the PHP scripts and add your own additional trace information.

A complete WebDAV query can involve several requests and responses, and sometimes it's good to be able to have the detail of each request and response. In my experience, Fiddler2 by Eric Lawrence of Microsoft is a great tool for this purpose. It will record the details of all requests and their responses. It also allows you to issue a specific request, and control the verb, headers, and body sent. Using this feature, it is relatively easy to focus on and debug responses to a specific verb. Fiddler is simple to install. When it is started, it injects itself as a local proxy so that it can record and relay all HTTP transactions.

There are a couple of things to be aware of. Fiddler does not proxy the localhost (127.0.0.1). If you are working with a test server instance on your own PC, you will need to refer to your web server using your machine name, not localhost. Secondly, when Fiddler is started, it works all the time, and this is normally OK. However, if you download a file while it's running, as a proxy, it will be caching the file in the background, and you will only see the Run/Open/Save/Cancel button when it has finished downloading. Because you will normally expect the prompt to appear before the file is downloaded, you may think that the file transfer has gone wrong, especially if the background download takes a while to complete.

Finally, don't forget that changes you make may manifest themselves as .NET run-time errors. If you are working on the server, you will be able to see these errors in the browser, but if you are working on a different PC, then you will need to ensure that this element: <customerrors mode="Off"> is included the web.config. Also, you should temporarily set the trace element's localonly attribute to false so that you can see trace output on another PC.

Debugging steps summary

  1. Point a browser at the WebDAV site or virtual directory. If you see 404, you know the WebDAV service is not responding.
  2. If you see a blank page, then a run-time error will have occurred because either because the C++ run-time is missing or the .mdb cannot be written to by the IIS process.
  3. Use the trace.axd page to review any available trace information. This assumes you have enabled tracing in Web.config. Look in the trace for run-time error reports.
  4. Start Fiddler2 and try to access the WebDAV server from Excel. Fiddler2 records the request sent to a server and the response from the server so that you can examine the information sent both ways to try and locate the cause of an issue.

Installing a WebDAV server

Unzip the files in the webdavbinary.zip file to a folder. The unzipped folder will have the following structure:

  • WebDAV
    • Scripts - Where the test scripts and compilation command files are stored
      • bin - Where the compiled assembly will be created
      • Scripts - Where the WebDAV server scripts are stored
    • Server
      • bin - The compiled assemblies required to run the WebDAV server
      • db - The location of the SQL script to create the MySQL database (optional)
      • Dynamic - "Wrapper" assemblies generated automatically by Phalanger
      • Extensions - PHP extensions used by the WebDAV server implementation
      • Files - The default location of the files and folder that will be presented by the WebDAV server
      • Scripts - The WebDAV server scripts that will be used if not using a pre-compiled assembly
      • TypeDefs - Extension specific XML files used to create the static wrapper assemblies
      • Wrappers - Statically generated assemblies to "wrap" the native PHP extensions
    • Source - The source code changes

Dynamic, Extensions, TypeDefs, and Wrappers are folders required by Phalanger, and which are referenced by the phpNet custom section of web.config. For more information about the purpose of these folders, see the Phalanger documentation.

Locks database

A WebDAV server that supports the specification must support lots of features including the ability to hold details about the "lock" status of any given resource. In this sample server, the lock information is held in a JET (Access/.mdb) database. Make sure that the account used to run IIS has rights to update the .mdb file.. If IIS is unable to update this file, you will see an ADO.NET error message indicating an updatable query must be used. Also, if you plan to use the binaries associated with this article on Windows 2000 Pro or Server, you will need to make sure version 2.6 or later of the Microsoft Data Access Components (MDAC) is installed.

There are a couple of reasons for using JET, the primary one being that it's easy to install because the .mdb file is copied with the binaries and there's no database or script to run or database permissions to setup. The second reason is that the use of JET serves to illustrate how the .NET Framework, in this case, classes from the System.Data assembly, can be used from a PHP script. Here's an example of establishing a connection:

PHP
try
{
    $con = new System:::Data:::OleDb:::OleDbConnection();
    $con->ConnectionString = 
       "Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=.\\WebDAV.mdb;";
    $con->Open();
}
catch(System:::Exception $e)
{
    parent::TraceCategory("FILESYSTEM", "Locks database connection error: " . $e->Message);
    return;
}

Most of this is what you'd expect. A point to note is that to trap errors, the .NET Exception type must be caught. You can't rely on the PHP native Exception class to catch .NET exceptions because they have a different ancestry.

The code below shows the established connection being used to populate a reader and then iterate over the returned rows. The main difference is that Phalanger doesn't yet support indexers. The consequence of this omission is that the values of the reader must be accessed using the more primitive IDataReader methods GetOrdinal() and GetString().

PHP
try
{
    parent::TraceCategory("FILEINFO", $query);
    $cmd = new System:::Data:::OleDb:::OleDbCommand($query, $con);
    $reader = $cmd->ExecuteReader();
    $hasRows = $reader->HasRows;
}
catch(System:::Exception $e)
{
    parent::TraceCategory("FILEINFO", "QUERY ERROR: " . $e->Message);
}

try
{
    if ($hasRows)
    {
        while ($reader->Read())
        {
            $col_ns      = $reader->GetOrdinal("ns");
            $col_name    = $reader->GetOrdinal("name");
            $col_value   = $reader->GetOrdinal("ns");

            $info["props"][] = $this->mkprop($reader->GetString($col_ns), 
               $reader->GetString($col_name), $reader->GetString($col_value));
        }
    }
}
catch(System:::Exception $e)
{
    parent::TraceCategory("FILEINFO", "QUERY ERROR: " . $e->Message);
}
catch(Exception $e)
{
    parent::TraceCategory("FILEINFO", "FETCH ERROR: " . $e->message);
}

if ($reader != null) $reader->Close();

It you really prefer to use a "database", then the file called FileSystemMySQL.php implements the same WebDAV, but uses MySQL as a database. The MySQL implementation uses the native PHP extension to interact with MySQL, so serves to illustrate how Phalanger has retained support for existing PHP extensions even though they are written in unmanaged code. The binary files associated with this article also contains a .sql script to create the required MySQL database and tables. When you have created the database, you will need to edit the web.config file to edit the appSetting keys that describe the database location, database name, username, and password.

Web Server (IIS)

To create a WebDAV server in IIS, follow these instructions. If you like to see, rather then just read, how to create a WebDAV application, there are Flash presentations here showing how to setup a server using IIS 5.x and IIS 6.0.

  1. Create a Virtual Folder (if using IIS 6 of IIS 5 on Windows 2000 Server, you could also create a new website).
  2. Select the WebDAV sub-folder of the folder into which you unzipped the binary files.
  3. Right mouse-click on the new virtual folder or website, and select the Properties option from the context menu.
  4. Select the ASP.NET page and make sure .NET 2.0 is selected. If this change has to be made, this change must be made first.
  5. Select the Home Directory page.
  6. Press the Configuration page.
  7. Remove *all* the existing mapping.
  8. On IIS 6...
    1. Click on the Insert... button to add a new wildcard map.
    2. Enter aspnet_isapi.dll as the "Executable"; c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll.
    3. Make sure the Verify file exists check box is unchecked.
  9. On IIS 5...
    1. Click on the Add button to add a new wildcard map.
    2. Enter aspnet_isapi.dll as the "Executable"; c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll. Enter ".*" as the extension.
    3. Select the All Verbs radio button.
    4. Make sure the Verify file exists check box is unchecked.
  10. Press the OK button to save the changes to the configuration.
  11. Select the Documents tab and uncheck the Enable Default Document check box.
  12. Select the Directory Security tab. Press the Edit... button. Uncheck all options except anonymous.

Edit the web.config to:

  1. Change any settings such as logging and tracing.
  2. Set the SITEPATH appSetting value to reference the folder you want to use as the source for the WebDAV file (this folder must have security rights to be accessible by the IIS process).
  3. Enter the names and passwords of the users you want to be able to access the WebDAV service; or
  4. Set the value of the UseAuthentication appSetting name to false.
  5. Set the values for the database location and access credentials.

To test the WebDAV server, start by pointing your browser at the website or virtual directory you have created. If the application is successful, you will see a listing of any files that you have stored in the SITEPATH location.

Screenshot - WebDAV.png

The presentation of the output is controlled by the GetDir() method of the derived FileSystem class so you can adjust it to suit your needs. If you don't see a listing, there are pointers in the Debugging section to help find out what's going wrong. In summary, this is to review the log file which will list any compilation errors, to look at the trace information, and if all else fails, use Fiddler2 to look at the transactions between the client and server.

When you see a file/directory listing shown in your browser, you are in a position to use a WebDAV client. The easiest thing to do is to open an Office product like Word, then click on the file open menu open and enter the website or web site/virtual directory in the box labeled File name.

The Office product will attempt to use the WebDAV service just like any other file service and present a list of files and folders for you to use. Again, if you don't see the files and folders you expect or if the Office product reports an error, then you can run through the steps identified in the Debugging section. Although access to the service is from Excel, because it's still going through your IIS website or virtual directory, you can use a browser to display the trace pages.

Using Windows Explorer

The final thing to do is to create a WebDAV Net workplace. This gives you access to the WebDAV service from Windows Explorer.

  1. Click on My Network Places.
  2. Click with the right mouse button to display the context menu and select Map Network Drive.
  3. Click on "Sign up for online storage or connect to network server" and press the OK button.
  4. Select "Choose another network location" and press the Next button again.
  5. Enter the address of your WebDAV server site or virtual directory, and press the Next button once more. Note: I find that if using the WebDAV support in Windows Explorer against a local server, I cannot use localhost and must use the machine's domain name.
  6. If Windows is able to connect to the WebDAV server, you will be prompted to enter a name for the shortcut. Click the Next button and then the Finish button.

Pre-compiling the PHP scripts

One of the potential benefits of Phalanger is that it permits the pre-compilation of PHP scripts. The PHP compiler (phpc.exe) is included in the binary files associated with this article (.\WebDAV\Server\phpc.exe), along with a command file to run the compiler (.\WebDAV\Scripts\build.cmd).

The compiler, config file, and script are tailored to compile the scripts in .\WebDAV\Scripts and copy the generated assembly into the .\WebDAV\Server\bin folder. The WebDAV server works without pre-compilation because it will use the copy of the scripts in .\WebDAV\Server\Scripts by default. Phalanger will use the compiled script assembly preferentially so there's no need to remove the .\WebDAV\Server\Scripts sub-folder, though it will make it more convincing that the generated assembly is being used if this folder is at least renamed.

Implementation limitations

When I've looked closely at most WebDAV implementations, whether Java, C#, or Python, there are aspects of the WebDAV specification which have not been implemented. The PEAR WebDAV server used as the basis of this article is no exception. The authors of the original PHP script included comments in the code to highlight potential deficiencies. Their deficiencies are not material to me, but you can review the code to see if these are important to you. These omissions include:

  1. Recursive locking of resources within a locked collection is not supported.
  2. A PUT will support only one resource. That is, a single PUT operation cannot submit multiple resources.
  3. Copying resources across different WebDAV servers (URLs) is not supported.
  4. Content encoded PUT actions are not supported.

WebDAV is a specification that describes a protocol that is intended to facilitate standardized communication between a client and a server. However, one of the issues is whether both implement the protocol in the same way. In this server, the potential for misunderstandings between the client and server is enhanced because the server also implements Digest Authentication. It's known that IIS, Apache, Firefox, and IE sometimes have different ways of implementing the detail of the Digest specification. The following table shows the combinations of client and server that I've tested successfully. It's not exhaustive, and does not include non-Microsoft clients, but at least you can see the combinations I expect will work.

ClientServer
Excel 2003 and Web Folders on Windows XP (SP2)IIS 5.0 on Windows XP2 (SP2)
IIS 6.0 on Windows 2003
IIS 5.0 on Windows 2000 Server (SP4)
Excel 2003 and Web Folders on Windows 2000 (SP4)IIS 5.0 on Windows XP2 (SP2)
IIS 6.0 on Windows 2003
IIS 5.0 on Windows 2000 Server (SP4)
Excel 2000 and Web Folders on Windows 2000 (SP4)IIS 6.0 on Windows 2003
IIS 6.0 on Windows 2003

License

The license for this software is controlled by the licenses of the two constituent projects.

Phalanger is released under the Microsoft Shared Source Permissive License (SS-PL) which permits redistribution in binary form.

The PEAR PHP HTTP_WebDAV_Server is released under version 2.02 of the PHP license which permits redistribution providing copyright notices are retained.

Resources

ResourceLink
WebDAV SpecificationRFC 2518
WebDAV news sitewww.webdav.org
Phalangerhttp://php-compiler.net/
PEAR WebDAVpear.php.net
Presentations and Fileswww.lyquidity.com/webdav/

History

2007-05-28

  • Now returns error code 403 Forbidden when a file is read-only and a LOCK is requested. This causes Office to treat such files as read only.
  • Error exists in the FileSystem.cs file of the Phalanger PhpNetClassLibrary project. The errors mean that is_readable and is_writable *always* return true.
  • The boolean properties ishidden, isreadonly, isstructureddocument, and iscollection requested by Office are now tagged with a data type.

2007-05-26

  • The port from MySQL to support ADO.NET did not completely change the checklock function which causes a problem when a user saves their own file.
  • Updated webdav.php to support Windows authentication. To use Windows Authentication:
    • IIS must be set to Integrated Windows Authentication, and not to support any other type of authentication;
    • Set the appSettings key called "UseAuthentication" to "windows";
    • Set the authentication element of the system.web section:
    • XML
      <authentication mode="Windows" />

      and:

    • The authorization section of the system.web section must be set to: <deny users="?"/>.

2007-05-23

Fixes already - thanks to those who sent emails.

  • Fixed a bug causing authentication failure when dealing with files containing URL encodable characters such as a space.
  • Fixed an error in the calculation of the destination path in a copy or move when used with virtual directory.
  • Added a HEAD method to the derived class specifically to handle this verb. HEAD should behave like GET, but not return a body.
  • Changed the GET method of the derived class to accept a flag indicating whether or not the method should include a body.

2007-05-20

First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)