all
This commit is contained in:
48
Downloads/Voltaserve/webdav/src/handler/handle-copy.ts
Normal file
48
Downloads/Voltaserve/webdav/src/handler/handle-copy.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import path from 'path'
|
||||
import { getTargetPath } from '@/helper/path'
|
||||
import { FileAPI } from '@/client/api'
|
||||
import { Token } from '@/client/idp'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method copies a resource from a source URL to a destination URL.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the source and destination paths from the headers or request body.
|
||||
- Use fs.copyFile() to copy the file from the source to the destination.
|
||||
- Set the response status code to 204 if successful or an appropriate error code if the source file is not found or encountered an error.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handleCopy(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
try {
|
||||
const api = new FileAPI(token)
|
||||
const sourceFile = await api.getByPath(decodeURIComponent(req.url))
|
||||
const targetFile = await api.getByPath(
|
||||
decodeURIComponent(path.dirname(getTargetPath(req)))
|
||||
)
|
||||
|
||||
if (sourceFile.workspaceId !== targetFile.workspaceId) {
|
||||
res.statusCode = 400
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
|
||||
const clones = await api.copy(targetFile.id, { ids: [sourceFile.id] })
|
||||
await api.rename(clones[0].id, {
|
||||
name: decodeURIComponent(path.basename(getTargetPath(req))),
|
||||
})
|
||||
|
||||
res.statusCode = 204
|
||||
res.end()
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handleCopy
|
34
Downloads/Voltaserve/webdav/src/handler/handle-delete.ts
Normal file
34
Downloads/Voltaserve/webdav/src/handler/handle-delete.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import { FileAPI } from '@/client/api'
|
||||
import { Token } from '@/client/idp'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method deletes a resource identified by the URL.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the file path from the URL.
|
||||
- Use fs.unlink() to delete the file.
|
||||
- Set the response status code to 204 if successful or an appropriate error code if the file is not found.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handleDelete(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
try {
|
||||
const api = new FileAPI(token)
|
||||
const file = await api.getByPath(decodeURIComponent(req.url))
|
||||
|
||||
await api.delete(file.id)
|
||||
|
||||
res.statusCode = 204
|
||||
res.end()
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handleDelete
|
68
Downloads/Voltaserve/webdav/src/handler/handle-get.ts
Normal file
68
Downloads/Voltaserve/webdav/src/handler/handle-get.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import fs, { createReadStream, rmSync } from 'fs'
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { FileAPI } from '@/client/api'
|
||||
import { Token } from '@/client/idp'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method retrieves the content of a resource identified by the URL.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the file path from the URL.
|
||||
- Create a read stream from the file and pipe it to the response stream.
|
||||
- Set the response status code to 200 if successful or an appropriate error code if the file is not found.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handleGet(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
try {
|
||||
const api = new FileAPI(token)
|
||||
const file = await api.getByPath(decodeURIComponent(req.url))
|
||||
|
||||
/* TODO: This should be optimized for the case when there is a range header,
|
||||
only a partial file should be fetched, here we are fetching the whole file
|
||||
which is not ideal. */
|
||||
const outputPath = path.join(os.tmpdir(), uuidv4())
|
||||
await api.downloadOriginal(file, outputPath)
|
||||
|
||||
const stat = fs.statSync(outputPath)
|
||||
const rangeHeader = req.headers.range
|
||||
if (rangeHeader) {
|
||||
const [start, end] = rangeHeader.replace(/bytes=/, '').split('-')
|
||||
const rangeStart = parseInt(start, 10) || 0
|
||||
const rangeEnd = parseInt(end, 10) || stat.size - 1
|
||||
const chunkSize = rangeEnd - rangeStart + 1
|
||||
res.writeHead(206, {
|
||||
'Content-Range': `bytes ${rangeStart}-${rangeEnd}/${stat.size}`,
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Length': chunkSize.toString(),
|
||||
'Content-Type': 'application/octet-stream',
|
||||
})
|
||||
createReadStream(outputPath, {
|
||||
start: rangeStart,
|
||||
end: rangeEnd,
|
||||
})
|
||||
.pipe(res)
|
||||
.on('finish', () => rmSync(outputPath))
|
||||
} else {
|
||||
res.writeHead(200, {
|
||||
'Content-Length': stat.size.toString(),
|
||||
'Content-Type': 'application/octet-stream',
|
||||
})
|
||||
createReadStream(outputPath)
|
||||
.pipe(res)
|
||||
.on('finish', () => rmSync(outputPath))
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handleGet
|
37
Downloads/Voltaserve/webdav/src/handler/handle-head.ts
Normal file
37
Downloads/Voltaserve/webdav/src/handler/handle-head.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import { FileAPI, FileType } from '@/client/api'
|
||||
import { Token } from '@/client/idp'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method is similar to GET but only retrieves the metadata of a resource, without returning the actual content.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the file path from the URL.
|
||||
- Retrieve the file metadata using fs.stat().
|
||||
- Set the response status code to 200 if successful or an appropriate error code if the file is not found.
|
||||
- Set the Content-Length header with the file size.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handleHead(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
try {
|
||||
const file = await new FileAPI(token).getByPath(decodeURIComponent(req.url))
|
||||
if (file.type === FileType.File) {
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Length', file.original.size)
|
||||
res.end()
|
||||
} else {
|
||||
res.statusCode = 200
|
||||
res.end()
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handleHead
|
39
Downloads/Voltaserve/webdav/src/handler/handle-mkcol.ts
Normal file
39
Downloads/Voltaserve/webdav/src/handler/handle-mkcol.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import path from 'path'
|
||||
import { File, FileAPI } from '@/client/api'
|
||||
import { Token } from '@/client/idp'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method creates a new collection (directory) at the specified URL.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the directory path from the URL.
|
||||
- Use fs.mkdir() to create the directory.
|
||||
- Set the response status code to 201 if created or an appropriate error code if the directory already exists or encountered an error.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handleMkcol(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
let directory: File
|
||||
try {
|
||||
const api = new FileAPI(token)
|
||||
directory = await api.getByPath(decodeURIComponent(path.dirname(req.url)))
|
||||
await api.createFolder({
|
||||
workspaceId: directory.workspaceId,
|
||||
parentId: directory.id,
|
||||
name: decodeURIComponent(path.basename(req.url)),
|
||||
})
|
||||
|
||||
res.statusCode = 201
|
||||
res.end()
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handleMkcol
|
57
Downloads/Voltaserve/webdav/src/handler/handle-move.ts
Normal file
57
Downloads/Voltaserve/webdav/src/handler/handle-move.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import path from 'path'
|
||||
import { FileAPI } from '@/client/api'
|
||||
import { Token } from '@/client/idp'
|
||||
import { getTargetPath } from '@/helper/path'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method moves or renames a resource from a source URL to a destination URL.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the source and destination paths from the headers or request body.
|
||||
- Use fs.rename() to move or rename the file from the source to the destination.
|
||||
- Set the response status code to 204 if successful or an appropriate error code if the source file is not found or encountered an error.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handleMove(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
try {
|
||||
const sourcePath = decodeURIComponent(req.url)
|
||||
const targetPath = decodeURIComponent(getTargetPath(req))
|
||||
|
||||
const api = new FileAPI(token)
|
||||
const sourceFile = await api.getByPath(decodeURIComponent(req.url))
|
||||
const targetFile = await api.getByPath(
|
||||
decodeURIComponent(path.dirname(getTargetPath(req)))
|
||||
)
|
||||
|
||||
if (sourceFile.workspaceId !== targetFile.workspaceId) {
|
||||
res.statusCode = 400
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
sourcePath.split('/').length === targetPath.split('/').length &&
|
||||
path.dirname(sourcePath) === path.dirname(targetPath)
|
||||
) {
|
||||
await api.rename(sourceFile.id, {
|
||||
name: decodeURIComponent(path.basename(targetPath)),
|
||||
})
|
||||
} else {
|
||||
await api.move(targetFile.id, { ids: [sourceFile.id] })
|
||||
}
|
||||
|
||||
res.statusCode = 204
|
||||
res.end()
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handleMove
|
21
Downloads/Voltaserve/webdav/src/handler/handle-options.ts
Normal file
21
Downloads/Voltaserve/webdav/src/handler/handle-options.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
/*
|
||||
This method should respond with the allowed methods and capabilities of the server.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Set the response status code to 200.
|
||||
- Set the Allow header to specify the supported methods, such as OPTIONS, GET, PUT, DELETE, etc.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handleOptions(_: IncomingMessage, res: ServerResponse) {
|
||||
res.statusCode = 200
|
||||
res.setHeader(
|
||||
'Allow',
|
||||
'OPTIONS, GET, HEAD, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, PROPPATCH'
|
||||
)
|
||||
res.end()
|
||||
}
|
||||
|
||||
export default handleOptions
|
111
Downloads/Voltaserve/webdav/src/handler/handle-propfind.ts
Normal file
111
Downloads/Voltaserve/webdav/src/handler/handle-propfind.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import { FileAPI, FileType } from '@/client/api'
|
||||
import { Token } from '@/client/idp'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method retrieves properties and metadata of a resource.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the file path from the URL.
|
||||
- Use fs.stat() to retrieve the file metadata.
|
||||
- Format the response body in the desired XML format with the properties and metadata.
|
||||
- Set the response status code to 207 if successful or an appropriate error code if the file is not found or encountered an error.
|
||||
- Set the Content-Type header to indicate the XML format.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handlePropfind(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
try {
|
||||
const api = new FileAPI(token)
|
||||
const file = await api.getByPath(decodeURIComponent(req.url))
|
||||
if (file.type === FileType.File) {
|
||||
const responseXml = `
|
||||
<D:multistatus xmlns:D="DAV:">
|
||||
<D:response>
|
||||
<D:href>${encodeURIComponent(file.name)}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype></D:resourcetype>
|
||||
${
|
||||
file.original
|
||||
? `<D:getcontentlength>${file.original.size}</D:getcontentlength>`
|
||||
: ''
|
||||
}
|
||||
<D:creationdate>${new Date(
|
||||
file.createTime
|
||||
).toUTCString()}</D:creationdate>
|
||||
<D:getlastmodified>${new Date(
|
||||
file.updateTime
|
||||
).toUTCString()}</D:getlastmodified>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>`
|
||||
res.statusCode = 207
|
||||
res.setHeader('Content-Type', 'application/xml; charset=utf-8')
|
||||
res.end(responseXml)
|
||||
} else if (file.type === FileType.Folder) {
|
||||
const list = await api.listByPath(decodeURIComponent(req.url))
|
||||
const responseXml = `
|
||||
<D:multistatus xmlns:D="DAV:">
|
||||
<D:response>
|
||||
<D:href>${req.url}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype><D:collection/></D:resourcetype>
|
||||
<D:getcontentlength>0</D:getcontentlength>
|
||||
<D:getlastmodified>${new Date(
|
||||
file.updateTime
|
||||
).toUTCString()}</D:getlastmodified>
|
||||
<D:creationdate>${new Date(
|
||||
file.createTime
|
||||
).toUTCString()}</D:creationdate>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
${list
|
||||
.map((item) => {
|
||||
return `
|
||||
<D:response>
|
||||
<D:href>${req.url}${encodeURIComponent(item.name)}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<D:resourcetype>${
|
||||
item.type === FileType.Folder ? '<D:collection/>' : ''
|
||||
}</D:resourcetype>
|
||||
${
|
||||
item.type === FileType.File && item.original
|
||||
? `<D:getcontentlength>${item.original.size}</D:getcontentlength>`
|
||||
: ''
|
||||
}
|
||||
<D:getlastmodified>${new Date(
|
||||
item.updateTime
|
||||
).toUTCString()}</D:getlastmodified>
|
||||
<D:creationdate>${new Date(
|
||||
item.createTime
|
||||
).toUTCString()}</D:creationdate>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
`
|
||||
})
|
||||
.join('')}
|
||||
</D:multistatus>`
|
||||
res.statusCode = 207
|
||||
res.setHeader('Content-Type', 'application/xml; charset=utf-8')
|
||||
res.end(responseXml)
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handlePropfind
|
34
Downloads/Voltaserve/webdav/src/handler/handle-proppatch.ts
Normal file
34
Downloads/Voltaserve/webdav/src/handler/handle-proppatch.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
/*
|
||||
This method updates the properties of a resource.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Parse the request body to extract the properties to be updated.
|
||||
- Read the existing data from the file.
|
||||
- Parse the existing properties.
|
||||
- Merge the updated properties with the existing ones.
|
||||
- Format the updated properties and store them back in the file.
|
||||
- Set the response status code to 204 if successful or an appropriate error code if the file is not found or encountered an error.
|
||||
- Return the response.
|
||||
|
||||
In this example implementation, the handleProppatch() method first parses the XML
|
||||
payload containing the properties to be updated. Then, it reads the existing data from the file,
|
||||
parses the existing properties (assuming an XML format),
|
||||
merges the updated properties with the existing ones, and formats
|
||||
the properties back into the desired format (e.g., XML).
|
||||
|
||||
Finally, the updated properties are written back to the file.
|
||||
You can customize the parseProperties() and formatProperties()
|
||||
functions to match the specific property format you are using in your WebDAV server.
|
||||
|
||||
Note that this implementation assumes a simplified example and may require further
|
||||
customization based on your specific property format and requirements.
|
||||
*/
|
||||
async function handleProppatch(_: IncomingMessage, res: ServerResponse) {
|
||||
res.statusCode = 501
|
||||
res.end()
|
||||
}
|
||||
|
||||
export default handleProppatch
|
71
Downloads/Voltaserve/webdav/src/handler/handle-put.ts
Normal file
71
Downloads/Voltaserve/webdav/src/handler/handle-put.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import fs from 'fs'
|
||||
import { readFile } from 'fs/promises'
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Token } from '@/client/idp'
|
||||
import { File, FileAPI } from '@/client/api'
|
||||
import { handleError } from '@/infra/error'
|
||||
|
||||
/*
|
||||
This method creates or updates a resource with the provided content.
|
||||
|
||||
Example implementation:
|
||||
|
||||
- Extract the file path from the URL.
|
||||
- Create a write stream to the file.
|
||||
- Listen for the data event to write the incoming data to the file.
|
||||
- Listen for the end event to indicate the completion of the write stream.
|
||||
- Set the response status code to 201 if created or 204 if updated.
|
||||
- Return the response.
|
||||
*/
|
||||
async function handlePut(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
token: Token
|
||||
) {
|
||||
const api = new FileAPI(token)
|
||||
try {
|
||||
const directory = await api.getByPath(
|
||||
decodeURIComponent(path.dirname(req.url))
|
||||
)
|
||||
const outputPath = path.join(os.tmpdir(), uuidv4())
|
||||
const ws = fs.createWriteStream(outputPath)
|
||||
req.pipe(ws)
|
||||
ws.on('error', (err) => {
|
||||
console.error(err)
|
||||
res.statusCode = 500
|
||||
res.end()
|
||||
})
|
||||
ws.on('finish', async () => {
|
||||
try {
|
||||
res.statusCode = 201
|
||||
res.end()
|
||||
const blob = new Blob([await readFile(outputPath)])
|
||||
let existingFile: File | null = null
|
||||
try {
|
||||
existingFile = await api.getByPath(decodeURIComponent(req.url))
|
||||
// Delete existing file to simulate an overwrite
|
||||
await api.delete(existingFile.id)
|
||||
} catch {
|
||||
// Ignored
|
||||
}
|
||||
await api.upload({
|
||||
workspaceId: directory.workspaceId,
|
||||
parentId: directory.id,
|
||||
name: decodeURIComponent(path.basename(req.url)),
|
||||
blob,
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
} finally {
|
||||
fs.rmSync(outputPath)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
handleError(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
export default handlePut
|
Reference in New Issue
Block a user