refactor: update dependencies and remove unused code
- Added new dependencies: `adler2`, `crc32fast`, `flate2`, `miniz_oxide`, and `libredox`. - Updated existing dependencies: `tokio-rustls` to version 0.26.4 and `filetime` to version 0.2.27. - Removed the `backup.rs` file as it is no longer needed. - Introduced tests for configuration and credential management. - Enhanced the `identity` module to generate W3C compliant DID documents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
2a867b32a8
commit
6fee6befed
@@ -15,6 +15,26 @@ interface FileBrowserListResponse {
|
||||
sorting: { by: string; asc: boolean }
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a path: resolve `.` and `..`, reject traversal outside root.
|
||||
* Always returns a path starting with `/` and never containing `..`.
|
||||
*/
|
||||
export function sanitizePath(path: string): string {
|
||||
const segments = path.split('/').filter(Boolean)
|
||||
const resolved: string[] = []
|
||||
|
||||
for (const seg of segments) {
|
||||
if (seg === '.') continue
|
||||
if (seg === '..') {
|
||||
resolved.pop() // go up one level, but never past root
|
||||
} else {
|
||||
resolved.push(seg)
|
||||
}
|
||||
}
|
||||
|
||||
return '/' + resolved.join('/')
|
||||
}
|
||||
|
||||
class FileBrowserClient {
|
||||
private token: string | null = null
|
||||
private baseUrl: string
|
||||
@@ -38,6 +58,8 @@ class FileBrowserClient {
|
||||
const text = await res.text()
|
||||
// FileBrowser returns the JWT as a plain string (possibly quoted)
|
||||
this.token = text.replace(/^"|"$/g, '')
|
||||
// Store token as cookie for img/video/audio src requests (avoids token in URL)
|
||||
document.cookie = `auth=${this.token}; path=/app/filebrowser; SameSite=Strict`
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
@@ -51,7 +73,7 @@ class FileBrowserClient {
|
||||
}
|
||||
|
||||
async listDirectory(path: string): Promise<FileBrowserItem[]> {
|
||||
const safePath = path.startsWith('/') ? path : `/${path}`
|
||||
const safePath = sanitizePath(path)
|
||||
const res = await fetch(`${this.baseUrl}/api/resources${safePath}`, {
|
||||
headers: this.headers(),
|
||||
})
|
||||
@@ -63,14 +85,47 @@ class FileBrowserClient {
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use fetchBlobUrl() instead to avoid exposing tokens in URLs.
|
||||
* Returns a plain URL (no token in query string).
|
||||
*/
|
||||
downloadUrl(path: string): string {
|
||||
const safePath = path.startsWith('/') ? path : `/${path}`
|
||||
// Token is passed as query param for direct downloads (img src, audio src, etc.)
|
||||
return `${this.baseUrl}/api/raw${safePath}?auth=${this.token}`
|
||||
const safePath = sanitizePath(path)
|
||||
return `${this.baseUrl}/api/raw${safePath}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a file as a blob URL using header-based auth (no token in URL).
|
||||
* Use this for img/video/audio src attributes and download links.
|
||||
*/
|
||||
async fetchBlobUrl(path: string): Promise<string> {
|
||||
const safePath = sanitizePath(path)
|
||||
const res = await fetch(`${this.baseUrl}/api/raw${safePath}`, {
|
||||
headers: this.headers(),
|
||||
})
|
||||
if (!res.ok) throw new Error(`Failed to fetch file: ${res.status}`)
|
||||
const blob = await res.blob()
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a file download using header-based auth (no token in URL).
|
||||
*/
|
||||
async downloadFile(path: string): Promise<void> {
|
||||
const blobUrl = await this.fetchBlobUrl(path)
|
||||
const filename = path.split('/').pop() || 'download'
|
||||
const a = document.createElement('a')
|
||||
a.href = blobUrl
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
}
|
||||
|
||||
async upload(dirPath: string, file: File): Promise<void> {
|
||||
const safePath = dirPath.endsWith('/') ? dirPath : `${dirPath}/`
|
||||
const sanitized = sanitizePath(dirPath)
|
||||
const safePath = sanitized.endsWith('/') ? sanitized : `${sanitized}/`
|
||||
const encodedName = encodeURIComponent(file.name)
|
||||
const res = await fetch(
|
||||
`${this.baseUrl}/api/resources${safePath}${encodedName}?override=true`,
|
||||
@@ -87,8 +142,10 @@ class FileBrowserClient {
|
||||
}
|
||||
|
||||
async createFolder(parentPath: string, name: string): Promise<void> {
|
||||
const safePath = parentPath.endsWith('/') ? parentPath : `${parentPath}/`
|
||||
const res = await fetch(`${this.baseUrl}/api/resources${safePath}${name}/`, {
|
||||
const sanitized = sanitizePath(parentPath)
|
||||
const safePath = sanitized.endsWith('/') ? sanitized : `${sanitized}/`
|
||||
const sanitizedName = name.replace(/\.\./g, '').replace(/\//g, '')
|
||||
const res = await fetch(`${this.baseUrl}/api/resources${safePath}${sanitizedName}/`, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
})
|
||||
@@ -96,7 +153,7 @@ class FileBrowserClient {
|
||||
}
|
||||
|
||||
async deleteItem(path: string): Promise<void> {
|
||||
const safePath = path.startsWith('/') ? path : `/${path}`
|
||||
const safePath = sanitizePath(path)
|
||||
const res = await fetch(`${this.baseUrl}/api/resources${safePath}`, {
|
||||
method: 'DELETE',
|
||||
headers: this.headers(),
|
||||
@@ -142,7 +199,7 @@ class FileBrowserClient {
|
||||
if (!this.isTextFile(path)) {
|
||||
throw new Error(`Cannot read binary file: ${path}`)
|
||||
}
|
||||
const safePath = path.startsWith('/') ? path : `/${path}`
|
||||
const safePath = sanitizePath(path)
|
||||
const res = await fetch(`${this.baseUrl}/api/raw${safePath}`, {
|
||||
headers: this.headers(),
|
||||
})
|
||||
@@ -156,15 +213,16 @@ class FileBrowserClient {
|
||||
}
|
||||
|
||||
async rename(oldPath: string, newName: string): Promise<void> {
|
||||
const safePath = oldPath.startsWith('/') ? oldPath : `/${oldPath}`
|
||||
const safePath = sanitizePath(oldPath)
|
||||
const dir = safePath.substring(0, safePath.lastIndexOf('/') + 1)
|
||||
const sanitizedName = newName.replace(/\.\./g, '').replace(/\//g, '')
|
||||
const res = await fetch(`${this.baseUrl}/api/resources${safePath}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...this.headers(),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ destination: `${dir}${newName}` }),
|
||||
body: JSON.stringify({ destination: `${dir}${sanitizedName}` }),
|
||||
})
|
||||
if (!res.ok) throw new Error(`Rename failed: ${res.status}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user