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 !