mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-29 14:14:26 -05:00
MediaBackend: Add WebdavBackend
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
edf71cc177
commit
9043e39dc6
3 changed files with 146 additions and 0 deletions
139
src/media/backends/webdav-backend.ts
Normal file
139
src/media/backends/webdav-backend.ts
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import mediaConfiguration from '../../config/media.config';
|
||||||
|
import { ConsoleLoggerService } from '../../logger/console-logger.service';
|
||||||
|
import { MediaBackend } from '../media-backend.interface';
|
||||||
|
import { BackendData } from '../media-upload.entity';
|
||||||
|
import { MediaConfig } from '../../config/media.config';
|
||||||
|
import { MediaBackendError } from '../../errors/errors';
|
||||||
|
import { BackendType } from './backend-type.enum';
|
||||||
|
import fetch, { Response } from 'node-fetch';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WebdavBackend implements MediaBackend {
|
||||||
|
private config: MediaConfig['backend']['webdav'];
|
||||||
|
private authHeader: string;
|
||||||
|
private baseUrl: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly logger: ConsoleLoggerService,
|
||||||
|
@Inject(mediaConfiguration.KEY)
|
||||||
|
private mediaConfig: MediaConfig,
|
||||||
|
) {
|
||||||
|
this.logger.setContext(WebdavBackend.name);
|
||||||
|
if (mediaConfig.backend.use === BackendType.WEBDAV) {
|
||||||
|
this.config = mediaConfig.backend.webdav;
|
||||||
|
const url = new URL(this.config.connectionString);
|
||||||
|
const port = url.port !== '' ? `:${url.port}` : '';
|
||||||
|
this.baseUrl = `${url.protocol}//${url.hostname}${port}${url.pathname}`;
|
||||||
|
if (this.config.uploadDir && this.config.uploadDir !== '') {
|
||||||
|
this.baseUrl = WebdavBackend.joinURL(
|
||||||
|
this.baseUrl,
|
||||||
|
this.config.uploadDir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.authHeader = WebdavBackend.generateBasicAuthHeader(
|
||||||
|
url.username,
|
||||||
|
url.password,
|
||||||
|
);
|
||||||
|
fetch(this.baseUrl, {
|
||||||
|
method: 'PROPFIND',
|
||||||
|
headers: {
|
||||||
|
Accept: 'text/plain', // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
Authorization: this.authHeader, // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
Depth: '0', // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Can't access ${this.baseUrl}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error(`Can't access ${this.baseUrl}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveFile(
|
||||||
|
buffer: Buffer,
|
||||||
|
fileName: string,
|
||||||
|
): Promise<[string, BackendData]> {
|
||||||
|
try {
|
||||||
|
const contentLength = buffer.length;
|
||||||
|
await fetch(WebdavBackend.joinURL(this.baseUrl, '/', fileName), {
|
||||||
|
method: 'PUT',
|
||||||
|
body: buffer,
|
||||||
|
headers: {
|
||||||
|
Authorization: this.authHeader, // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
'Content-Type': 'application/octet-stream', // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
'Content-Length': `${contentLength}`, // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'If-None-Match': '*', // Don't overwrite already existing files
|
||||||
|
},
|
||||||
|
}).then((res) => WebdavBackend.checkStatus(res));
|
||||||
|
this.logger.log(`Uploaded file ${fileName}`, 'saveFile');
|
||||||
|
return [this.getUrl(fileName), null];
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error((e as Error).message, (e as Error).stack, 'saveFile');
|
||||||
|
throw new MediaBackendError(`Could not save '${fileName}' on WebDav`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFile(fileName: string, _: BackendData): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fetch(WebdavBackend.joinURL(this.baseUrl, '/', fileName), {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
Authorization: this.authHeader,
|
||||||
|
},
|
||||||
|
}).then((res) => WebdavBackend.checkStatus(res));
|
||||||
|
const url = this.getUrl(fileName);
|
||||||
|
this.logger.log(`Deleted ${url}`, 'saveFile');
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error((e as Error).message, (e as Error).stack, 'saveFile');
|
||||||
|
throw new MediaBackendError(`Could not delete '${fileName}' on WebDav`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUrl(fileName: string): string {
|
||||||
|
return WebdavBackend.joinURL(this.config.publicUrl, '/', fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static generateBasicAuthHeader(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): string {
|
||||||
|
const encoded = Buffer.from(`${username}:${password}`).toString('base64');
|
||||||
|
return `Basic ${encoded}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static joinURL(...urlParts: Array<string>): string {
|
||||||
|
return urlParts.reduce((output, next, index) => {
|
||||||
|
if (
|
||||||
|
index === 0 ||
|
||||||
|
next !== '/' ||
|
||||||
|
(next === '/' && output[output.length - 1] !== '/')
|
||||||
|
) {
|
||||||
|
output += next;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static checkStatus(res: Response): Response {
|
||||||
|
if (res.ok) {
|
||||||
|
// res.status >= 200 && res.status < 300
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
throw new MediaBackendError(res.statusText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import { MediaService } from './media.service';
|
||||||
import { S3Backend } from './backends/s3-backend';
|
import { S3Backend } from './backends/s3-backend';
|
||||||
import { ImgurBackend } from './backends/imgur-backend';
|
import { ImgurBackend } from './backends/imgur-backend';
|
||||||
import { AzureBackend } from './backends/azure-backend';
|
import { AzureBackend } from './backends/azure-backend';
|
||||||
|
import { WebdavBackend } from './backends/webdav-backend';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -31,6 +32,7 @@ import { AzureBackend } from './backends/azure-backend';
|
||||||
AzureBackend,
|
AzureBackend,
|
||||||
ImgurBackend,
|
ImgurBackend,
|
||||||
S3Backend,
|
S3Backend,
|
||||||
|
WebdavBackend,
|
||||||
],
|
],
|
||||||
exports: [MediaService],
|
exports: [MediaService],
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { ImgurBackend } from './backends/imgur-backend';
|
||||||
import { User } from '../users/user.entity';
|
import { User } from '../users/user.entity';
|
||||||
import { MediaUploadDto } from './media-upload.dto';
|
import { MediaUploadDto } from './media-upload.dto';
|
||||||
import { Note } from '../notes/note.entity';
|
import { Note } from '../notes/note.entity';
|
||||||
|
import { WebdavBackend } from './backends/webdav-backend';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MediaService {
|
export class MediaService {
|
||||||
|
@ -203,6 +204,8 @@ export class MediaService {
|
||||||
return BackendType.IMGUR;
|
return BackendType.IMGUR;
|
||||||
case 's3':
|
case 's3':
|
||||||
return BackendType.S3;
|
return BackendType.S3;
|
||||||
|
case 'webdav':
|
||||||
|
return BackendType.WEBDAV;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,6 +219,8 @@ export class MediaService {
|
||||||
return this.moduleRef.get(AzureBackend);
|
return this.moduleRef.get(AzureBackend);
|
||||||
case BackendType.IMGUR:
|
case BackendType.IMGUR:
|
||||||
return this.moduleRef.get(ImgurBackend);
|
return this.moduleRef.get(ImgurBackend);
|
||||||
|
case BackendType.WEBDAV:
|
||||||
|
return this.moduleRef.get(WebdavBackend);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue