Hello.
I'm currently developing a WebDAV server in VB.NET. The server I'm trying to build should be standalone, so no IIS, nor Apache,...
I'm using the HttpListener class to wait for HTTP/WebDAV requests.
The server will be used to map files/folders that are contained in a SQL Server DB.
I tried to map a simple local folder to the Windows Explorer (as a network drive) with IIS, and with Apache, and it works "out of the box". But when I try to map my own WebDAV server to the Windows Explorer as a network drive, Windows says it's not
a valid folder...
My server receives 3 requests from the Windows Explorer : OPTIONS, PROPFIND, and again PROPFIND.
The server sends a response to all these requests, and if I snif the network traffic with RawCap, I can't see anything wrong in the XML response, nor in the HTTP headers...
I also sniffed the traffic when I tried with IIS or Apache, and there were a few differences, which I tried to correct, but nothing made a difference, the server is still not working as a network drive in Windows Explorer...
I also compared my code to other open-source WebDAV servers, but I can't figure out why it is not working...
I also tried to take the XML sent as a response from IIS or Apache, and to send it with my server, without success.
So, are there any specific things to implement to make it work with the Windows Explorer ?
Maybe I forgot a little thing that's needed, maybe someone can help me out...
The main code :
Module WebServer #Region "Main" Sub Main() Dim prefixes(0) As String prefixes(0) = "http://*:80/" ProcessRequests(prefixes) End Sub #End Region #Region "Request processing" Private Sub HandleRequest(context As HttpListenerContext) Dim sw = Stopwatch.StartNew() Dim response As HttpListenerResponse = Nothing Dim requestHandler As IMethodHandler = Nothing Console.WriteLine(String.Format("{0:hh:mm:ss} >>> ", DateTime.Now) + context.Request.HttpMethod + " Request from : " + context.Request.UserAgent) Console.WriteLine(" KeepAlive: {0}", context.Request.KeepAlive) Console.WriteLine(" Local end point: {0}", context.Request.LocalEndPoint.ToString()) Console.WriteLine(" Remote end point: {0}", context.Request.RemoteEndPoint.ToString()) Console.WriteLine(" Is local? {0}", context.Request.IsLocal) Console.WriteLine(" HTTP method: {0}", context.Request.HttpMethod) Console.WriteLine(" Protocol version: {0}", context.Request.ProtocolVersion) Console.WriteLine(" Is authenticated: {0}", context.Request.IsAuthenticated) Console.WriteLine(" Is secure: {0}", context.Request.IsSecureConnection) ' Create the response response = context.Response response.ContentType = "text/xml" response.ContentEncoding = System.Text.Encoding.UTF8 ' User authentication 'Dim identity As HttpListenerBasicIdentity = context.User.Identity 'If Not identity.Name.Equals("test") Or Not identity.Password.Equals("test") Then 'response.StatusCode = HttpStatusCode.Unauthorized 'response.AddHeader("WWW-Authenticate", "Basic realm=""Server""") 'Else Select Case context.Request.HttpMethod.ToUpper() Case "OPTIONS" requestHandler = New OPTIONS_Handler(context) Case "GET" requestHandler = New GET_Handler(context) Case "HEAD" requestHandler = New HEAD_Handler(context) Case "PUT" requestHandler = New PUT_Handler(context) Case "POST" requestHandler = New POST_Handler(context) Case "DELETE" requestHandler = New DELETE_Handler(context) Case "COPY" requestHandler = New COPY_Handler(context) Case "MOVE" requestHandler = New MOVE_Handler(context) Case "MKCOL" requestHandler = New MKCOL_Handler(context) Case "PROPFIND" requestHandler = New PROPFIND_Handler(context) Case "PROPPATCH" requestHandler = New PROPPATCH_Handler(context) Case Else Console.WriteLine("Unknown Command") response.StatusCode = HttpStatusCode.NotImplemented End Select If Not requestHandler Is Nothing Then requestHandler.processRequest() End If If response IsNot Nothing Then response.Close() End If Console.WriteLine("Time : {0}", sw.Elapsed) 'End If End Sub Private Sub ProcessRequests(ByVal prefixes() As String) If Not System.Net.HttpListener.IsSupported Then Console.WriteLine( _ "Windows XP SP2, Server 2003, or higher is required to " & _"use the HttpListener class.") Exit Sub End If ' URI prefixes are required If prefixes Is Nothing OrElse prefixes.Length = 0 Then Throw New ArgumentException("prefixes") End If ' Create a listener and add the prefixes Dim listener As System.Net.HttpListener = New System.Net.HttpListener() For Each s As String In prefixes listener.Prefixes.Add(s) Next Try ' Start the listener to begin listening for requests and set authentication 'listener.AuthenticationSchemes = AuthenticationSchemes.Basic listener.Start() Console.WriteLine("Listening...") While True Try ' Note: GetContext blocks while waiting for a request Dim context As HttpListenerContext = listener.GetContext() Dim t As New Threading.Thread(Sub() HandleRequest(context)) t.Start() Catch ex As HttpListenerException Console.WriteLine(ex.Message) End Try End While Catch ex As HttpListenerException Console.WriteLine(ex.Message) Finally ' Stop listening for requests listener.Close() Console.WriteLine("Done Listening...") End Try End Sub #End Region End Module
The OPTIONS handler :
Public Class OPTIONS_Handler : Implements IMethodHandler Private context As HttpListenerContext Private response As HttpListenerResponse Public Sub New(ByVal ctxt As HttpListenerContext) context = ctxt response = context.Response End Sub Public Sub processRequest() Implements IMethodHandler.processRequest response.AppendHeader("Allow", "OPTIONS, GET, HEAD, POST, PUT, DELETE, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH") response.AppendHeader("Public", "OPTIONS, GET, HEAD, POST, PUT, DELETE, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH") response.AppendHeader("DAV", "1, 2, ordered-collections") response.AppendHeader("Versioning-Support", "DAV:basicversioning") response.AppendHeader("MS-Author-Via", "DAV") 'response.AppendHeader("X_MSDAVEXT", "1") 'response.AppendHeader("Translate", "f") response.StatusCode = HttpStatusCode.OK End Sub End Class
And the PROPFIND handler :
Public Class PROPFIND_Handler : Implements IMethodHandler Private context As HttpListenerContext Private response As HttpListenerResponse Public Sub New(ByVal ctxt As HttpListenerContext) context = ctxt response = context.Response End Sub Public Sub processRequest() Implements IMethodHandler.processRequest Try context.Response.SendChunked = False ' Check if the XML request is valid and well-formed Dim request As HttpListenerRequest = context.Request Dim reader As XmlReader = XmlReader.Create(request.InputStream) ' See if the inputstream includes some data. If not --> Exception Dim buffer(16 * 1024) As Byte Dim read As Integer Dim memstr As New MemoryStream While (read = request.InputStream.Read(buffer, 0, buffer.Length)) > 0 memstr.Write(buffer, 0, read) End While If memstr.Length <> 0 Then Dim doc As XDocument = XDocument.Load(reader) End If response.StatusCode = 207 Dim xmlWriter As XmlTextWriter = New XmlTextWriter("test.xml", New System.Text.UTF8Encoding(False)) 'xmlWriter.Formatting = Formatting.Indented xmlWriter.WriteStartDocument() xmlWriter.WriteStartElement("D:multistatus") xmlWriter.WriteAttributeString("xmlns:D", "DAV:") xmlWriter.WriteAttributeString("xmlns:b", "urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882") ' Get the requested URI Dim requestedURI As String = context.Request.Url.LocalPath ' Get the files and folders list from the mapped folder Dim mappedFolderInfo If requestedURI.Equals("/") Then mappedFolderInfo = New IO.DirectoryInfo("MappedFolder") Else mappedFolderInfo = New IO.DirectoryInfo("MappedFolder" + requestedURI) End If Dim filesList As IO.FileInfo() = mappedFolderInfo.GetFiles() Dim foldersList As IO.DirectoryInfo() = mappedFolderInfo.GetDirectories() ' List all the files and folders and build the corresponding XML response Dim tempFile As IO.FileInfo Dim tempFolder As IO.DirectoryInfo For Each tempFolder In foldersList Dim webDavItem As New WebDAVItem() webDavItem.CollectionYN = True webDavItem.ContentLength = 0 webDavItem.CreationDate = tempFolder.CreationTime webDavItem.LastModifDate = tempFolder.LastWriteTime webDavItem.Name = tempFolder.Name + "/" webDavItem.Path = requestedURI webDavItem.BuildXmlResponse(xmlWriter) Next For Each tempFile In filesList Dim webDavItem As New WebDAVItem() webDavItem.CollectionYN = False webDavItem.ContentLength = tempFile.Length webDavItem.CreationDate = tempFile.CreationTime webDavItem.LastModifDate = tempFile.LastWriteTime webDavItem.Name = tempFile.Name webDavItem.Path = requestedURI webDavItem.BuildXmlResponse(xmlWriter) Next xmlWriter.Close() Dim f As FileStream = File.OpenRead("test.xml") Dim fileData(f.Length) As Byte response.ContentLength64 = f.Length f.Read(fileData, 0, f.Length) f.Close() response.OutputStream.Write(fileData, 0, response.ContentLength64) Catch ex As Exception Console.WriteLine("Exception while handling PROPFIND : " + ex.Message) response.StatusCode = HttpStatusCode.BadRequest End Try End Sub End Class
Thank you very much !