<?php

/**
 * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */
namespace OCA\WorkflowEngine\Check;

use OC\Files\Storage\Local;
use OCA\WorkflowEngine\Entity\File;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\Storage\IStorage;
use OCP\IL10N;
use OCP\IRequest;
use OCP\WorkflowEngine\IFileCheck;

class FileMimeType extends AbstractStringCheck implements IFileCheck {
	use TFileCheck {
		setFileInfo as _setFileInfo;
	}

	/** @var array */
	protected $mimeType;

	/**
	 * @param IL10N $l
	 * @param IRequest $request
	 * @param IMimeTypeDetector $mimeTypeDetector
	 */
	public function __construct(
		IL10N $l,
		protected IRequest $request,
		protected IMimeTypeDetector $mimeTypeDetector,
	) {
		parent::__construct($l);
	}

	/**
	 * @param IStorage $storage
	 * @param string $path
	 * @param bool $isDir
	 */
	public function setFileInfo(IStorage $storage, string $path, bool $isDir = false): void {
		$this->_setFileInfo($storage, $path, $isDir);
		if (!isset($this->mimeType[$this->storage->getId()][$this->path])
			|| $this->mimeType[$this->storage->getId()][$this->path] === '') {
			if ($isDir) {
				$this->mimeType[$this->storage->getId()][$this->path] = 'httpd/unix-directory';
			} else {
				$this->mimeType[$this->storage->getId()][$this->path] = null;
			}
		}
	}

	/**
	 * The mimetype is only cached if the file has a valid mimetype. Otherwise files access
	 * control will cache "application/octet-stream" for all the target node on:
	 * rename, move, copy and all other methods which create a new item
	 *
	 * To check this:
	 * 1. Add an automated tagging rule which tags on mimetype NOT "httpd/unix-directory"
	 * 2. Add an access control rule which checks for any mimetype
	 * 3. Create a folder and rename it, the folder should not be tagged, but it is
	 *
	 * @param string $storageId
	 * @param string|null $path
	 * @param string $mimeType
	 * @return string
	 */
	protected function cacheAndReturnMimeType(string $storageId, ?string $path, string $mimeType): string {
		if ($path !== null && $mimeType !== 'application/octet-stream') {
			$this->mimeType[$storageId][$path] = $mimeType;
		}

		return $mimeType;
	}

	/**
	 * Make sure that even though the content based check returns an application/octet-stream can still be checked based on mimetypemappings of their extension
	 *
	 * @param string $operator
	 * @param string $value
	 * @return bool
	 */
	public function executeCheck($operator, $value) {
		return $this->executeStringCheck($operator, $value, $this->getActualValue());
	}

	/**
	 * @return string
	 */
	protected function getActualValue() {
		if ($this->mimeType[$this->storage->getId()][$this->path] !== null) {
			return $this->mimeType[$this->storage->getId()][$this->path];
		}
		$cacheEntry = $this->storage->getCache()->get($this->path);
		if ($cacheEntry && $cacheEntry->getMimeType() !== 'application/octet-stream') {
			return $this->cacheAndReturnMimeType($this->storage->getId(), $this->path, $cacheEntry->getMimeType());
		}

		if ($this->storage->file_exists($this->path)
			&& $this->storage->filesize($this->path)
			&& $this->storage->instanceOfStorage(Local::class)
		) {
			$path = $this->storage->getLocalFile($this->path);
			$mimeType = $this->mimeTypeDetector->detectContent($path);
			return $this->cacheAndReturnMimeType($this->storage->getId(), $this->path, $mimeType);
		}

		if ($this->isWebDAVRequest() || $this->isPublicWebDAVRequest()) {
			// Creating a folder
			if ($this->request->getMethod() === 'MKCOL') {
				return 'httpd/unix-directory';
			}
		}

		// We do not cache this, as the file did not exist yet.
		// In case it does in the future, we will check with detectContent()
		// again to get the real mimetype of the content, rather than
		// guessing it from the path.
		return $this->mimeTypeDetector->detectPath($this->path);
	}

	/**
	 * @return bool
	 */
	protected function isWebDAVRequest() {
		return substr($this->request->getScriptName(), 0 - strlen('/remote.php')) === '/remote.php' && (
			$this->request->getPathInfo() === '/webdav'
			|| str_starts_with($this->request->getPathInfo() ?? '', '/webdav/')
			|| $this->request->getPathInfo() === '/dav/files'
			|| str_starts_with($this->request->getPathInfo() ?? '', '/dav/files/')
			|| $this->request->getPathInfo() === '/dav/uploads'
			|| str_starts_with($this->request->getPathInfo() ?? '', '/dav/uploads/')
		);
	}

	/**
	 * @return bool
	 */
	protected function isPublicWebDAVRequest() {
		return substr($this->request->getScriptName(), 0 - strlen('/public.php')) === '/public.php' && (
			$this->request->getPathInfo() === '/webdav'
			|| str_starts_with($this->request->getPathInfo() ?? '', '/webdav/')
		);
	}

	public function supportedEntities(): array {
		return [ File::class ];
	}

	public function isAvailableForScope(int $scope): bool {
		return true;
	}
}
