--- /dev/null
+import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
+import { ApiResult, apiResultFromError, apiResultFromValue } from "../Result";
+
+type Response = {
+ identifier: string;
+ numberOfChunks: number;
+};
+
+export async function upload(
+ filename: string,
+ fileSize: number,
+ fileHash: string,
+ typeName: string,
+ context: string,
+): Promise<ApiResult<Response>> {
+ const url = new URL(window.WSC_API_URL + "index.php?api/rpc/core/files/upload");
+
+ const payload = {
+ filename,
+ fileSize,
+ fileHash,
+ typeName,
+ context,
+ };
+
+ let response: Response;
+ try {
+ response = (await prepareRequest(url).post(payload).fetchAsJson()) as Response;
+ } catch (e) {
+ return apiResultFromError(e);
+ }
+
+ return apiResultFromValue(response);
+}
import { StatusNotOk } from "WoltLabSuite/Core/Ajax/Error";
import { isPlainObject } from "WoltLabSuite/Core/Core";
import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector";
+import { upload as filesUpload } from "WoltLabSuite/Core/Api/Files/Upload";
import WoltlabCoreFileElement from "./woltlab-core-file";
-type PreflightResponse = {
- endpoints: string[];
-};
-
type UploadResponse =
| { completed: false }
| ({
const event = new CustomEvent<WoltlabCoreFileElement>("uploadStart", { detail: fileElement });
element.dispatchEvent(event);
- let response: PreflightResponse | undefined = undefined;
- try {
- response = (await prepareRequest(element.dataset.endpoint!)
- .post({
- filename: file.name,
- fileSize: file.size,
- fileHash,
- typeName,
- context: element.dataset.context,
- })
- .fetchAsJson()) as PreflightResponse;
- } catch (e) {
- if (e instanceof StatusNotOk) {
- const body = await e.response.clone().json();
- if (isPlainObject(body) && isPlainObject(body.error)) {
- console.log(body);
- return;
- } else {
- throw e;
- }
- } else {
- throw e;
- }
- } finally {
- if (response === undefined) {
+ const response = await filesUpload(file.name, file.size, fileHash, typeName, element.dataset.context || "");
+ if (!response.ok) {
+ const validationError = response.error.getValidationError();
+ if (validationError === undefined) {
fileElement.uploadFailed();
+
+ throw response.error;
}
+
+ console.log(validationError);
+ return;
}
- const { endpoints } = response;
+ const { identifier, numberOfChunks } = response.value;
- const chunkSize = Math.ceil(file.size / endpoints.length);
+ const chunkSize = Math.ceil(file.size / numberOfChunks);
// TODO: Can we somehow report any meaningful upload progress?
- for (let i = 0, length = endpoints.length; i < length; i++) {
+ for (let i = 0; i < numberOfChunks; i++) {
const start = i * chunkSize;
const end = start + chunkSize;
const chunk = file.slice(start, end);
- const endpoint = new URL(endpoints[i]);
+ // TODO fix the URL
+ throw new Error("TODO: fix the url");
+ const endpoint = new URL(String(i));
const checksum = await getSha256Hash(await chunk.arrayBuffer());
endpoint.searchParams.append("checksum", checksum);
--- /dev/null
+define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], function (require, exports, Backend_1, Result_1) {
+ "use strict";
+ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.upload = void 0;
+ async function upload(filename, fileSize, fileHash, typeName, context) {
+ const url = new URL(window.WSC_API_URL + "index.php?api/rpc/core/files/upload");
+ const payload = {
+ filename,
+ fileSize,
+ fileHash,
+ typeName,
+ context,
+ };
+ let response;
+ try {
+ response = (await (0, Backend_1.prepareRequest)(url).post(payload).fetchAsJson());
+ }
+ catch (e) {
+ return (0, Result_1.apiResultFromError)(e);
+ }
+ return (0, Result_1.apiResultFromValue)(response);
+ }
+ exports.upload = upload;
+});
-define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "WoltLabSuite/Core/Ajax/Error", "WoltLabSuite/Core/Core", "WoltLabSuite/Core/Helper/Selector"], function (require, exports, Backend_1, Error_1, Core_1, Selector_1) {
+define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "WoltLabSuite/Core/Helper/Selector", "WoltLabSuite/Core/Api/Files/Upload"], function (require, exports, Backend_1, Selector_1, Upload_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setup = void 0;
fileElement.dataset.filename = file.name;
const event = new CustomEvent("uploadStart", { detail: fileElement });
element.dispatchEvent(event);
- let response = undefined;
- try {
- response = (await (0, Backend_1.prepareRequest)(element.dataset.endpoint)
- .post({
- filename: file.name,
- fileSize: file.size,
- fileHash,
- typeName,
- context: element.dataset.context,
- })
- .fetchAsJson());
- }
- catch (e) {
- if (e instanceof Error_1.StatusNotOk) {
- const body = await e.response.clone().json();
- if ((0, Core_1.isPlainObject)(body) && (0, Core_1.isPlainObject)(body.error)) {
- console.log(body);
- return;
- }
- else {
- throw e;
- }
- }
- else {
- throw e;
- }
- }
- finally {
- if (response === undefined) {
+ const response = await (0, Upload_1.upload)(file.name, file.size, fileHash, typeName, element.dataset.context || "");
+ if (!response.ok) {
+ const validationError = response.error.getValidationError();
+ if (validationError === undefined) {
fileElement.uploadFailed();
+ throw response.error;
}
+ console.log(validationError);
+ return;
}
- const { endpoints } = response;
- const chunkSize = Math.ceil(file.size / endpoints.length);
+ const { identifier, numberOfChunks } = response.value;
+ const chunkSize = Math.ceil(file.size / numberOfChunks);
// TODO: Can we somehow report any meaningful upload progress?
- for (let i = 0, length = endpoints.length; i < length; i++) {
+ for (let i = 0; i < numberOfChunks; i++) {
const start = i * chunkSize;
const end = start + chunkSize;
const chunk = file.slice(start, end);
- const endpoint = new URL(endpoints[i]);
+ // TODO fix the URL
+ throw new Error("TODO: fix the url");
+ const endpoint = new URL(String(i));
const checksum = await getSha256Hash(await chunk.arrayBuffer());
endpoint.searchParams.append("checksum", checksum);
let response;
});
$eventHandler->register(ControllerCollecting::class, static function (ControllerCollecting $event) {
+ $event->register(new \wcf\system\endpoint\controller\core\files\PostUpload);
$event->register(new \wcf\system\endpoint\controller\core\messages\GetMentionSuggestions);
$event->register(new \wcf\system\endpoint\controller\core\sessions\DeleteSession);
});
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\files;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use wcf\data\file\temporary\FileTemporary;
+use wcf\data\file\temporary\FileTemporaryAction;
+use wcf\http\Helper;
+use wcf\system\endpoint\IController;
+use wcf\system\endpoint\PostRequest;
+use wcf\system\exception\SystemException;
+use wcf\system\exception\UserInputException;
+use wcf\system\file\processor\FileProcessor;
+use wcf\util\JSON;
+
+#[PostRequest('/core/files/upload')]
+final class PostUpload implements IController
+{
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $parameters = Helper::mapApiParameters($request, PostUploadParameters::class);
+
+ $fileProcessor = FileProcessor::getInstance()->forTypeName($parameters->typeName);
+ if ($fileProcessor === null) {
+ throw new UserInputException('typeName', 'unknown');
+ }
+
+ try {
+ $decodedContext = JSON::decode($parameters->context);
+ } catch (SystemException) {
+ throw new UserInputException('context', 'invalid');
+ }
+
+ $validationResult = $fileProcessor->acceptUpload($parameters->filename, $parameters->fileSize, $decodedContext);
+ if (!$validationResult->ok()) {
+ throw new UserInputException('filename', $validationResult->toString());
+ }
+
+ $numberOfChunks = FileTemporary::getNumberOfChunks($parameters->fileSize);
+ if ($numberOfChunks > FileTemporary::MAX_CHUNK_COUNT) {
+ throw new UserInputException('fileSize', 'tooLarge');
+ }
+
+ $fileTemporary = $this->createTemporaryFile($parameters, $numberOfChunks);
+
+ return new JsonResponse([
+ 'identifier' => $fileTemporary->identifier,
+ 'numberOfChunks' => $numberOfChunks,
+ ]);
+ }
+
+ private function createTemporaryFile(PostUploadParameters $parameters, int $numberOfChunks): FileTemporary
+ {
+ $identifier = \bin2hex(\random_bytes(20));
+
+ $action = new FileTemporaryAction([], 'create', [
+ 'data' => [
+ 'identifier' => $identifier,
+ 'time' => \TIME_NOW,
+ 'filename' => $parameters->filename,
+ 'fileSize' => $parameters->fileSize,
+ 'fileHash' => $parameters->fileHash,
+ 'typeName' => $parameters->typeName,
+ 'context' => $parameters->context,
+ 'chunks' => \str_repeat('0', $numberOfChunks),
+ ],
+ ]);
+
+ return $action->executeAction()['returnValues'];
+ }
+}
+
+/** @internal */
+final class PostUploadParameters
+{
+ public function __construct(
+ /** @var non-empty-string */
+ public readonly string $filename,
+
+ /** @var positive-int **/
+ public readonly int $fileSize,
+
+ /** @var non-empty-string */
+ public readonly string $fileHash,
+
+ /** @var non-empty-string */
+ public readonly string $typeName,
+
+ /** @var non-empty-string */
+ public readonly string $context,
+ ) {
+ }
+}