Add folder permissions UI and WebDAV protocol support
Permissions UI: - FolderPermissionsDialog component with public/private toggle, role/user permission management, and access level badges - Integrated into file manager toolbar (visible for folder admins) - Backend returns accessLevel in folder detail endpoint WebDAV server: - Full WebDAV protocol at /webdav/ with Basic Auth (existing credentials) - PROPFIND, GET, PUT, DELETE, MKCOL, COPY, MOVE, LOCK/UNLOCK support - Permission-checked against existing folder access model - In-memory lock stubs for Windows client compatibility - 22 API integration tests covering all operations Also fixes canAccess to check folder creator (was missing).
This commit is contained in:
104
packages/backend/src/utils/webdav-xml.ts
Normal file
104
packages/backend/src/utils/webdav-xml.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* WebDAV XML response builders.
|
||||
* Generates DAV-compliant XML without external dependencies.
|
||||
*/
|
||||
|
||||
export interface DavResource {
|
||||
href: string
|
||||
isCollection: boolean
|
||||
displayName: string
|
||||
contentType?: string
|
||||
contentLength?: number
|
||||
lastModified?: Date
|
||||
createdAt?: Date
|
||||
etag?: string
|
||||
}
|
||||
|
||||
function escapeXml(str: string): string {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
function formatRfc1123(date: Date): string {
|
||||
return date.toUTCString()
|
||||
}
|
||||
|
||||
function formatIso8601(date: Date): string {
|
||||
return date.toISOString()
|
||||
}
|
||||
|
||||
function buildResourceResponse(resource: DavResource): string {
|
||||
const props: string[] = []
|
||||
|
||||
if (resource.isCollection) {
|
||||
props.push('<D:resourcetype><D:collection/></D:resourcetype>')
|
||||
} else {
|
||||
props.push('<D:resourcetype/>')
|
||||
}
|
||||
|
||||
props.push(`<D:displayname>${escapeXml(resource.displayName)}</D:displayname>`)
|
||||
|
||||
if (resource.contentType && !resource.isCollection) {
|
||||
props.push(`<D:getcontenttype>${escapeXml(resource.contentType)}</D:getcontenttype>`)
|
||||
}
|
||||
|
||||
if (resource.contentLength != null && !resource.isCollection) {
|
||||
props.push(`<D:getcontentlength>${resource.contentLength}</D:getcontentlength>`)
|
||||
}
|
||||
|
||||
if (resource.lastModified) {
|
||||
props.push(`<D:getlastmodified>${formatRfc1123(resource.lastModified)}</D:getlastmodified>`)
|
||||
}
|
||||
|
||||
if (resource.createdAt) {
|
||||
props.push(`<D:creationdate>${formatIso8601(resource.createdAt)}</D:creationdate>`)
|
||||
}
|
||||
|
||||
if (resource.etag) {
|
||||
props.push(`<D:getetag>"${escapeXml(resource.etag)}"</D:getetag>`)
|
||||
}
|
||||
|
||||
return `<D:response>
|
||||
<D:href>${escapeXml(resource.href)}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
${props.join('\n')}
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>`
|
||||
}
|
||||
|
||||
export function buildMultistatus(resources: DavResource[]): string {
|
||||
const responses = resources.map(buildResourceResponse).join('\n')
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:multistatus xmlns:D="DAV:">
|
||||
${responses}
|
||||
</D:multistatus>`
|
||||
}
|
||||
|
||||
export function buildLockResponse(lockToken: string, owner: string, timeout: number): string {
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:prop xmlns:D="DAV:">
|
||||
<D:lockdiscovery>
|
||||
<D:activelock>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:depth>infinity</D:depth>
|
||||
<D:owner><D:href>${escapeXml(owner)}</D:href></D:owner>
|
||||
<D:timeout>Second-${timeout}</D:timeout>
|
||||
<D:locktoken><D:href>${escapeXml(lockToken)}</D:href></D:locktoken>
|
||||
</D:activelock>
|
||||
</D:lockdiscovery>
|
||||
</D:prop>`
|
||||
}
|
||||
|
||||
export function buildErrorResponse(statusCode: number, message: string): string {
|
||||
return `<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:error xmlns:D="DAV:">
|
||||
<D:message>${escapeXml(message)}</D:message>
|
||||
</D:error>`
|
||||
}
|
||||
Reference in New Issue
Block a user