Пример #1
0
 /**
  * store a new version of a file.
  */
 public function store($filename)
 {
     if (\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED) == 'true') {
         list($uid, $filename) = self::getUidAndFilename($filename);
         $files_view = new \OC_FilesystemView('/' . $uid . '/files');
         $users_view = new \OC_FilesystemView('/' . $uid);
         //check if source file already exist as version to avoid recursions.
         // todo does this check work?
         if ($users_view->file_exists($filename)) {
             return false;
         }
         // check if filename is a directory
         if ($files_view->is_dir($filename)) {
             return false;
         }
         // check filetype blacklist
         $blacklist = explode(' ', \OCP\Config::getSystemValue('files_versionsblacklist', Storage::DEFAULTBLACKLIST));
         foreach ($blacklist as $bl) {
             $parts = explode('.', $filename);
             $ext = end($parts);
             if (strtolower($ext) == $bl) {
                 return false;
             }
         }
         // we should have a source file to work with
         if (!$files_view->file_exists($filename)) {
             return false;
         }
         // check filesize
         if ($files_view->filesize($filename) > \OCP\Config::getSystemValue('files_versionsmaxfilesize', Storage::DEFAULTMAXFILESIZE)) {
             return false;
         }
         // check mininterval if the file is being modified by the owner (all shared files should be versioned despite mininterval)
         if ($uid == \OCP\User::getUser()) {
             $versions_fileview = new \OC_FilesystemView('/' . $uid . '/files_versions');
             $versionsName = \OCP\Config::getSystemValue('datadirectory') . $versions_fileview->getAbsolutePath($filename);
             $versionsFolderName = \OCP\Config::getSystemValue('datadirectory') . $versions_fileview->getAbsolutePath('');
             $matches = glob($versionsName . '.v*');
             sort($matches);
             $parts = explode('.v', end($matches));
             if (end($parts) + Storage::DEFAULTMININTERVAL > time()) {
                 return false;
             }
         }
         // create all parent folders
         $info = pathinfo($filename);
         if (!file_exists($versionsFolderName . '/' . $info['dirname'])) {
             mkdir($versionsFolderName . '/' . $info['dirname'], 0750, true);
         }
         // store a new version of a file
         $users_view->copy('files' . $filename, 'files_versions' . $filename . '.v' . time());
         // expire old revisions if necessary
         Storage::expire($filename);
     }
 }
Пример #2
0
 function testDescryptAllWithBrokenFiles()
 {
     $file1 = "/decryptAll1" . uniqid() . ".txt";
     $file2 = "/decryptAll2" . uniqid() . ".txt";
     $util = new Encryption\Util($this->view, $this->userId);
     $this->view->file_put_contents($this->userId . '/files/' . $file1, $this->dataShort);
     $this->view->file_put_contents($this->userId . '/files/' . $file2, $this->dataShort);
     $fileInfoEncrypted1 = $this->view->getFileInfo($this->userId . '/files/' . $file1);
     $fileInfoEncrypted2 = $this->view->getFileInfo($this->userId . '/files/' . $file2);
     $this->assertTrue(is_array($fileInfoEncrypted1));
     $this->assertTrue(is_array($fileInfoEncrypted2));
     $this->assertEquals($fileInfoEncrypted1['encrypted'], 1);
     $this->assertEquals($fileInfoEncrypted2['encrypted'], 1);
     // rename keyfile for file1 so that the decryption for file1 fails
     // Expected behaviour: decryptAll() returns false, file2 gets decrypted anyway
     $this->view->rename($this->userId . '/files_encryption/keyfiles/' . $file1 . '.key', $this->userId . '/files_encryption/keyfiles/' . $file1 . '.key.moved');
     // decrypt all encrypted files
     $result = $util->decryptAll('/' . $this->userId . '/' . 'files');
     $this->assertFalse($result);
     $fileInfoUnencrypted1 = $this->view->getFileInfo($this->userId . '/files/' . $file1);
     $fileInfoUnencrypted2 = $this->view->getFileInfo($this->userId . '/files/' . $file2);
     $this->assertTrue(is_array($fileInfoUnencrypted1));
     $this->assertTrue(is_array($fileInfoUnencrypted2));
     // file1 should be still encrypted; file2 should be decrypted
     $this->assertEquals(1, $fileInfoUnencrypted1['encrypted']);
     $this->assertEquals(0, $fileInfoUnencrypted2['encrypted']);
     // keyfiles and share keys should still exist
     $this->assertTrue($this->view->is_dir($this->userId . '/files_encryption/keyfiles/'));
     $this->assertTrue($this->view->is_dir($this->userId . '/files_encryption/share-keys/'));
     // rename the keyfile for file1 back
     $this->view->rename($this->userId . '/files_encryption/keyfiles/' . $file1 . '.key.moved', $this->userId . '/files_encryption/keyfiles/' . $file1 . '.key');
     // try again to decrypt all encrypted files
     $result = $util->decryptAll('/' . $this->userId . '/' . 'files');
     $this->assertTrue($result);
     $fileInfoUnencrypted1 = $this->view->getFileInfo($this->userId . '/files/' . $file1);
     $fileInfoUnencrypted2 = $this->view->getFileInfo($this->userId . '/files/' . $file2);
     $this->assertTrue(is_array($fileInfoUnencrypted1));
     $this->assertTrue(is_array($fileInfoUnencrypted2));
     // now both files should be decrypted
     $this->assertEquals(0, $fileInfoUnencrypted1['encrypted']);
     $this->assertEquals(0, $fileInfoUnencrypted2['encrypted']);
     // keyfiles and share keys should be deleted
     $this->assertFalse($this->view->is_dir($this->userId . '/files_encryption/keyfiles/'));
     $this->assertFalse($this->view->is_dir($this->userId . '/files_encryption/share-keys/'));
     $this->view->unlink($this->userId . '/files/' . $file1);
     $this->view->unlink($this->userId . '/files/' . $file2);
 }
Пример #3
0
 /**
  * @brief if session is started, check if ownCloud key pair is set up, if not create it
  * @param \OC_FilesystemView $view
  *
  * @note The ownCloud key pair is used to allow public link sharing even if encryption is enabled
  */
 public function __construct($view)
 {
     $this->view = $view;
     if (!$this->view->is_dir('owncloud_private_key')) {
         $this->view->mkdir('owncloud_private_key');
     }
     $publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId');
     if ($publicShareKeyId === null) {
         $publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8);
         \OC_Appconfig::setValue('files_encryption', 'publicShareKeyId', $publicShareKeyId);
     }
     if (!$this->view->file_exists("/public-keys/" . $publicShareKeyId . ".public.key") || !$this->view->file_exists("/owncloud_private_key/" . $publicShareKeyId . ".private.key")) {
         $keypair = Crypt::createKeypair();
         // Disable encryption proxy to prevent recursive calls
         $proxyStatus = \OC_FileProxy::$enabled;
         \OC_FileProxy::$enabled = false;
         // Save public key
         if (!$view->is_dir('/public-keys')) {
             $view->mkdir('/public-keys');
         }
         $this->view->file_put_contents('/public-keys/' . $publicShareKeyId . '.public.key', $keypair['publicKey']);
         // Encrypt private key empty passphrase
         $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], '');
         // Save private key
         $this->view->file_put_contents('/owncloud_private_key/' . $publicShareKeyId . '.private.key', $encryptedPrivateKey);
         \OC_FileProxy::$enabled = $proxyStatus;
     }
     if (\OCA\Encryption\Helper::isPublicAccess()) {
         // Disable encryption proxy to prevent recursive calls
         $proxyStatus = \OC_FileProxy::$enabled;
         \OC_FileProxy::$enabled = false;
         $encryptedKey = $this->view->file_get_contents('/owncloud_private_key/' . $publicShareKeyId . '.private.key');
         $privateKey = Crypt::decryptPrivateKey($encryptedKey, '');
         $this->setPublicSharePrivateKey($privateKey);
         \OC_FileProxy::$enabled = $proxyStatus;
     }
 }
Пример #4
0
 /**
  * @brief mark file as renamed so that we know the original source after the file was renamed
  * @param array $params with the old path and the new path
  */
 public static function preRename($params)
 {
     $user = \OCP\User::getUser();
     $view = new \OC_FilesystemView('/');
     $util = new Util($view, $user);
     list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
     // we only need to rename the keys if the rename happens on the same mountpoint
     // otherwise we perform a stream copy, so we get a new set of keys
     $mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
     $mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
     $type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
     if ($mp1 === $mp2) {
         self::$renamedFiles[$params['oldpath']] = array('uid' => $ownerOld, 'path' => $pathOld, 'type' => $type, 'operation' => 'rename');
     }
 }
Пример #5
0
 /**
  * @param $path
  * @param $size
  * @return bool
  */
 public function postFileSize($path, $size, $fileInfo = null)
 {
     $view = new \OC_FilesystemView('/');
     $userId = Helper::getUser($path);
     $util = new Util($view, $userId);
     // if encryption is no longer enabled or if the files aren't migrated yet
     // we return the default file size
     if (!\OCP\App::isEnabled('files_encryption') || $util->getMigrationStatus() !== Util::MIGRATION_COMPLETED) {
         return $size;
     }
     // if path is a folder do nothing
     if ($view->is_dir($path)) {
         $proxyState = \OC_FileProxy::$enabled;
         \OC_FileProxy::$enabled = false;
         $fileInfo = $view->getFileInfo($path);
         \OC_FileProxy::$enabled = $proxyState;
         if (isset($fileInfo['unencrypted_size']) && $fileInfo['unencrypted_size'] > 0) {
             return $fileInfo['unencrypted_size'];
         }
         return $size;
     }
     // get relative path
     $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
     // if path is empty we cannot resolve anything
     if (empty($relativePath)) {
         return $size;
     }
     // get file info from database/cache if not .part file
     if (empty($fileInfo) && !Helper::isPartialFilePath($path)) {
         $proxyState = \OC_FileProxy::$enabled;
         \OC_FileProxy::$enabled = false;
         $fileInfo = $view->getFileInfo($path);
         \OC_FileProxy::$enabled = $proxyState;
     }
     // if file is encrypted return real file size
     if (isset($fileInfo['encrypted']) && $fileInfo['encrypted'] === true) {
         // try to fix unencrypted file size if it doesn't look plausible
         if ((int) $fileInfo['size'] > 0 && (int) $fileInfo['unencrypted_size'] === 0) {
             $fixSize = $util->getFileSize($path);
             $fileInfo['unencrypted_size'] = $fixSize;
             // put file info if not .part file
             if (!Helper::isPartialFilePath($relativePath)) {
                 $view->putFileInfo($path, $fileInfo);
             }
         }
         $size = $fileInfo['unencrypted_size'];
     } else {
         // self healing if file was removed from file cache
         if (!is_array($fileInfo)) {
             $fileInfo = array();
         }
         $fixSize = $util->getFileSize($path);
         if ($fixSize > 0) {
             $size = $fixSize;
             $fileInfo['encrypted'] = true;
             $fileInfo['unencrypted_size'] = $size;
             // put file info if not .part file
             if (!Helper::isPartialFilePath($relativePath)) {
                 $view->putFileInfo($path, $fileInfo);
             }
         }
     }
     return $size;
 }
Пример #6
0
 /**
  * Share an item, adds an entry into the database
  * @param $source The source location of the item
  * @param $uid_shared_with The user or group to share the item with
  * @param $permissions The permissions, use the constants WRITE and DELETE
  */
 public function __construct($source, $uid_shared_with, $permissions)
 {
     $uid_owner = OCP\USER::getUser();
     $query = OCP\DB::prepare("INSERT INTO *PREFIX*sharing VALUES(?,?,?,?,?)");
     // Check if this is a reshare and use the original source
     if ($result = OC_Share::getSource($source)) {
         $source = $result;
     }
     if ($uid_shared_with == self::PUBLICLINK) {
         $token = sha1("{$uid_shared_with}-{$source}");
         $query->execute(array($uid_owner, self::PUBLICLINK, $source, $token, $permissions));
         $this->token = $token;
     } else {
         if (OC_Group::groupExists($uid_shared_with)) {
             $gid = $uid_shared_with;
             $uid_shared_with = OC_Group::usersInGroup($gid);
             // Remove the owner from the list of users in the group
             $uid_shared_with = array_diff($uid_shared_with, array($uid_owner));
         } else {
             if (OCP\User::userExists($uid_shared_with)) {
                 $userGroups = OC_Group::getUserGroups($uid_owner);
                 // Check if the user is in one of the owner's groups
                 foreach ($userGroups as $group) {
                     if ($inGroup = OC_Group::inGroup($uid_shared_with, $group)) {
                         $gid = null;
                         $uid_shared_with = array($uid_shared_with);
                         break;
                     }
                 }
                 if (!$inGroup) {
                     throw new Exception("You can't share with " . $uid_shared_with);
                 }
             } else {
                 throw new Exception($uid_shared_with . " is not a user");
             }
         }
         foreach ($uid_shared_with as $uid) {
             // Check if this item is already shared with the user
             $checkSource = OCP\DB::prepare("SELECT source FROM *PREFIX*sharing WHERE source = ? AND uid_shared_with " . self::getUsersAndGroups($uid, false));
             $resultCheckSource = $checkSource->execute(array($source))->fetchAll();
             // TODO Check if the source is inside a folder
             if (count($resultCheckSource) > 0) {
                 if (!isset($gid)) {
                     throw new Exception("This item is already shared with " . $uid);
                 } else {
                     // Skip this user if sharing with a group
                     continue;
                 }
             }
             // Check if the target already exists for the user, if it does append a number to the name
             $sharedFolder = '/' . $uid . '/files/Shared';
             $target = $sharedFolder . "/" . basename($source);
             $checkTarget = OCP\DB::prepare("SELECT source FROM *PREFIX*sharing WHERE target = ? AND uid_shared_with " . self::getUsersAndGroups($uid, false) . " LIMIT 1");
             $result = $checkTarget->execute(array($target))->fetchAll();
             if (count($result) > 0) {
                 if ($pos = strrpos($target, ".")) {
                     $name = substr($target, 0, $pos);
                     $ext = substr($target, $pos);
                 } else {
                     $name = $target;
                     $ext = "";
                 }
                 $counter = 1;
                 while (count($result) > 0) {
                     $target = $name . "_" . $counter . $ext;
                     $result = $checkTarget->execute(array($target))->fetchAll();
                     $counter++;
                 }
             }
             if (isset($gid)) {
                 $uid = $uid . "@" . $gid;
             }
             $query->execute(array($uid_owner, $uid, $source, $target, $permissions));
             // Update mtime of shared folder to invoke a file cache rescan
             $rootView = new OC_FilesystemView('/');
             if (!$rootView->is_dir($sharedFolder)) {
                 if (!$rootView->is_dir('/' . $uid . '/files')) {
                     OC_Util::tearDownFS();
                     OC_Util::setupFS($uid);
                     OC_Util::tearDownFS();
                 }
                 $rootView->mkdir($sharedFolder);
             }
             $rootView->touch($sharedFolder);
         }
     }
 }
Пример #7
0
 /**
  * update the filecache according to changes to the filesystem
  * @param string path
  * @param string root (optional)
  */
 public static function update($path, $root = false)
 {
     if ($root === false) {
         $view = OC_Filesystem::getView();
     } else {
         $view = new OC_FilesystemView($root);
     }
     $mimetype = $view->getMimeType($path);
     $size = 0;
     $cached = OC_FileCache_Cached::get($path, $root);
     $cachedSize = isset($cached['size']) ? $cached['size'] : 0;
     if ($view->is_dir($path . '/')) {
         if (OC_FileCache::inCache($path, $root)) {
             $cachedContent = OC_FileCache_Cached::getFolderContent($path, $root);
             foreach ($cachedContent as $file) {
                 $size += $file['size'];
             }
             $mtime = $view->filemtime($path . '/');
             $ctime = $view->filectime($path . '/');
             $writable = $view->is_writable($path . '/');
             OC_FileCache::put($path, array('size' => $size, 'mtime' => $mtime, 'ctime' => $ctime, 'mimetype' => $mimetype, 'writable' => $writable));
         } else {
             $count = 0;
             OC_FileCache::scan($path, null, $count, $root);
             return;
             //increaseSize is already called inside scan
         }
     } else {
         $size = OC_FileCache::scanFile($path, $root);
     }
     OC_FileCache::increaseSize(dirname($path), $size - $cachedSize, $root);
 }
Пример #8
0
 /**
  * @param $path
  * @param $size
  * @return bool
  */
 public function postFileSize($path, $size)
 {
     $view = new \OC_FilesystemView('/');
     // if path is a folder do nothing
     if ($view->is_dir($path)) {
         return $size;
     }
     // get relative path
     $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
     // if path is empty we cannot resolve anything
     if (empty($relativePath)) {
         return $size;
     }
     $fileInfo = false;
     // get file info from database/cache if not .part file
     if (!Keymanager::isPartialFilePath($path)) {
         $fileInfo = $view->getFileInfo($path);
     }
     // if file is encrypted return real file size
     if (is_array($fileInfo) && $fileInfo['encrypted'] === true) {
         $size = $fileInfo['unencrypted_size'];
     } else {
         // self healing if file was removed from file cache
         if (!is_array($fileInfo)) {
             $fileInfo = array();
         }
         $userId = \OCP\User::getUser();
         $util = new Util($view, $userId);
         $fixSize = $util->getFileSize($path);
         if ($fixSize > 0) {
             $size = $fixSize;
             $fileInfo['encrypted'] = true;
             $fileInfo['unencrypted_size'] = $size;
             // put file info if not .part file
             if (!Keymanager::isPartialFilePath($relativePath)) {
                 $view->putFileInfo($path, $fileInfo);
             }
         }
     }
     return $size;
 }
Пример #9
0
 /**
  * recursively scan the filesystem and fill the cache
  * @param string $path
  * @param OC_EventSource $enventSource (optional)
  * @param int count (optional)
  * @param string root (optional)
  */
 public static function scan($path, $eventSource = false, &$count = 0, $root = false)
 {
     if ($eventSource) {
         $eventSource->send('scanning', array('file' => $path, 'count' => $count));
     }
     $lastSend = $count;
     // NOTE: Ugly hack to prevent shared files from going into the cache (the source already exists somewhere in the cache)
     if (substr($path, 0, 7) == '/Shared') {
         return;
     }
     if ($root === false) {
         $view = OC_Filesystem::getView();
     } else {
         $view = new OC_FilesystemView($root);
     }
     self::scanFile($path, $root);
     $dh = $view->opendir($path . '/');
     $totalSize = 0;
     if ($dh) {
         while (($filename = readdir($dh)) !== false) {
             if ($filename != '.' and $filename != '..') {
                 $file = $path . '/' . $filename;
                 if ($view->is_dir($file . '/')) {
                     self::scan($file, $eventSource, $count, $root);
                 } else {
                     $totalSize += self::scanFile($file, $root);
                     $count++;
                     if ($count > $lastSend + 25 and $eventSource) {
                         $lastSend = $count;
                         $eventSource->send('scanning', array('file' => $path, 'count' => $count));
                     }
                 }
             }
         }
     }
     OC_FileCache_Update::cleanFolder($path, $root);
     self::increaseSize($path, $totalSize, $root);
 }
Пример #10
0
 /**
  * @brief after a file is renamed, rename its keyfile and share-keys also fix the file size and fix also the sharing
  * @param array with oldpath and newpath
  *
  * This function is connected to the rename signal of OC_Filesystem and adjust the name and location
  * of the stored versions along the actual file
  */
 public static function postRename($params)
 {
     if (\OCP\App::isEnabled('files_encryption') === false) {
         return true;
     }
     // Disable encryption proxy to prevent recursive calls
     $proxyStatus = \OC_FileProxy::$enabled;
     \OC_FileProxy::$enabled = false;
     $view = new \OC_FilesystemView('/');
     $session = new \OCA\Encryption\Session($view);
     $userId = \OCP\User::getUser();
     $util = new Util($view, $userId);
     // Format paths to be relative to user files dir
     if ($util->isSystemWideMountPoint($params['oldpath'])) {
         $baseDir = 'files_encryption/';
         $oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
     } else {
         $baseDir = $userId . '/' . 'files_encryption/';
         $oldKeyfilePath = $baseDir . 'keyfiles/' . $params['oldpath'];
     }
     if ($util->isSystemWideMountPoint($params['newpath'])) {
         $newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
     } else {
         $newKeyfilePath = $baseDir . 'keyfiles/' . $params['newpath'];
     }
     // add key ext if this is not an folder
     if (!$view->is_dir($oldKeyfilePath)) {
         $oldKeyfilePath .= '.key';
         $newKeyfilePath .= '.key';
         // handle share-keys
         $localKeyPath = $view->getLocalFile($baseDir . 'share-keys/' . $params['oldpath']);
         $escapedPath = Helper::escapeGlobPattern($localKeyPath);
         $matches = glob($escapedPath . '*.shareKey');
         foreach ($matches as $src) {
             $dst = \OC\Files\Filesystem::normalizePath(str_replace($params['oldpath'], $params['newpath'], $src));
             // create destination folder if not exists
             if (!file_exists(dirname($dst))) {
                 mkdir(dirname($dst), 0750, true);
             }
             rename($src, $dst);
         }
     } else {
         // handle share-keys folders
         $oldShareKeyfilePath = $baseDir . 'share-keys/' . $params['oldpath'];
         $newShareKeyfilePath = $baseDir . 'share-keys/' . $params['newpath'];
         // create destination folder if not exists
         if (!$view->file_exists(dirname($newShareKeyfilePath))) {
             $view->mkdir(dirname($newShareKeyfilePath), 0750, true);
         }
         $view->rename($oldShareKeyfilePath, $newShareKeyfilePath);
     }
     // Rename keyfile so it isn't orphaned
     if ($view->file_exists($oldKeyfilePath)) {
         // create destination folder if not exists
         if (!$view->file_exists(dirname($newKeyfilePath))) {
             $view->mkdir(dirname($newKeyfilePath), 0750, true);
         }
         $view->rename($oldKeyfilePath, $newKeyfilePath);
     }
     // build the path to the file
     $newPath = '/' . $userId . '/files' . $params['newpath'];
     $newPathRelative = $params['newpath'];
     if ($util->fixFileSize($newPath)) {
         // get sharing app state
         $sharingEnabled = \OCP\Share::isEnabled();
         // get users
         $usersSharing = $util->getSharingUsersArray($sharingEnabled, $newPathRelative);
         // update sharing-keys
         $util->setSharedFileKeyfiles($session, $usersSharing, $newPathRelative);
     }
     \OC_FileProxy::$enabled = $proxyStatus;
 }
Пример #11
0
 /**
  * check if a file or folder is updated outside owncloud
  * @param string path
  * @param string root (optional)
  * @return bool
  */
 public static function isUpdated($path, $root = '')
 {
     if (!$root) {
         $root = OC_Filesystem::getRoot();
         $view = OC_Filesystem::getView();
     } else {
         if ($root == '/') {
             $root = '';
         }
         $view = new OC_FilesystemView($root);
     }
     if (!$view->file_exists($path)) {
         return false;
     }
     $mtime = $view->filemtime($path);
     $isDir = $view->is_dir($path);
     $fullPath = $root . $path;
     $query = OC_DB::prepare('SELECT mtime FROM *PREFIX*fscache WHERE path_hash=?');
     $result = $query->execute(array(md5($fullPath)));
     if ($row = $result->fetchRow()) {
         $cachedMTime = $row['mtime'];
         return $mtime > $cachedMTime;
     } else {
         //file not in cache, so it has to be updated
         if ($path == '/' or $path == '') {
             //dont auto update the root folder, it will be scanned
             return false;
         }
         return true;
     }
 }
Пример #12
0
 /**
  * @brief Make preparations to vars and filesystem for saving a keyfile
  */
 public static function keySetPreparation(\OC_FilesystemView $view, $path, $basePath, $userId)
 {
     $targetPath = ltrim($path, '/');
     $path_parts = pathinfo($targetPath);
     // If the file resides within a subdirectory, create it
     if (isset($path_parts['dirname']) && !$view->file_exists($basePath . '/' . $path_parts['dirname'])) {
         $sub_dirs = explode(DIRECTORY_SEPARATOR, $basePath . '/' . $path_parts['dirname']);
         $dir = '';
         foreach ($sub_dirs as $sub_dir) {
             $dir .= '/' . $sub_dir;
             if (!$view->is_dir($dir)) {
                 $view->mkdir($dir);
             }
         }
     }
     return $targetPath;
 }