aboutsummaryrefslogtreecommitdiffstats
path: root/lib/filesystem.php
blob: c69970467f588996b6923fbf7a3c934bb2853166 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96<
<?php

declare(strict_types=1);
/**
 * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
 * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
 * SPDX-License-Identifier: AGPL-3.0-only
 */
namespace OC\Security;

use OCP\ICertificate;

class Certificate implements ICertificate {
	protected string $name;

	protected ?string $commonName;

	protected ?string $organization;


	protected \DateTime $issueDate;

	protected \DateTime $expireDate;

	protected ?string $issuerName;

	protected ?string $issuerOrganization;

	/**
	 * @param string $data base64 encoded certificate
	 * @throws \Exception If the certificate could not get parsed
	 */
	public function __construct(string $data, string $name) {
		$this->name = $name;
		$gmt = new \DateTimeZone('GMT');

		// If string starts with "file://" ignore the certificate
		$query = 'file://';
		if (strtolower(substr($data, 0, strlen($query))) === $query) {
			throw new \Exception('Certificate could not get parsed.');
		}

		$info = openssl_x509_parse($data);
		if (!is_array($info)) {
			// There is a non-standardized certificate format only used by OpenSSL. Replace all
			// separators and try again.
			$data = str_replace(
				['-----BEGIN TRUSTED CERTIFICATE-----', '-----END TRUSTED CERTIFICATE-----'],
				['-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----'],
				$data,
			);
			$info = openssl_x509_parse($data);
		}
		if (!is_array($info)) {
			throw new \Exception('Certificate could not get parsed.');
		}

		$this->commonName = $info['subject']['CN'] ?? null;
		$this->organization = $info['subject']['O'] ?? null;
		$this->issueDate = new \DateTime('@' . $info['validFrom_time_t'], $gmt);
		$this->expireDate = new \DateTime('@' . $info['validTo_time_t'], $gmt);
		$this->issuerName = $info['issuer']['CN'] ?? null;
		$this->issuerOrganization = $info['issuer']['O'] ?? null;
	}

	public function getName(): string {
		return $this->name;
	}

	public function getCommonName(): ?string {
		return $this->commonName;
	}

	public function getOrganization(): ?string {
		return $this->organization;
	}

	public function getIssueDate(): \DateTime {
		return $this->issueDate;
	}

	public function getExpireDate(): \DateTime {
		return $this->expireDate;
	}

	public function isExpired(): bool {
		$now = new \DateTime();
		return $this->issueDate > $now or $now > $this->expireDatepre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
<?php

/**
* ownCloud
*
* @author Frank Karlitschek
* @copyright 2012 Frank Karlitschek frank@owncloud.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library.  If not, see <http://www.gnu.org/licenses/>.
*
*/


/**
 * Class for abstraction of filesystem functions
 * This class won't call any filesystem functions for itself but but will pass them to the correct OC_Filestorage object
 * this class should also handle all the file permission related stuff
 *
 * Hooks provided:
 *   read(path)
 *   write(path, &run)
 *   post_write(path)
 *   create(path, &run) (when a file is created, both create and write will be emited in that order)
 *   post_create(path)
 *   delete(path, &run)
 *   post_delete(path)
 *   rename(oldpath,newpath, &run)
 *   post_rename(oldpath,newpath)
 *   copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emited in that order)
 *   post_rename(oldpath,newpath)
 *
 *   the &run parameter can be set to false to prevent the operation from occuring
 */

class OC_Filesystem{
	static private $storages=array();
	static private $mounts=array();
	public static $loaded=false;
	/**
	 * @var OC_Filestorage $defaultInstance
	 */
	static private $defaultInstance;


	/**
	 * classname which used for hooks handling
	 * used as signalclass in OC_Hooks::emit()
	 */
	const CLASSNAME = 'OC_Filesystem';

	/**
	 * signalname emited before file renaming
	 * @param oldpath
	 * @param newpath
	 */
	const signal_rename = 'rename';

	/**
	 * signal emited after file renaming
	 * @param oldpath
	 * @param newpath
	 */
	const signal_post_rename = 'post_rename';

	/**
	 * signal emited before file/dir creation
	 * @param path
	 * @param run changing this flag to false in hook handler will cancel event
	 */
	const signal_create = 'create';

	/**
	 * signal emited after file/dir creation
	 * @param path
	 * @param run changing this flag to false in hook handler will cancel event
	 */
	const signal_post_create = 'post_create';

	/**
	 * signal emits before file/dir copy
	* @param oldpath
	 * @param newpath
	  * @param run changing this flag to false in hook handler will cancel event
	 */
	const signal_copy = 'copy';

	/**
	 * signal emits after file/dir copy
	 * @param oldpath
	 * @param newpath
	 */
	const signal_post_copy = 'post_copy';

	/**
	 * signal emits before file/dir save
	 * @param path
	 * @param run changing this flag to false in hook handler will cancel event
	 */
	const signal_write = 'write';

	/**
	 * signal emits after file/dir save
	 * @param path
	 */
	const signal_post_write = 'post_write';

	/**
	 * signal emits when reading file/dir
	 * @param path
	 */
	const signal_read = 'read';

	/**
	 * signal emits when removing file/dir
	 * @param path
	 */
	const signal_delete = 'delete';

	/**
	 * parameters definitions for signals
	 */
	const signal_param_path = 'path';
	const signal_param_oldpath = 'oldpath';
	const signal_param_newpath = 'newpath';

	/**
	 * run - changing this flag to false in hook handler will cancel event
	 */
	const signal_param_run = 'run';

	/**
	 * get the mountpoint of the storage object for a path
	 ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account
	 *
	 * @param string path
	  * @return string
	 */
	static public function getMountPoint($path){
		OC_Hook::emit(self::CLASSNAME,'get_mountpoint',array('path'=>$path));
		if(!$path){
			$path='/';
		}
		if($path[0]!=='/'){
			$path='/'.$path;
		}
		$path=str_replace('//', '/',$path);
		$foundMountPoint='';
		$mountPoints=array_keys(OC_Filesystem::$mounts);
		foreach($mountPoints as $mountpoint){
			if($mountpoint==$path){
				return $mountpoint;
			}
			if(strpos($path,$mountpoint)===0 and strlen($mountpoint)>strlen($foundMountPoint)){
				$foundMountPoint=$mountpoint;
			}
		}
		return $foundMountPoint;
	}

	/**
	* get the part of the path relative to the mountpoint of the storage it's stored in
	* @param  string  path
	* @return bool
	*/
	static public function getInternalPath($path){
		$mountPoint=self::getMountPoint($path);
		$internalPath=substr($path,strlen($mountPoint));
		return $internalPath;
	}
	/**
	* get the storage object for a path
	* @param string path
	* @return OC_Filestorage
	*/
	static public function getStorage($path){
		$mountpoint=self::getMountPoint($path);
		if($mountpoint){
			if(!isset(OC_Filesystem::$storages[$mountpoint])){
				$mount=OC_Filesystem::$mounts[$mountpoint];
				OC_Filesystem::$storages[$mountpoint]=OC_Filesystem::createStorage($mount['class'],$mount['arguments']);
			}
			return OC_Filesystem::$storages[$mountpoint];
		}
	}

	static public function init($root){
		if(self::$defaultInstance){
			return false;
		}
		self::$defaultInstance=new OC_FilesystemView($root);

		//load custom mount config
		if(is_file(OC::$SERVERROOT.'/config/mount.php')){
			$mountConfig=include(OC::$SERVERROOT.'/config/mount.php');
			if(isset($mountConfig['global'])){
				foreach($mountConfig['global'] as $mountPoint=>$options){
					self::mount($options['class'],$options['options'],$mountPoint);
				}
			}

			if(isset($mountConfig['group'])){
				foreach($mountConfig['group'] as $group=>$mounts){
					if(OC_Group::inGroup(OC_User::getUser(),$group)){
						foreach($mounts as $mountPoint=>$options){
							$mountPoint=self::setUserVars($mountPoint);
							foreach($options as &$option){
								$option=self::setUserVars($option);
							}
							self::mount($options['class'],$options['options'],$mountPoint);
						}
					}
				}
			}

			if(isset($mountConfig['user'])){
				foreach($mountConfig['user'] as $user=>$mounts){
					if($user==='all' or strtolower($user)===strtolower(OC_User::getUser())){
						foreach($mounts as $mountPoint=>$options){
							$mountPoint=self::setUserVars($mountPoint);
							foreach($options as &$option){
								$option=self::setUserVars($option);
							}
							self::mount($options['class'],$options['options'],$mountPoint);
						}
					}
				}
			}
		}

		self::$loaded=true;
	}

	/**
	 * fill in the correct values for $user, and $password placeholders
	 * @param string intput
	 * @return string
	 */
	private static function setUserVars($input){
		return str_replace('$user',OC_User::getUser(),$input);
	}

	/**
	 * get the default filesystem view
	 * @return OC_FilesystemView
	 */
	static public function getView(){
		return self::$defaultInstance;
	}

	/**
	 * tear down the filesystem, removing all storage providers
	 */
	static public function tearDown(){
		self::$storages=array();
	}

	/**
	* create a new storage of a specific type
	* @param  string  type
	* @param  array  arguments
	* @return OC_Filestorage
	*/
	static private function createStorage($class,$arguments){
		if(class_exists($class)){
			try {
				return new $class($arguments);
			} catch (Exception $exception) {
				OC_Log::write('core', $exception->getMessage(), OC_Log::ERROR);
				return false;
			}
		}else{
			OC_Log::write('core','storage backend '.$class.' not found',OC_Log::ERROR);
			return false;
		}
	}

	/**
	* change the root to a fake root
	* @param  string  fakeRoot
	* @return bool
	*/
	static public function chroot($fakeRoot){
		return self::$defaultInstance->chroot($fakeRoot);
	}

	/**
	 * @brief get the relative path of the root data directory for the current user
	 * @return string
	 *
	 * Returns path like /admin/files
	 */
	static public function getRoot(){
		return self::$defaultInstance->getRoot();
	}

	/**
	 * clear all mounts and storage backends
	 */
	public static function clearMounts(){
		self::$mounts=array();
		self::$storages=array();
	}

	/**
	* mount an OC_Filestorage in our virtual filesystem
	* @param OC_Filestorage storage
	* @param string mountpoint
	*/
	static public function mount($class,$arguments,$mountpoint){
		if($mountpoint[0]!='/'){
			$mountpoint='/'.$mountpoint;
		}
		if(substr($mountpoint,-1)!=='/'){
			$mountpoint=$mountpoint.'/';
		}
		self::$mounts[$mountpoint]=array('class'=>$class,'arguments'=>$arguments);
	}

	/**
	* return the path to a local version of the file
	* we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed
	* @param string path
	* @return string
	*/
	static public function getLocalFile($path){
		return self::$defaultInstance->getLocalFile($path);
	}
	/**
	 * @param string path
	 * @return string
	 */
	static public function getLocalFolder($path){
		return self::$defaultInstance->getLocalFolder($path);
	}

	/**
	* return path to file which reflects one visible in browser
	* @param string path
	* @return string
	*/
	static public function getLocalPath($path) {
		$datadir = OC_User::getHome($user).'/files';
		$newpath = $path;
		if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
			$newpath = substr($path, strlen($datadir));
		}
		return $newpath;
	}

	/**
	 * check if the requested path is valid
	 * @param string path
	 * @return bool
	 */
	static public function isValidPath($path){
		if(!$path || $path[0]!=='/'){
			$path='/'.$path;
		}
		if(strstr($path,'/../') || strrchr($path, '/') === '/..' ){
			return false;
		}
		return true;
	}

	/**
	 * checks if a file is blacklsited for storage in the filesystem
	 * Listens to write and rename hooks
	 * @param array $data from hook
	 */
	static public function isBlacklisted($data){
		$blacklist = array('.htaccess');
		if (isset($data['path'])) {
			$path = $data['path'];
		} else if (isset($data['newpath'])) {
			$path = $data['newpath'];
		}
		if (isset($path)) {
			$filename = strtolower(basename($path));
			if (in_array($filename, $blacklist)) {
				$data['run'] = false;
			}
		}
	}

	/**
	 * following functions are equivilent to their php buildin equivilents for arguments/return values.
	 */
	static public function mkdir($path){
		return self::$defaultInstance->mkdir($path);
	}
	static public function rmdir($path){
		return self::$defaultInstance->rmdir($path);
	}
	static public function opendir($path){
		return self::$defaultInstance->opendir($path);
	}
	static public function readdir($path){
		return self::$defaultInstance->readdir($path);
	}
	static public function is_dir($path){
		return self::$defaultInstance->is_dir($path);
	}
	static public function is_file($path){
		return self::$defaultInstance->is_file($path);
	}
	static public function stat($path){
		return self::$defaultInstance->stat($path);
	}
	static public function filetype($path){
		return self::$defaultInstance->filetype($path);
	}
	static public function filesize($path){
		return self::$defaultInstance->filesize($path);
	}
	static public function readfile($path){
		return self::$defaultInstance->readfile($path);
	}
	/**
	* @deprecated Replaced by isReadable() as part of CRUDS
	*/
	static public function is_readable($path){
		return self::$defaultInstance->is_readable($path);
	}
	/**
	* @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS
	*/
	static public function is_writable($path){
		return self::$defaultInstance->is_writable($path);
	}
	static public function isCreatable($path) {
		return self::$defaultInstance->isCreatable($path);
	}
	static public function isReadable($path) {
		return self::$defaultInstance->isReadable($path);
	}
	static public function isUpdatable($path) {
		return self::$defaultInstance->isUpdatable($path);
	}
	static public function isDeletable($path) {
		return self::$defaultInstance->isDeletable($path);
	}
	static public function isSharable($path) {
		return self::$defaultInstance->isSharable($path);
	}
	static public function file_exists($path){
		return self::$defaultInstance->file_exists($path);
	}
	static public function filectime($path){
		return self::$defaultInstance->filectime($path);
	}
	static public function filemtime($path){
		return self::$defaultInstance->filemtime($path);
	}
	static public function touch($path, $mtime=null){
		return self::$defaultInstance->touch($path, $mtime);
	}
	static public function file_get_contents($path){
		return self::$defaultInstance->file_get_contents($path);
	}
	static public function file_put_contents($path,$data){
		return self::$defaultInstance->file_put_contents($path,$data);
	}
	static public function unlink($path){
		return self::$defaultInstance->unlink($path);
	}
	static public function rename($path1,$path2){
		return self::$defaultInstance->rename($path1,$path2);
	}
	static public function copy($path1,$path2){
		return self::$defaultInstance->copy($path1,$path2);
	}
	static public function fopen($path,$mode){
		return self::$defaultInstance->fopen($path,$mode);
	}
	static public function toTmpFile($path){
		return self::$defaultInstance->toTmpFile($path);
	}
	static public function fromTmpFile($tmpFile,$path){
		return self::$defaultInstance->fromTmpFile($tmpFile,$path);
	}

	static public function getMimeType($path){
		return self::$defaultInstance->getMimeType($path);
	}
	static public function hash($type,$path, $raw = false){
		return self::$defaultInstance->hash($type,$path, $raw);
	}

	static public function free_space($path='/'){
		return self::$defaultInstance->free_space($path);
	}

	static public function search($query){
		return OC_FileCache::search($query);
	}

	/**
	 * check if a file or folder has been updated since $time
	 * @param int $time
	 * @return bool
	 */
	static public function hasUpdated($path,$time){
		return self::$defaultInstance->hasUpdated($path,$time);
	}

	static public function removeETagHook($params) {
		if (isset($params['path'])) {
			$path=$params['path'];
		} else {
			$path=$params['oldpath'];
		}
		OC_Connector_Sabre_Node::removeETagPropertyForPath($path);
	}

	/**
	 * normalize a path
	 * @param string path
	 * @param bool $stripTrailingSlash
	 * @return string
	 */
	public static function normalizePath($path,$stripTrailingSlash=true){
		if($path==''){
			return '/';
		}
		//no windows style slashes
		$path=str_replace('\\','/',$path);
		//add leading slash
		if($path[0]!=='/'){
			$path='/'.$path;
		}
		//remove trainling slash
		if($stripTrailingSlash and strlen($path)>1 and substr($path,-1,1)==='/'){
			$path=substr($path,0,-1);
		}
		//remove duplicate slashes
		while(strpos($path,'//')!==false){
			$path=str_replace('//','/',$path);
		}
		//normalize unicode if possible
		if(class_exists('Normalizer')){
			$path=Normalizer::normalize($path);
		}
		return $path;
	}
}
OC_Hook::connect('OC_Filesystem','post_write', 'OC_Filesystem','removeETagHook');
OC_Hook::connect('OC_Filesystem','post_delete','OC_Filesystem','removeETagHook');
OC_Hook::connect('OC_Filesystem','post_rename','OC_Filesystem','removeETagHook');

OC_Util::setupFS();
require_once 'filecache.php';