1
0
mirror of https://github.com/misterunknown/ifm.git synced 2025-08-16 13:04:00 +02:00

Merge pull request #40 from misterunknown/v2.4.0

V2.4.0
This commit is contained in:
Marco Dickert
2017-07-09 23:13:10 +02:00
committed by GitHub
29 changed files with 7195 additions and 1541 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
ifm.php -diff
build/* -diff

View File

@@ -1,12 +1,22 @@
# IFM - improved file manager # IFM - improved file manager
## about ## about
This is a simple filemanager. It is a single file solution which uses HTML5, CSS3, JavaScript and PHP. It works like a client-server system where HTML5/CSS3/JavaScript is the client part and the PHP API acts as the server, which reduces the traffic significant. The IFM is a web-based filemanager, which comes as a single file solution using HTML5, CSS3, JavaScript and PHP.
The IFM uses the following resources: The IFM uses the following resources:
* [ACE Editor](https://ace.c9.io) * [ACE Editor](https://ace.c9.io)
* [Bootstrap v3](https://getbootstrap.com) * [Bootstrap v3](https://getbootstrap.com)
* [jQuery](https://jquery.com)
* custom icon set generated with [Fontello](http://fontello.com/) * custom icon set generated with [Fontello](http://fontello.com/)
* [jQuery](https://jquery.com)
* [Mustache](https://mustache.github.io/)
## features
* create/edit files and directories
* copy/move files and directories
* download files and directories
* upload files directly or via URL
* extract zip archives
* change permissions
* image preview
## requirements ## requirements
| Client | Server | | Client | Server |
@@ -19,8 +29,8 @@ Just copy the ifm.php to your webspace - thats all :)
## key bindings ## key bindings
* <kbd>e</kbd> - edit / extract current file * <kbd>e</kbd> - edit / extract current file
* <kbd>h</kbd><kbd>j</kbd><kbd>k</kbd><kbd>l</kbd> - vim-style navigation * <kbd>h</kbd><kbd>j</kbd><kbd>k</kbd><kbd>l</kbd> - vim-style navigation (alternative to arrow keys)
* <kbd>g</kbd> - focus path (goto) * <kbd>g</kbd> - focus the path input field (i.e. "goto")
* <kbd>r</kbd> - refresh file table * <kbd>r</kbd> - refresh file table
* <kbd>u</kbd> - upload a file * <kbd>u</kbd> - upload a file
* <kbd>o</kbd> - remote upload a file * <kbd>o</kbd> - remote upload a file
@@ -29,11 +39,12 @@ Just copy the ifm.php to your webspace - thats all :)
* <kbd>D</kbd> - new directory * <kbd>D</kbd> - new directory
* <kbd>space</kbd> - select a highlighted item * <kbd>space</kbd> - select a highlighted item
* <kbd>del</kbd> - delete selected files * <kbd>del</kbd> - delete selected files
* <kbd>Enter</kbd> - open a file or change to the directory
## configuration ## configuration
The configuration is located at the top of the script in a separate configuration class. The options in the class are commented and named laconically. If you have questions anyway [write me an email](mailto:marco@misterunknown.de). The configuration is located at the top of the script. The options are commented and named laconically. If you have any questions [write me an email](mailto:marco@misterunknown.de).
### authentication ### authentication
Meanwhile I added a super simple authentication feature using the configuration keys `auth` and `auth_source`. You can configure it like this: The IFM offers a simple authentication feature using the configuration keys `auth` and `auth_source`. You can configure it like this:
```php ```php
"auth" => 1, "auth" => 1,
"auth_source" => 'inline;<username>:<password_hash>', "auth_source" => 'inline;<username>:<password_hash>',
@@ -57,8 +68,5 @@ The password hash has to be a hash generated by PHPs `password_hash()` function.
## issues ## issues
Currently there are no known issues. If you find any flaws please let me know. Currently there are no known issues. If you find any flaws please let me know.
## security information ## security information
The IFM was developed with the assumption that the highest level of operation is the scripts base location. So it is neither possible to nagivate nor to use any API function in a level above the script root. By default, the IFM is locked to it's own directory, so you are not able to go above. You can change that by setting the $root_dir in the scripts configuration.
It is highly recommended to restrict access to the script e.g. using a basic authentication.

1240
build/ifm.js Normal file

File diff suppressed because it is too large Load Diff

2833
build/ifmlib.php Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,32 +1,56 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
/**
* IFM compiler
*
* This script compiles all sources into one single file.
*/
// This compiles the source files into one file chdir( realpath( dirname( __FILE__ ) ) );
$IFM_CONFIG = "src/config.php"; $IFM_SRC_MAIN = "src/main.php";
$IFM_MAIN = "src/main.php"; $IFM_SRC_PHPFILES = array( "src/ifmzip.php" );
$IFM_OTHER_PHPFILES = array("src/ifmzip.php"); $IFM_SRC_JS = "src/ifm.js";
$filename = "ifm.php"; $IFM_BUILD_STANDALONE = "ifm.php";
$IFM_BUILD_LIB_PHP = "build/ifmlib.php";
// config /**
file_put_contents($filename, file_get_contents($IFM_CONFIG)); * Prepare main script
*/
// other php classes $main = file_get_contents( $IFM_SRC_MAIN );
foreach ( $IFM_OTHER_PHPFILES as $file) { $includes = NULL;
$content_file = file($file); preg_match_all( "/\@\@\@([^\@]+)\@\@\@/", $main, $includes, PREG_SET_ORDER );
unset($content_file[0]); // remove <?php line foreach( $includes as $file ) {
file_put_contents($filename, $content_file, FILE_APPEND); $main = str_replace( $file[0], file_get_contents( $file[1] ), $main );
} }
// main /**
$content_main = file($IFM_MAIN); * Add PHP files
unset($content_main[0]); */
$content_main = implode($content_main); $phpincludes = array();
$include_files = NULL; foreach( $IFM_SRC_PHPFILES as $file ) {
preg_match_all( "/\@\@\@([^\@]+)\@\@\@/", $content_main, $include_files, PREG_SET_ORDER ); $content = file( $file );
foreach( $include_files as $file ) { unset( $content[0] ); // remove <?php line
//echo $file[0]. " " .$file[1]."\n"; $phpincludes = array_merge( $phpincludes, $content );
$content_main = str_replace( $file[0], file_get_contents( $file[1] ), $content_main );
} }
file_put_contents($filename, $content_main, FILE_APPEND);
/**
* Build standalone script
*/
file_put_contents( $IFM_BUILD_STANDALONE, $main );
file_put_contents( $IFM_BUILD_STANDALONE, $phpincludes, FILE_APPEND );
file_put_contents( $IFM_BUILD_STANDALONE, array(
'',
'/**',
' * start IFM',
' */',
'$ifm = new IFM();',
'$ifm->run();'
), FILE_APPEND );
/**
* Build library
*/
file_put_contents( $IFM_BUILD_LIB_PHP, $main );
file_put_contents( $IFM_BUILD_LIB_PHP, $phpincludes, FILE_APPEND );

2353
ifm.php

File diff suppressed because one or more lines are too long

View File

@@ -1,87 +0,0 @@
<?php
/* =======================================================================
* Improved File Manager
* ---------------------
* License: This project is provided under the terms of the MIT LICENSE
* http://github.com/misterunknown/ifm/blob/master/LICENSE
* =======================================================================
*
* config
*/
class IFMConfig {
// 0 = no/not allowed;; 1 = yes/allowed;; default: no/forbidden;
// action controls
const upload = 1; // allow uploads
const remoteupload = 1; // allow remote uploads using cURL
const delete = 1; // allow deletions
const rename = 1; // allow renamings
const edit = 1; // allow editing
const chmod = 1; // allow to change rights
const extract = 1; // allow extracting zip archives
const download = 1; // allow to download files and skripts (even php scripts!)
const selfdownload = 1; // allow to download this skript itself
const createdir = 1; // allow to create directorys
const createfile = 1; // allow to create files
const zipnload = 1; // allow to zip and download directorys
// view controls
const multiselect = 1; // implement multiselect of files and directories
const showlastmodified = 0; // show the last modified date?
const showfilesize = 1; // show filesize?
const showowner = 1; // show file owner?
const showgroup = 1; // show file group?
const showpermissions = 2; // show permissions 0 -> not; 1 -> octal, 2 -> human readable
const showhtdocs = 1; // show .htaccess and .htpasswd
const showhiddenfiles = 1; // show files beginning with a dot (e.g. ".bashrc")
const showpath = 0; // show absolute path
/*
authentication
This provides a super simple authentication functionality. At the moment only one user can be
configured. The credential information can be either set inline or read from a file. The
password has to be a hash generated by PHPs password_hash function. The default credentials are
admin:admin.
If you specify a file it should only contain one line, with the credentials in the following
format:
<username>:<passwordhash>
examples:
const auth_source = 'inline;admin:$2y$10$0Bnm5L4wKFHRxJgNq.oZv.v7yXhkJZQvinJYR2p6X1zPvzyDRUVRC';
const auth_source = 'file;/path/to/file';
*/
const auth = 0;
const auth_source = 'inline;admin:$2y$10$0Bnm5L4wKFHRxJgNq.oZv.v7yXhkJZQvinJYR2p6X1zPvzyDRUVRC';
/*
root_dir - set a custom root directory instead of the script location
This option is highly experimental and should only be set if you definitely know what you do.
Settings this option may cause black holes or other unwanted things. Use with special care.
default setting:
const root_dir = "";
*/
const root_dir = "";
const defaulttimezone = "Europe/Berlin"; // set default timezone
/**
* Temp directory for zip files
*
* Default is the upload_tmp_dir which is set in the php.ini, but you may also set an different path
*/
const tmp_dir = "";
// development tools
const ajaxrequest = 1; // formular to perform an ajax request
static function getConstants() {
$oClass = new ReflectionClass(__CLASS__);
return $oClass->getConstants();
}
}

1194
src/ifm.js

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,9 @@
*/ */
class IFMZip { class IFMZip {
/**
* Add a folder to the zip file
*/
private static function folderToZip($folder, &$zipFile, $exclusiveLength) { private static function folderToZip($folder, &$zipFile, $exclusiveLength) {
$handle = opendir( $folder ); $handle = opendir( $folder );
while( false !== $f = readdir( $handle ) ) { while( false !== $f = readdir( $handle ) ) {
@@ -34,7 +37,10 @@ class IFMZip {
closedir( $handle ); closedir( $handle );
} }
public static function create_zip( $src, $out, $root=false ) /**
* Create a zip file
*/
public static function create( $src, $out, $root=false )
{ {
$z = new ZipArchive(); $z = new ZipArchive();
$z->open( $out, ZIPARCHIVE::CREATE); $z->open( $out, ZIPARCHIVE::CREATE);
@@ -53,11 +59,14 @@ class IFMZip {
} }
} }
public static function unzip_file( $file ) { /**
$zip = new ZipArchive(); * Unzip a zip file
*/
public static function extract( $file, $destination="./" ) {
$zip = new ZipArchive;
$res = $zip->open( $file ); $res = $zip->open( $file );
if( $res === true ) { if( $res === true ) {
$zip->extractTo( './' ); $zip->extractTo( $destination );
$zip->close(); $zip->close();
return true; return true;
} else { } else {

View File

@@ -0,0 +1 @@
.treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
.ekko-lightbox-nav-overlay a:focus,.ekko-lightbox-nav-overlay a>:focus{outline:0}.ekko-lightbox-container{position:relative}.ekko-lightbox-container>div.ekko-lightbox-item{position:absolute;top:0;left:0;width:100%;transition:opacity .5s ease-in-out;opacity:1}.ekko-lightbox-nav-overlay{z-index:1;position:absolute;top:0;left:0;width:100%;height:100%;display:-ms-flexbox;display:flex}.ekko-lightbox-nav-overlay a{-ms-flex:1;flex:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;opacity:0;transition:opacity .5s;color:#fff;font-size:30px;z-index:1}.ekko-lightbox-nav-overlay a>*{-ms-flex-positive:1;flex-grow:1}.ekko-lightbox-nav-overlay a span{padding:0 30px}.ekko-lightbox-nav-overlay a:last-child span{text-align:right}.ekko-lightbox-nav-overlay a:hover{text-decoration:none}.ekko-lightbox a:hover{opacity:1;text-decoration:none}.ekko-lightbox .modal-footer{text-align:left}.ekko-lightbox-loader{position:absolute;top:0;left:0;bottom:0;right:0;width:100%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}.ekko-lightbox-loader>div{width:40px;height:40px;position:relative;text-align:center}.ekko-lightbox-loader>div>div{width:100%;height:100%;border-radius:50%;background-color:#fff;opacity:.6;position:absolute;top:0;left:0;animation:a 2s infinite ease-in-out}.ekko-lightbox-loader>div>div:last-child{animation-delay:-1s}@keyframes a{0%,to{transform:scale(0);-webkit-transform:scale(0)}50%{transform:scale(1);-webkit-transform:scale(1)}}
/*# sourceMappingURL=ekko-lightbox.min.css.map */

File diff suppressed because one or more lines are too long

1
src/includes/mustache.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,17 +13,56 @@
error_reporting( E_ALL ); error_reporting( E_ALL );
ini_set( 'display_errors', 'OFF' ); ini_set( 'display_errors', 'OFF' );
class IFM {
const VERSION = '2.3.1';
public function __construct() { class IFM {
session_start(); const VERSION = '2.4.0';
private $defaultconfig = array(
// general config
"auth" => 0,
"auth_source" => 'inlineadmin:$2y$10$0Bnm5L4wKFHRxJgNq.oZv.v7yXhkJZQvinJYR2p6X1zPvzyDRUVRC',
"root_dir" => "",
"tmp_dir" => "",
"defaulttimezone" => "Europe/Berlin",
// api controls
"ajaxrequest" => 1,
"chmod" => 1,
"copymove" => 1,
"createdir" => 1,
"createfile" => 1,
"edit" => 1,
"delete" => 1,
"download" => 1,
"extract" => 1,
"upload" => 1,
"remoteupload" => 1,
"rename" => 1,
"zipnload" => 1,
// gui controls
"showlastmodified" => 0,
"showfilesize" => 1,
"showowner" => 1,
"showgroup" => 1,
"showpermissions" => 2,
"showhtdocs" => 1,
"showhiddenfiles" => 1,
"showpath" => 0,
);
private $config = array();
public $mode = "";
public function __construct( $config=array() ) {
if( session_status() !== PHP_SESSION_ACTIVE )
session_start();
$this->config = array_merge( $this->defaultconfig, $config );
} }
/* /**
this function contains the client-side application * This function contains the client-side application
*/ */
public function getApplication() { public function getApplication() {
print '<!DOCTYPE HTML> print '<!DOCTYPE HTML>
<html> <html>
@@ -31,91 +70,43 @@ class IFM {
<title>IFM - improved file manager</title> <title>IFM - improved file manager</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">';
<style type="text/css">';?> @@@src/includes/bootstrap.min.css@@@ <?php print '</style> $this->getCSS();
<style type="text/css">';?> @@@src/includes/ekko-lightbox.min.css@@@ <?php print '</style> print '
<style type="text/css">';?> @@@src/includes/fontello-embedded.css@@@ <?php print '</style>
<style type="text/css">';?> @@@src/style.css@@@ <?php print '</style>
</head> </head>
<body> <body>
<nav class="navbar navbar-inverse navbar-fixed-top"> <div id="ifm"></div>';
<div class="container"> $this->getJS();
<div class="navbar-header"> print '
<a class="navbar-brand">IFM</a> <script>var ifm = new IFM(); ifm.init( "ifm" );</script>
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbar"> </body>
<span class="sr-only">Toggle navigation</span> </html>
<span class="icon-bar"></span> ';
<span class="icon-bar"></span> }
<span class="icon-bar"></span>
</button> public function getInlineApplication() {
</div> $this->getCSS();
<div class="navbar-collapse collapse" id="navbar"> print '<div id="ifm"></div>';
<form class="navbar-form navbar-left"> $this->getJS();
<div class="form-group"> }
<div class="input-group">
<span class="input-group-addon" id="currentDirLabel">Content of <span id="docroot">'; public function getCSS() {
print ( IFMConfig::showpath == 1 ) ? realpath( IFMConfig::root_dir ) : "/"; print '
print '</span></span><input class="form-control" id="currentDir" aria-describedby="currentDirLabel" type="text"> <style type="text/css">';?> @@@src/includes/bootstrap.min.css@@@ <?php print '</style>
</div> <style type="text/css">';?> @@@src/includes/fontello-embedded.css@@@ <?php print '</style>
</div> <style type="text/css">';?> @@@src/style.css@@@ <?php print '</style>
</form> ';
<ul class="nav navbar-nav navbar-right"> }
<li><a id="refresh"><span title="refresh" class="icon icon-arrows-cw"></span> <span class="visible-xs">refresh</span></a></li>';
if( IFMConfig::upload == 1 ) { public function getJS() {
print '<li><a id="upload"><span title="upload" class="icon icon-upload"></span> <span class="visible-xs">upload</span></a></li>'; print '
}
if( IFMConfig::createfile == 1 ) {
print '<li><a id="createFile"><span title="new file" class="icon icon-doc-inv"></span> <span class="visible-xs">new file</span></a></li>';
}
if( IFMConfig::createdir == 1 ) {
print '<li><a id="createDir"><span title="new folder" class="icon icon-folder"></span> <span class="visible-xs">new folder</span></a></li>';
}
print '<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="icon icon-down-open"></span></a><ul class="dropdown-menu" role="menu">';
$options = false;
if( IFMConfig::remoteupload == 1 ) {
print '<li><a onclick="ifm.remoteUploadDialog();return false;"><span class="icon icon-upload-cloud"></span> remote upload</a></li>';
$options = true;
}
if( IFMConfig::ajaxrequest == 1 ) {
print '<li><a onclick="ifm.ajaxRequestDialog();return false;"><span class="icon icon-link-ext"></span> ajax request</a></li>';
$options = true;
}
if( !$options ) print '<li>No options available</li>';
print '</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<table id="filetable" class="table">
<thead>
<tr>
<th>Filename</th>';
if( IFMConfig::download == 1 ) print '<th><!-- column for download link --></th>';
if( IFMConfig::showlastmodified == 1 ) print '<th>last modified</th>';
if( IFMConfig::showfilesize == 1 ) print '<th>size</th>';
if( IFMConfig::showpermissions > 0 ) print '<th class="hidden-xs">permissions</th>';
if( IFMConfig::showowner == 1 && function_exists( "posix_getpwuid" ) ) print '<th class="hidden-xs hidden-sm">owner</th>';
if( IFMConfig::showgroup == 1 && function_exists( "posix_getgrgid" ) ) print '<th class="hidden-xs hidden-sm hidden-md">group</th>';
if( in_array( 1, array( IFMConfig::edit, IFMConfig::rename, IFMConfig::delete, IFMConfig::zipnload, IFMConfig::extract ) ) ) print '<th class="buttons"><!-- column for buttons --></th>';
print '</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="container">
<div class="panel panel-default footer"><div class="panel-body">IFM - improved file manager | ifm.php hidden | <a href="http://github.com/misterunknown/ifm">Visit the project on GitHub</a></div></div>
</div>
<script>';?> @@@src/includes/ace.js@@@ <?php print '</script> <script>';?> @@@src/includes/ace.js@@@ <?php print '</script>
<script>';?> @@@src/includes/jquery.min.js@@@ <?php print '</script> <script>';?> @@@src/includes/jquery.min.js@@@ <?php print '</script>
<script>';?> @@@src/includes/bootstrap.min.js@@@ <?php print '</script> <script>';?> @@@src/includes/bootstrap.min.js@@@ <?php print '</script>
<script>';?> @@@src/includes/bootstrap-notify.min.js@@@ <?php print '</script> <script>';?> @@@src/includes/bootstrap-notify.min.js@@@ <?php print '</script>
<script>';?> @@@src/includes/ekko-lightbox.min.js@@@ <?php print '</script> <script>';?> @@@src/includes/bootstrap-treeview.min.js@@@ <?php print '</script>
<script>';?> @@@src/includes/mustache.min.js@@@ <?php print '</script>
<script>';?> @@@src/ifm.js@@@ <?php print '</script> <script>';?> @@@src/ifm.js@@@ <?php print '</script>
</body>
</html>
'; ';
} }
@@ -135,40 +126,55 @@ class IFM {
$this->getFiles( $_REQUEST["dir"] ); $this->getFiles( $_REQUEST["dir"] );
else else
$this->getFiles( "" ); $this->getFiles( "" );
} else { }
elseif( $_REQUEST["api"] == "getConfig" ) {
$this->getConfig();
} elseif( $_REQUEST["api"] == "getTemplates" ) {
echo json_encode( $this->getTemplates() );
} else {
if( isset( $_REQUEST["dir"] ) && $this->isPathValid( $_REQUEST["dir"] ) ) { if( isset( $_REQUEST["dir"] ) && $this->isPathValid( $_REQUEST["dir"] ) ) {
switch( $_REQUEST["api"] ) { switch( $_REQUEST["api"] ) {
case "createDir": $this->createDir( $_REQUEST["dir"], $_REQUEST["dirname"] ); break; case "createDir": $this->createDir( $_REQUEST["dir"], $_REQUEST["dirname"] ); break;
case "saveFile": $this->saveFile( $_REQUEST ); break; case "saveFile": $this->saveFile( $_REQUEST ); break;
case "getContent": $this->getContent( $_REQUEST ); break; case "getContent": $this->getContent( $_REQUEST ); break;
case "deleteFile": $this->deleteFile( $_REQUEST ); break; case "delete": $this->deleteFile( $_REQUEST ); break;
case "renameFile": $this->renameFile( $_REQUEST ); break; case "rename": $this->renameFile( $_REQUEST ); break;
case "downloadFile": $this->downloadFile( $_REQUEST ); break; case "download": $this->downloadFile( $_REQUEST ); break;
case "extractFile": $this->extractFile( $_REQUEST ); break; case "extract": $this->extractFile( $_REQUEST ); break;
case "uploadFile": $this->uploadFile( $_REQUEST ); break; case "upload": $this->uploadFile( $_REQUEST ); break;
case "copyMove": $this->copyMove( $_REQUEST ); break;
case "changePermissions": $this->changePermissions( $_REQUEST ); break; case "changePermissions": $this->changePermissions( $_REQUEST ); break;
case "zipnload": $this->zipnload( $_REQUEST); break; case "zipnload": $this->zipnload( $_REQUEST); break;
case "remoteUpload": $this->remoteUpload( $_REQUEST ); break; case "remoteUpload": $this->remoteUpload( $_REQUEST ); break;
case "deleteMultipleFiles": $this->deleteMultipleFiles( $_REQUEST ); break; case "multidelete": $this->deleteMultipleFiles( $_REQUEST ); break;
default: echo json_encode(array("status"=>"ERROR", "message"=>"No valid api action given")); break; case "getFolderTree":
echo json_encode( array_merge( array( 0 => array( "text" => "/ [root]", "nodes" => array(), "dataAttributes" => array( "path" => realpath( $this->config['root_dir'] ) ) ) ), $this->getFolderTreeRecursive( $this->config['root_dir'] ) ) );
break;
default:
echo json_encode( array( "status" => "ERROR", "message" => "No valid api action given" ) );
break;
} }
} else { } else {
print json_encode(array("status"=>"ERROR", "message"=>"No valid working directory")); print json_encode(array("status"=>"ERROR", "message"=>"No valid working directory"));
} }
} }
exit( 0 );
} }
public function run() { public function run( $mode="standalone" ) {
if ( $this->checkAuth() ) { if ( $this->checkAuth() ) {
// go to our root_dir // go to our root_dir
if( ! is_dir( realpath( IFMConfig::root_dir ) ) || ! is_readable( realpath( IFMConfig::root_dir ) ) ) if( ! is_dir( realpath( $this->config['root_dir'] ) ) || ! is_readable( realpath( $this->config['root_dir'] ) ) )
die( "Cannot access root_dir."); die( "Cannot access root_dir.");
else else
chdir( realpath( IFMConfig::root_dir ) ); chdir( realpath( $this->config['root_dir'] ) );
if ( ! isset($_REQUEST['api']) ) { $this->mode = $mode;
$this->getApplication(); if ( isset( $_REQUEST['api'] ) || $mode == "api" ) {
} else {
$this->handleRequest(); $this->handleRequest();
} elseif( $mode == "standalone" ) {
$this->getApplication();
} else {
$this->getInlineApplication();
} }
} }
} }
@@ -177,61 +183,53 @@ class IFM {
api functions api functions
*/ */
private function getFiles($dir) { private function getFiles( $dir ) {
// SECURITY FUNCTION (check that we don't operate on a higher level that the script itself) $dir = $this->getValidDir( $dir );
$dir=$this->getValidDir($dir); $this->chDirIfNecessary( $dir );
// now we change in our target directory
$this->chDirIfNecessary($dir); unset( $files ); unset( $dirs ); $files = array(); $dirs = array();
// unset our file and directory arrays
unset($files); unset($dirs); $files = array(); $dirs = array(); if( $handle = opendir( "." ) ) {
// so lets loop over our directory while( false !== ( $result = readdir( $handle ) ) ) {
if ($handle = opendir(".")) { if( $result == basename( $_SERVER['SCRIPT_NAME'] ) && $this->getScriptRoot() == getcwd() ) { }
while (false !== ($result = readdir($handle))) { // this awesome statement is the correct way to loop over a directory :) elseif( ( $result == ".htaccess" || $result==".htpasswd" ) && $this->config['showhtdocs'] != 1 ) {}
if( $result == basename( $_SERVER['SCRIPT_NAME'] ) && $this->getScriptRoot() == getcwd() ) { } // we don't want to see the script itself elseif( $result == "." ) {}
elseif( ( $result == ".htaccess" || $result==".htpasswd" ) && IFMConfig::showhtdocs != 1 ) {} // check if we are granted to see .ht-docs elseif( $result != ".." && substr( $result, 0, 1 ) == "." && $this->config['showhiddenfiles'] != 1 ) {}
elseif( $result == "." ) {} // the folder itself will also be invisible else {
elseif( $result != ".." && substr( $result, 0, 1 ) == "." && IFMConfig::showhiddenfiles != 1 ) {} // eventually hide hidden files, if we should not see them
elseif( ! @is_readable( $result ) ) {}
else { // thats are the files we should see
$item = array(); $item = array();
$i = 0;
$item["name"] = $result; $item["name"] = $result;
$i++;
if( is_dir($result) ) { if( is_dir($result) ) {
$item["type"] = "dir"; $item["type"] = "dir";
} else {
$item["type"] = "file";
}
if( is_dir( $result ) ) {
if( $result == ".." ) if( $result == ".." )
$item["icon"] = "icon icon-up-open"; $item["icon"] = "icon icon-up-open";
else else
$item["icon"] = "icon icon-folder-empty"; $item["icon"] = "icon icon-folder-empty";
} else { } else {
$item["type"] = "file";
$type = substr( strrchr( $result, "." ), 1 ); $type = substr( strrchr( $result, "." ), 1 );
$item["icon"] = $this->getTypeIcon( $type ); $item["icon"] = $this->getTypeIcon( $type );
} }
if( IFMConfig::showlastmodified == 1 ) { $item["lastmodified"] = date( "d.m.Y, G:i e", filemtime( $result ) ); } if( $this->config['showlastmodified'] == 1 ) { $item["lastmodified"] = date( "d.m.Y, G:i e", filemtime( $result ) ); }
if( IFMConfig::showfilesize == 1 ) { if( $this->config['showfilesize'] == 1 ) {
$item["filesize"] = filesize( $result ); $item["size"] = filesize( $result );
if( $item["filesize"] > 1073741824 ) $item["filesize"] = round( ( $item["filesize"]/1073741824 ), 2 ) . " GB"; if( $item["size"] > 1073741824 ) $item["size"] = round( ( $item["size"]/1073741824 ), 2 ) . " GB";
elseif($item["filesize"]>1048576)$item["filesize"] = round( ( $item["filesize"]/1048576 ), 2 ) . " MB"; elseif($item["size"]>1048576)$item["size"] = round( ( $item["size"]/1048576 ), 2 ) . " MB";
elseif($item["filesize"]>1024)$item["filesize"] = round( ( $item["filesize"]/1024 ), 2 ) . " KB"; elseif($item["size"]>1024)$item["size"] = round( ( $item["size"]/1024 ), 2 ) . " KB";
else $item["filesize"] = $item["filesize"] . " Byte"; else $item["size"] = $item["size"] . " Byte";
} }
if( IFMConfig::showpermissions > 0 ) { if( $this->config['showpermissions'] > 0 ) {
if( IFMConfig::showpermissions == 1 ) $item["fileperms"] = substr( decoct( fileperms( $result ) ), -3 ); if( $this->config['showpermissions'] == 1 ) $item["fileperms"] = substr( decoct( fileperms( $result ) ), -3 );
elseif( IFMConfig::showpermissions == 2 ) $item["fileperms"] = $this->filePermsDecode( fileperms( $result ) ); elseif( $this->config['showpermissions'] == 2 ) $item["fileperms"] = $this->filePermsDecode( fileperms( $result ) );
if( $item["fileperms"] == "" ) $item["fileperms"] = " "; if( $item["fileperms"] == "" ) $item["fileperms"] = " ";
$item["filepermmode"] = ( IFMConfig::showpermissions == 1 ) ? "short" : "long"; $item["filepermmode"] = ( $this->config['showpermissions'] == 1 ) ? "short" : "long";
} }
if( IFMConfig::showowner == 1 ) { if( $this->config['showowner'] == 1 ) {
if ( function_exists( "posix_getpwuid" ) && fileowner($result) !== false ) { if ( function_exists( "posix_getpwuid" ) && fileowner($result) !== false ) {
$ownerarr = posix_getpwuid( fileowner( $result ) ); $ownerarr = posix_getpwuid( fileowner( $result ) );
$item["owner"] = $ownerarr['name']; $item["owner"] = $ownerarr['name'];
} else $item["owner"] = false; } else $item["owner"] = false;
} }
if( IFMConfig::showgroup == 1 ) { if( $this->config['showgroup'] == 1 ) {
if( function_exists( "posix_getgrgid" ) && filegroup( $result ) !== false ) { if( function_exists( "posix_getgrgid" ) && filegroup( $result ) !== false ) {
$grouparr = posix_getgrgid( filegroup( $result ) ); $grouparr = posix_getgrgid( filegroup( $result ) );
$item["group"] = $grouparr['name']; $item["group"] = $grouparr['name'];
@@ -248,8 +246,71 @@ class IFM {
echo json_encode( array_merge( $dirs, $files ) ); echo json_encode( array_merge( $dirs, $files ) );
} }
private function getConfig() {
$ret = $this->config;
$ret['inline'] = ( $this->mode == "inline" ) ? true : false;
$ret['isDocroot'] = ( realpath( $this->config['root_dir'] ) == dirname( __FILE__ ) ) ? "true" : "false";
echo json_encode( $ret );
}
private function getFolderTreeRecursive( $start_dir ) {
$ret = array();
$start_dir = realpath( $start_dir );
if( $handle = opendir( $start_dir ) ) {
while (false !== ( $result = readdir( $handle ) ) ) {
if( is_dir( $this->pathCombine( $start_dir, $result ) ) && $result != "." && $result != ".." ) {
array_push( $ret, array( "text" => $result, "dataAttributes" => array( "path" => $this->pathCombine( $start_dir, $result ) ), "nodes" => $this->getFolderTreeRecursive( $this->pathCombine( $start_dir, $result ) ) ) );
}
}
}
sort( $ret );
return $ret;
}
private function copyMove( $d ) {
if( $this->config['copymove'] != 1 ) {
echo json_encode( array( "status" => "ERROR", "message" => "No permission to copy or move files." ) );
exit( 1 );
}
$this->chDirIfNecessary( $d['dir'] );
if( ! isset( $d['destination'] ) || ! $this->isPathValid( realpath( $d['destination'] ) ) ) {
echo json_encode( array( "status" => "ERROR", "message" => "No valid destination directory given." ) );
exit( 1 );
}
if( ! file_exists( $d['filename'] ) ) {
echo json_encode( array( "status" => "ERROR", "message" => "No valid filename given." ) );
exit( 1 );
}
if( $d['action'] == "copy" ) {
if( $this->copyr( $d['filename'], $d['destination'] ) ) {
echo json_encode( array( "status" => "OK", "message" => "File(s) were successfully copied." ) );
exit( 0 );
} else {
$err = error_get_last();
echo json_encode( array( "status" => "ERROR", "message" => $err['message'] ) );
exit( 1 );
}
} elseif( $d['action'] == "move" ) {
if( rename( $d['filename'], $this->pathCombine( $d['destination'], basename( $d['filename'] ) ) ) ) {
echo json_encode( array( "status" => "OK", "message" => "File(s) were successfully moved." ) );
exit( 0 );
} else {
$err = error_get_last();
echo json_encode( array( "status" => "ERROR", "message" => $err['message'] ) );
exit( 1 );
}
} else {
echo json_encode( array( "status" => "ERROR", "message" => "No valid action given." ) );
exit( 1 );
}
}
// creates a directory // creates a directory
private function createDir($w, $dn) { private function createDir($w, $dn) {
if( $this->config['createDir'] != 1 ) {
echo json_encode( array( "status" => "ERROR", "message" => "No permission to create directories.") );
exit( 1 );
}
if( $dn == "" ) { if( $dn == "" ) {
echo json_encode( array( "status" => "ERROR", "message" => "No valid directory name") ); echo json_encode( array( "status" => "ERROR", "message" => "No valid directory name") );
} elseif( strpos( $dn, '/' ) !== false ) echo json_encode( array( "status" => "ERROR", "message" => "No slashes allowed in directory names" ) ); } elseif( strpos( $dn, '/' ) !== false ) echo json_encode( array( "status" => "ERROR", "message" => "No slashes allowed in directory names" ) );
@@ -264,14 +325,18 @@ class IFM {
} }
// save a file // save a file
private function saveFile(array $d) { private function saveFile( $d ) {
if( ( file_exists( $this->pathCombine( $d['dir'], $d['filename'] ) ) && $this->config['edit'] != 1 ) || ( ! file_exists( $this->pathCombine( $d['dir'], $d['filename'] ) ) && $this->config['createfile'] != 1 ) ) {
echo json_encode( array( "status" => "ERROR", "message" => "You are not allowed to edit/create this file." ) );
exit( 1 );
}
if( isset( $d['filename'] ) && $d['filename'] != "" ) { if( isset( $d['filename'] ) && $d['filename'] != "" ) {
// if you are not allowed to see .ht-docs you can't save one // if you are not allowed to see .ht-docs you can't save one
if( IFMConfig::showhtdocs != 1 && substr( $d['filename'], 0, 3 ) == ".ht" ) { if( $this->config['showhtdocs'] != 1 && substr( $d['filename'], 0, 3 ) == ".ht" ) {
echo json_encode( array( "status" => "ERROR", "message" => "You are not allowed to edit or create htdocs" ) ); echo json_encode( array( "status" => "ERROR", "message" => "You are not allowed to edit or create htdocs" ) );
} }
// same with hidden files // same with hidden files
elseif( IFMConfig::showhiddenfiles != 1 && substr( $d['filename'], 0, 1 ) == "." ) { elseif( $this->config['showhiddenfiles'] != 1 && substr( $d['filename'], 0, 1 ) == "." ) {
echo json_encode( array( "status" => "ERROR", "message" => "You are not allowed to edit or create hidden files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "You are not allowed to edit or create hidden files" ) );
} }
elseif(strpos($d['filename'],'/')!==false) { elseif(strpos($d['filename'],'/')!==false) {
@@ -298,7 +363,7 @@ class IFM {
// gets the content of a file // gets the content of a file
// notice: if the content is not JSON encodable it returns an error // notice: if the content is not JSON encodable it returns an error
private function getContent( array $d ) { private function getContent( array $d ) {
if( IFMConfig::edit != 1 ) echo json_encode( array( "status" => "ERROR", "message" => "No permission to edit files" ) ); if( $this->config['edit'] != 1 ) echo json_encode( array( "status" => "ERROR", "message" => "You are not allowed to edit files." ) );
else { else {
$this->chDirIfNecessary( $d['dir'] ); $this->chDirIfNecessary( $d['dir'] );
if( file_exists( $d['filename'] ) ) { if( file_exists( $d['filename'] ) ) {
@@ -311,7 +376,7 @@ class IFM {
// deletes a file or a directory (recursive!) // deletes a file or a directory (recursive!)
private function deleteFile( array $d ) { private function deleteFile( array $d ) {
if( IFMConfig::delete != 1 ) { if( $this->config['delete'] != 1 ) {
echo json_encode( array( "status" => "ERROR", "message" => "No permission to delete files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "No permission to delete files" ) );
} }
else { else {
@@ -336,7 +401,7 @@ class IFM {
// deletes a bunch of files or directories // deletes a bunch of files or directories
private function deleteMultipleFiles( array $d ) { private function deleteMultipleFiles( array $d ) {
if( IFMConfig::delete != 1 || IFMConfig::multiselect != 1 ) echo json_encode( array( "status" => "ERROR", "message" => "No permission to delete multiple files" ) ); if( $this->config['delete'] != 1 ) echo json_encode( array( "status" => "ERROR", "message" => "No permission to delete files" ) );
else { else {
$this->chDirIfNecessary( $d['dir'] ); $this->chDirIfNecessary( $d['dir'] );
$err = array(); $errFLAG = -1; // -1 -> no files deleted; 0 -> at least some files deleted; 1 -> all files deleted $err = array(); $errFLAG = -1; // -1 -> no files deleted; 0 -> at least some files deleted; 1 -> all files deleted
@@ -369,15 +434,15 @@ class IFM {
// renames a file // renames a file
private function renameFile( array $d ) { private function renameFile( array $d ) {
if( IFMConfig::rename != 1 ) { if( $this->config['rename'] != 1 ) {
echo json_encode( array( "status" => "ERROR", "message" => "No permission to rename files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "No permission to rename files" ) );
} else { } else {
$this->chDirIfNecessary( $d['dir'] ); $this->chDirIfNecessary( $d['dir'] );
if( strpos( $d['newname'], '/' ) !== false ) if( strpos( $d['newname'], '/' ) !== false )
echo json_encode( array( "status" => "ERROR", "message" => "No slashes allowed in filenames" ) ); echo json_encode( array( "status" => "ERROR", "message" => "No slashes allowed in filenames" ) );
elseif( IFMConfig::showhtdocs != 1 && ( substr( $d['newname'], 0, 3) == ".ht" || substr( $d['filename'], 0, 3 ) == ".ht" ) ) elseif( $this->config['showhtdocs'] != 1 && ( substr( $d['newname'], 0, 3) == ".ht" || substr( $d['filename'], 0, 3 ) == ".ht" ) )
echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to rename this file" ) ); echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to rename this file" ) );
elseif( IFMConfig::showhiddenfiles != 1 && ( substr( $d['newname'], 0, 1) == "." || substr( $d['filename'], 0, 1 ) == "." ) ) elseif( $this->config['showhiddenfiles'] != 1 && ( substr( $d['newname'], 0, 1) == "." || substr( $d['filename'], 0, 1 ) == "." ) )
echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to rename file" ) ); echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to rename file" ) );
else { else {
if( @rename( $d['filename'], $d['newname'] ) ) if( @rename( $d['filename'], $d['newname'] ) )
@@ -390,73 +455,58 @@ class IFM {
// provides a file for downloading // provides a file for downloading
private function downloadFile( array $d ) { private function downloadFile( array $d ) {
if( IFMConfig::download != 1 ) if( $this->config['download'] != 1 )
echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to download files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to download files" ) );
elseif( IFMConfig::showhtdocs != 1 && ( substr( $d['filename'], 0, 3 ) == ".ht" || substr( $d['filename'],0,3 ) == ".ht" ) ) elseif( $this->config['showhtdocs'] != 1 && ( substr( $d['filename'], 0, 3 ) == ".ht" || substr( $d['filename'],0,3 ) == ".ht" ) )
echo json_encode( array( "status" => "ERROR", "message"=>"Not allowed to download htdocs" ) ); echo json_encode( array( "status" => "ERROR", "message"=>"Not allowed to download htdocs" ) );
elseif( IFMConfig::showhiddenfiles != 1 && ( substr( $d['filename'], 0, 1 ) == "." || substr( $d['filename'],0,1 ) == "." ) ) elseif( $this->config['showhiddenfiles'] != 1 && ( substr( $d['filename'], 0, 1 ) == "." || substr( $d['filename'],0,1 ) == "." ) )
echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to download hidden files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to download hidden files" ) );
else { else {
$this->chDirIfNecessary( $d["dir"] ); $this->chDirIfNecessary( $d["dir"] );
$this->file_download( $d['filename'] ); $this->fileDownload( $d['filename'] );
} }
} }
// extracts a zip-archive // extracts a zip-archive
private function extractFile( array $d ) { private function extractFile( array $d ) {
if( IFMConfig::extract != 1 ) if( $this->config['extract'] != 1 )
echo json_encode( array( "status" => "ERROR", "message" => "No permission to extract files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "No permission to extract files" ) );
else { else {
$this->chDirIfNecessary( $d['dir'] ); $this->chDirIfNecessary( $d['dir'] );
if( ! file_exists( $d['filename'] ) || substr( $d['filename'],-4 ) != ".zip" ) if( ! file_exists( $d['filename'] ) || substr( $d['filename'],-4 ) != ".zip" ) {
echo json_encode( array( "status" => "ERROR","message" => "No valid zip file found" ) ); echo json_encode( array( "status" => "ERROR","message" => "No valid zip file found" ) );
else { exit( 1 );
if( ! isset( $d['targetdir'] ) ) }
$d['targetdir'] = ""; if( ! isset( $d['targetdir'] ) || trim( $d['targetdir'] ) == "" )
if( strpos( $d['targetdir'], "/" ) !== false ) $d['targetdir'] = "./";
echo json_encode( array( "status" => "ERROR","message" => "Target directory must not contain slashes" ) ); if( ! $this->isPathValid( $d['targetdir'] ) ) {
else { echo json_encode( array( "status" => "ERROR","message" => "Target directory is not valid." ) );
switch( $d['targetdir'] ){ exit( 1 );
case "": }
if( $this->unzip( $_POST["filename"] ) ) if( ! is_dir( $d['targetdir'] ) && ! mkdir( $d['targetdir'], 0777, true ) ) {
echo json_encode( array( "status" => "OK","message" => "File successfully extracted." ) ); echo json_encode( array( "status" => "ERROR","message" => "Could not create target directory." ) );
else exit( 1 );
echo json_encode( array( "status" => "ERROR","message" => "File could not be extracted" ) ); }
break; if( ! IFMZip::extract( $d['filename'], $d['targetdir'] ) ) {
default: echo json_encode( array( "status" => "ERROR","message" => "File could not be extracted" ) );
if( ! mkdir( $d['targetdir'] ) ) } else {
echo json_encode( array( "status" => "ERROR","message" => "Could not create target directory" ) ); echo json_encode( array( "status" => "OK","message" => "File successfully extracted." ) );
else {
chdir( $d['targetdir'] );
if( ! $this->unzip( "../" . $d["filename"] ) ) {
chdir( ".." );
rmdir( $d['targetdir'] );
echo json_encode( array( "status" => "ERROR","message" => "Could not extract file" ) );
}
else {
chdir( ".." );
echo json_encode( array( "status" => "OK","message" => "File successfully extracted" ) );
}
}
break;
}
}
} }
} }
} }
// uploads a file // uploads a file
private function uploadFile( array $d ) { private function uploadFile( array $d ) {
if( IFMConfig::upload != 1 ) if( $this->config['upload'] != 1 )
echo json_encode( array( "status" => "ERROR", "message" => "No permission to upload files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "No permission to upload files" ) );
elseif( !isset( $_FILES['file'] ) ) elseif( !isset( $_FILES['file'] ) )
echo json_encode( array( "file" => $_FILE,"files" => $_FILES ) ); echo json_encode( array( "file" => $_FILE,"files" => $_FILES ) );
else { else {
$this->chDirIfNecessary( $d['dir'] ); $this->chDirIfNecessary( $d['dir'] );
$newfilename = ( isset( $d["newfilename"] ) && $d["newfilename"]!="" ) ? $d["newfilename"] : $_FILES['file']['name']; $newfilename = ( isset( $d["newfilename"] ) && $d["newfilename"]!="" ) ? $d["newfilename"] : $_FILES['file']['name'];
if( IFMConfig::showhtdocs != 1 && ( substr( $newfilename, 0, 3 ) == ".ht" || substr( $newfilename,0,3 ) == ".ht" ) ) if( $this->config['showhtdocs'] != 1 && ( substr( $newfilename, 0, 3 ) == ".ht" || substr( $newfilename,0,3 ) == ".ht" ) )
echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to upload htdoc file" ) ); echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to upload htdoc file" ) );
elseif( IFMConfig::showhiddenfiles != 1 && ( substr( $newfilename, 0, 1 ) == "." || substr( $newfilename,0,1 ) == "." ) ) elseif( $this->config['showhiddenfiles'] != 1 && ( substr( $newfilename, 0, 1 ) == "." || substr( $newfilename,0,1 ) == "." ) )
echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to upload hidden file" ) ); echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to upload hidden file" ) );
else { else {
if( $_FILES['file']['tmp_name'] ) { if( $_FILES['file']['tmp_name'] ) {
@@ -478,7 +528,7 @@ class IFM {
// change permissions of a file // change permissions of a file
private function changePermissions( array $d ) { private function changePermissions( array $d ) {
if( IFMConfig::chmod != 1 ) echo json_encode( array( "status" => "ERROR", "message" => "No rights to change permissions" ) ); if( $this->config['chmod'] != 1 ) echo json_encode( array( "status" => "ERROR", "message" => "No rights to change permissions" ) );
elseif( ! isset( $d["chmod"] )||$d['chmod']=="" ) echo json_encode( array( "status" => "ERROR", "message" => "Could not identify new permissions" ) ); elseif( ! isset( $d["chmod"] )||$d['chmod']=="" ) echo json_encode( array( "status" => "ERROR", "message" => "Could not identify new permissions" ) );
elseif( ! $this->isPathValid( $this->pathCombine( $d['dir'],$d['filename'] ) ) ) { echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to change the permissions" ) ); } elseif( ! $this->isPathValid( $this->pathCombine( $d['dir'],$d['filename'] ) ) ) { echo json_encode( array( "status" => "ERROR", "message" => "Not allowed to change the permissions" ) ); }
else { else {
@@ -517,7 +567,7 @@ class IFM {
// zips a directory and provides it for downloading // zips a directory and provides it for downloading
// it creates a temporary zip file in the current directory, so it has to be as much space free as the file size is // it creates a temporary zip file in the current directory, so it has to be as much space free as the file size is
private function zipnload( array $d ) { private function zipnload( array $d ) {
if( IFMConfig::zipnload != 1 ) if( $this->config['zipnload'] != 1 )
echo json_encode( array( "status" => "ERROR", "message" => "No permission to download directories" ) ); echo json_encode( array( "status" => "ERROR", "message" => "No permission to download directories" ) );
else { else {
$this->chDirIfNecessary( $d['dir'] ); $this->chDirIfNecessary( $d['dir'] );
@@ -527,16 +577,16 @@ class IFM {
echo json_encode( array( "status" => "ERROR", "message" => "Filename not allowed" ) ); echo json_encode( array( "status" => "ERROR", "message" => "Filename not allowed" ) );
else { else {
unset( $zip ); unset( $zip );
$dfile = $this->pathCombine( IFMConfig::tmp_dir, uniqid( "ifm-tmp-" ) . ".zip" ); // temporary filename $dfile = $this->pathCombine( $this->config['tmp_dir'], uniqid( "ifm-tmp-" ) . ".zip" ); // temporary filename
try { try {
IFMZip::create_zip( realpath( $d['filename'] ), $dfile, ( $d['filename'] == "." ) ); IFMZip::create( realpath( $d['filename'] ), $dfile, ( $d['filename'] == "." ) );
if( $d['filename'] == "." ) { if( $d['filename'] == "." ) {
if( getcwd() == $this->getScriptRoot() ) if( getcwd() == $this->getScriptRoot() )
$d['filename'] = "root"; $d['filename'] = "root";
else else
$d['filename'] = basename( getcwd() ); $d['filename'] = basename( getcwd() );
} }
$this->file_download( $dfile, $d['filename'] . ".zip" ); $this->fileDownload( $dfile, $d['filename'] . ".zip" );
} catch ( Exception $e ) { } catch ( Exception $e ) {
echo "An error occured: " . $e->getMessage(); echo "An error occured: " . $e->getMessage();
} finally { } finally {
@@ -548,7 +598,7 @@ class IFM {
// uploads a file from an other server using the curl extention // uploads a file from an other server using the curl extention
private function remoteUpload( array $d ) { private function remoteUpload( array $d ) {
if( IFMConfig::remoteupload != 1 ) if( $this->config['remoteupload'] != 1 )
echo json_encode( array( "status" => "ERROR", "message" => "No permission to remote upload files" ) ); echo json_encode( array( "status" => "ERROR", "message" => "No permission to remote upload files" ) );
elseif( !isset( $d['method'] ) || !in_array( $d['method'], array( "curl", "file" ) ) ) elseif( !isset( $d['method'] ) || !in_array( $d['method'], array( "curl", "file" ) ) )
echo json_encode( array( "status" => "error", "message" => "No valid method given. Valid methods: ['curl', 'file']" ) ); echo json_encode( array( "status" => "error", "message" => "No valid method given. Valid methods: ['curl', 'file']" ) );
@@ -601,7 +651,7 @@ class IFM {
*/ */
public function checkAuth() { public function checkAuth() {
if( IFMConfig::auth == 1 && ( ! isset( $_SESSION['auth'] ) || $_SESSION['auth'] !== true ) ) { if( $this->config['auth'] == 1 && ( ! isset( $_SESSION['auth'] ) || $_SESSION['auth'] !== true ) ) {
$login_failed = false; $login_failed = false;
if( isset( $_POST["user"] ) && isset( $_POST["pass"] ) ) { if( isset( $_POST["user"] ) && isset( $_POST["pass"] ) ) {
if( $this->checkCredentials( $_POST["user"], $_POST["pass"] ) ) { if( $this->checkCredentials( $_POST["user"], $_POST["pass"] ) ) {
@@ -631,21 +681,46 @@ class IFM {
} }
} }
private function checkCredentials($user, $pass) { private function checkCredentials( $user, $pass ) {
list($src, $srcopt) = explode(";", IFMConfig::auth_source, 2); list( $src, $srcopt ) = explode( ";", $this->config['auth_source'], 2 );
switch($src) { switch( $src ) {
case "inline": case "inline":
list($uname, $hash) = explode(":", $srcopt); list( $uname, $hash ) = explode( ":", $srcopt );
return password_verify( $pass, trim( $hash ) ) ? ( $uname == $user ) : false;
break; break;
case "file": case "file":
if(@file_exists($srcopt) && @is_readable($srcopt)) { if( @file_exists( $srcopt ) && @is_readable( $srcopt ) ) {
list($uname, $hash) = explode(":", fgets(fopen($srcopt, 'r'))); list( $uname, $hash ) = explode( ":", fgets( fopen( $srcopt, 'r' ) ) );
return password_verify( $pass, trim( $hash ) ) ? ( $uname == $user ) : false;
} else { } else {
return false; return false;
} }
break; break;
case "ldap":
$authenticated = false;
list( $ldap_server, $rootdn ) = explode( ";", $srcopt );
$u = "uid=" . $user . "," . $rootdn;
if( ! $ds = ldap_connect( $ldap_server ) ) {
trigger_error( "Could not reach the ldap server.", E_USER_ERROR );
return false;
}
ldap_set_option( $ds, LDAP_OPT_PROTOCOL_VERSION, 3 );
if( $ds ) {
$ldbind = @ldap_bind( $ds, $u, $pass );
if( $ldbind ) {
$authenticated = true;
} else {
trigger_error( ldap_error( $ds ), E_USER_ERROR );
$authenticated = false;
}
ldap_unbind( $ds );
} else {
$authenticated = false;
}
return $authenticated;
break;
} }
return password_verify($pass, trim($hash))?($uname == $user):false; return false;
} }
private function loginForm($loginFailed=false) { private function loginForm($loginFailed=false) {
@@ -692,7 +767,7 @@ class IFM {
return ""; return "";
} else { } else {
$rpDir = realpath( $dir ); $rpDir = realpath( $dir );
$rpConfig = realpath( IFMConfig::root_dir ); $rpConfig = realpath( $this->config['root_dir'] );
if( $rpConfig == "/" ) if( $rpConfig == "/" )
return $rpDir; return $rpDir;
elseif( $rpDir == $rpConfig ) elseif( $rpDir == $rpConfig )
@@ -703,15 +778,25 @@ class IFM {
} }
private function isPathValid( $dir ) { private function isPathValid( $dir ) {
$rpDir = realpath( $dir ); /**
$rpConfig = realpath( IFMConfig::root_dir ); * This function is also used to check non-existent paths, but the PHP realpath function returns false for
* nonexistent paths. Hence we need to check the path manually in the following lines.
*/
$tmp_d = $dir;
$tmp_missing_parts = array();
while( realpath( $tmp_d ) === false ) {
$tmp_i = pathinfo( $tmp_d );
array_push( $tmp_missing_parts, $tmp_i['filename'] );
$tmp_d = dirname( $tmp_d );
}
$rpDir = $this->pathCombine( realpath( $tmp_d ), implode( "/", array_reverse( $tmp_missing_parts ) ) );
$rpConfig = ( $this->config['root_dir'] == "" ) ? realpath( dirname( __FILE__ ) ) : realpath( $this->config['root_dir'] );
if( ! is_string( $rpDir ) || ! is_string( $rpConfig ) ) // can happen if open_basedir is in effect if( ! is_string( $rpDir ) || ! is_string( $rpConfig ) ) // can happen if open_basedir is in effect
return false; return false;
elseif( $rpDir == $rpConfig ) elseif( $rpDir == $rpConfig )
return true; return true;
elseif( 0 === strpos( $rpDir, $rpConfig ) ) { elseif( 0 === strpos( $rpDir, $rpConfig ) )
return true; return true;
}
else else
return false; return false;
} }
@@ -768,6 +853,53 @@ class IFM {
return 0; return 0;
} }
/**
* Copy a file, or recursively copy a folder and its contents
*
* @author Aidan Lister <aidan@php.net>
* @version 1.0.1
* @link http://aidanlister.com/2004/04/recursively-copying-directories-in-php/
* @param string $source Source path
* @param string $dest Destination path
* @return bool Returns TRUE on success, FALSE on failure
*/
private function copyr( $source, $dest )
{
// Check for symlinks
if (is_link($source)) {
return symlink(readlink($source), $dest);
}
// Simple copy for a file
if (is_file($source)) {
$dest = ( is_dir( $dest ) ) ? $this->pathCombine( $dest, basename( $source ) ) : $dest;
return copy($source, $dest);
} else {
$dest = $this->pathCombine( $dest, basename( $source ) );
}
// Make destination directory
if (!is_dir($dest)) {
mkdir($dest);
}
// Loop through the folder
$dir = dir($source);
while (false !== $entry = $dir->read()) {
// Skip pointers
if ($entry == '.' || $entry == '..') {
continue;
}
// Deep copy directories
$this->copyr("$source/$entry", "$dest/$entry");
}
// Clean up
$dir->close();
return true;
}
// combines two parts to a valid path // combines two parts to a valid path
private function pathCombine( $a, $b ) { private function pathCombine( $a, $b ) {
if( trim( $a ) == "" && trim( $b ) == "" ) if( trim( $a ) == "" && trim( $b ) == "" )
@@ -780,9 +912,9 @@ class IFM {
// check if filename is allowed // check if filename is allowed
private function allowedFileName( $f ) { private function allowedFileName( $f ) {
if( IFMConfig::showhtdocs != 1 && substr( $f, 0, 3 ) == ".ht" ) if( $this->config['showhtdocs'] != 1 && substr( $f, 0, 3 ) == ".ht" )
return false; return false;
elseif( IFMConfig::showhiddenfiles != 1 && substr( $f, 0, 1 ) == "." ) elseif( $this->config['showhiddenfiles'] != 1 && substr( $f, 0, 1 ) == "." )
return false; return false;
elseif( ! $this->isPathValid( $f ) ) elseif( ! $this->isPathValid( $f ) )
return false; return false;
@@ -795,19 +927,6 @@ class IFM {
return ( strtolower( $a['name'] ) < strtolower( $b['name'] ) ) ? -1 : 1; return ( strtolower( $a['name'] ) < strtolower( $b['name'] ) ) ? -1 : 1;
} }
// unzip an archive
private function unzip( $file ) {
$zip = new ZipArchive;
$res = $zip->open( $file );
if( $res === true ) {
$zip->extractTo( './' );
$zip->close();
return true;
} else {
return false;
}
}
// is cURL extention avaliable? // is cURL extention avaliable?
private function checkCurl() { private function checkCurl() {
if( !function_exists( "curl_init" ) || if( !function_exists( "curl_init" ) ||
@@ -817,7 +936,7 @@ class IFM {
else return true; else return true;
} }
private function file_download( $file, $name="" ) { private function fileDownload( $file, $name="" ) {
header( 'Content-Description: File Transfer' ); header( 'Content-Description: File Transfer' );
header( 'Content-Type: application/octet-stream' ); header( 'Content-Type: application/octet-stream' );
header( 'Content-Disposition: attachment; filename="' . ( trim( $name ) == "" ? basename( $file ) : $name ) . '"' ); header( 'Content-Disposition: attachment; filename="' . ( trim( $name ) == "" ? basename( $file ) : $name ) . '"' );
@@ -835,12 +954,51 @@ class IFM {
fclose($stdout_stream); fclose($stdout_stream);
} }
///helper private function getTemplates() {
$templates = array();
$templates['app'] = <<<'f00bar'
@@@src/templates/app.html@@@
f00bar;
$templates['filetable'] = <<<'f00bar'
@@@src/templates/filetable.html@@@
f00bar;
$templates['file'] = <<<'f00bar'
@@@src/templates/modal.file.html@@@
f00bar;
$templates['createdir'] = <<<'f00bar'
@@@src/templates/modal.createdir.html@@@
f00bar;
$templates['ajaxrequest'] = <<<'f00bar'
@@@src/templates/modal.ajaxrequest.html@@@
f00bar;
$templates['copymove'] = <<<'f00bar'
@@@src/templates/modal.copymove.html@@@
f00bar;
$templates['createdir'] = <<<'f00bar'
@@@src/templates/modal.createdir.html@@@
f00bar;
$templates['deletefile'] = <<<'f00bar'
@@@src/templates/modal.deletefile.html@@@
f00bar;
$templates['extractfile'] = <<<'f00bar'
@@@src/templates/modal.extractfile.html@@@
f00bar;
$templates['file'] = <<<'f00bar'
@@@src/templates/modal.file.html@@@
f00bar;
$templates['multidelete'] = <<<'f00bar'
@@@src/templates/modal.multidelete.html@@@
f00bar;
$templates['remoteupload'] = <<<'f00bar'
@@@src/templates/modal.remoteupload.html@@@
f00bar;
$templates['renamefile'] = <<<'f00bar'
@@@src/templates/modal.renamefile.html@@@
f00bar;
$templates['uploadfile'] = <<<'f00bar'
@@@src/templates/modal.uploadfile.html@@@
f00bar;
return $templates;
}
} }
/*
start program
*/
$ifm = new IFM();
$ifm->run();

View File

@@ -7,15 +7,18 @@ body {
font-size: 14pt; font-size: 14pt;
} }
#filetable tr td:nth-child(5) { #filetable td:nth-child(5), #filetable th:nth-child(5) {
text-align: center; text-align: center;
} }
#filetable tr td:nth-child(6) { #filetable td:nth-child(6), #filetable th:nth-child(6) {
text-align: center; text-align: center;
} }
#filetable tr td:last-child { #filetable tr td:last-child {
text-align: right; text-align: right;
} }
#filetable td:last-child a:hover {
text-decoration: none;
}
a { cursor: pointer !important; } a { cursor: pointer !important; }

82
src/templates/app.html Normal file
View File

@@ -0,0 +1,82 @@
<nav class="navbar navbar-inverse
{{^config.inline}}
navbar-fixed-top
{{/config.inline}}
">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand">IFM</a>
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse" id="navbar">
<form class="navbar-form navbar-left">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon" id="currentDirLabel">Content of <span id="docroot">{{showpath}}</span></span>
<input class="form-control" id="currentDir" aria-describedby="currentDirLabel" type="text">
</div>
</div>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a id="refresh"><span title="refresh" class="icon icon-arrows-cw"></span> <span class="visible-xs">refresh</span></a></li>
{{#config.upload}}
<li><a id="upload"><span title="upload" class="icon icon-upload"></span> <span class="visible-xs">upload</span></a></li>
{{/config.upload}}
{{#config.createfile}}
<li><a id="createFile"><span title="new file" class="icon icon-doc-inv"></span> <span class="visible-xs">new file</span></a></li>
{{/config.createfile}}
{{#config.createdir}}
<li><a id="createDir"><span title="new folder" class="icon icon-folder"></span> <span class="visible-xs">new folder</span></a></li>
{{/config.createdir}}
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="icon icon-down-open"></span></a>
<ul class="dropdown-menu" role="menu">
{{#config.remoteupload}}
<li><a id="buttonRemoteUpload"><span class="icon icon-upload-cloud"></span> remote upload</a></li>
{{/config.remoteupload}}
{{#config.ajaxrequest}}
<li><a id="buttonAjaxRequest"><span class="icon icon-link-ext"></span> ajax request</a></li>
{{/config.ajaxrequest}}
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<table id="filetable" class="table">
<thead>
<tr>
<th>Filename</th>
{{#config.download}}
<th><!-- column for download link --></th>
{{/config.download}}
{{#config.showlastmodified}}
<th>last modified</th>
{{/config.showlastmodified}}
{{#config.showfilesize}}
<th>size</th>
{{/config.showfilesize}}
{{#config.showpermissions}}
<th class="hidden-xs">permissions</th>
{{/config.showpermissions}}
{{#config.showowner}}
<th class="hidden-xs hidden-sm">owner</th>
{{/config.showowner}}
{{#config.showgroup}}
<th class="hidden-xs hidden-sm hidden-md">group</th>
{{/config.showgroup}}
<th class="buttons"><!-- column for buttons --></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="container">
<div class="panel panel-default footer"><div class="panel-body">IFM - improved file manager | ifm.php hidden | <a href="http://github.com/misterunknown/ifm">Visit the project on GitHub</a></div></div>
</div>

View File

@@ -0,0 +1,52 @@
<tbody>
{{#items}}
<tr class="clickable-row {{rowclasses}}" data-filename="{{name}}" data-eaction="{{eaction}}">
<td>
<a tabindex="0" id="{{guid}}" class="ifmitem" {{{tooltip}}} data-type="{{type}}">
<span class="{{icon}}"></span>
{{linkname}}
</a>
</td>
{{#config.download}}
<td>
<form id="d_{{guid}}">
<input type="hidden" name="dir" value="{{download.currentDir}}">
<input type="hidden" name="filename" value="{{download.name}}">
<input type="hidden" name="api" value="{{download.action}}">
</form>
<a tabindex="0" name="start_download" data-guid="{{guid}}">
<span class="{{download.icon}}"></span>
</a>
</td>
{{/config.download}}
{{#config.showlastmodified}}
<td>{{lastmodified}}</td>
{{/config.showlastmodified}}
{{#config.showfilesize}}
<td>{{size}}</td>
{{/config.showfilesize}}
{{#config.showpermissions}}
<td class="hidden-xs">
<input type="text" name="newpermissions" class="form-control {{filepermmode}}" value="{{fileperms}}" data-filename="{{name}}" {{readonly}}>
</td>
{{/config.showpermissions}}
{{#config.showowner}}
<td class="hidden-xs hidden-sm">
{{owner}}
</td>
{{/config.showowner}}
{{#config.showgroup}}
<td class="hidden-xs hidden-sm hidden-md">
{{group}}
</td>
{{/config.showgroup}}
<td>
{{#button}}
<a tabindex="0" name="do-{{action}}" data-name="{{name}}">
<span class="{{icon}}" title="{{title}}"</span>
</a>
{{/button}}
</td>
</tr>
{{/items}}
</tbody>

View File

@@ -0,0 +1,5 @@
<div id="savequestion">
<label>Do you want to save this file?</label><br>
<button id="buttonSave">Save</button>
<button id="buttonDismiss">Dismiss</button>
</div>

View File

@@ -0,0 +1,16 @@
<form id="formAjaxRequest">
<div class="modal-body">
<fieldset>
<label>URL</label><br>
<input class="form-control" type="text" id="ajaxurl" required><br>
<label>Data</label><br>
<textarea class="form-control" id="ajaxdata"></textarea><br>
<label>Method</label><br>
<input type="radio" name="arMethod" value="GET">GET</input><input type="radio" name="arMethod" value="POST" checked="checked">POST</input><br>
<button type="button" class="btn btn-success" id="buttonRequest">Request</button>
<button type="button" class="btn btn-default" id="buttonClose">Close</button><br>
<label>Response</label><br>
<textarea class="form-control" id="ajaxresponse"></textarea>
</fieldset>
</form>
</div>

View File

@@ -0,0 +1,13 @@
<form id="formCopyMove">
<fieldset>
<div class="modal-body">
<label>Select destination:</label>
<div id="copyMoveTree"><span class="icon icon-spin5"></span></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="copyButton">copy</button>
<button type="button" class="btn btn-default" id="moveButton">move</button>
<button type="button" class="btn btn-default" id="cancelButton">cancel</button>
</div>
</fieldset>
</form>

View File

@@ -0,0 +1,12 @@
<form id="formCreateDir">
<div class="modal-body">
<fieldset>
<label>Directoy name:</label>
<input class="form-control" type="text" name="dirname" value="" />
</fieldset>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="buttonSave">Save</button>
<button type="button" class="btn btn-default" id="buttonCancel">Cancel</button>
</div>
</form>

View File

@@ -0,0 +1,9 @@
<form id="formDeleteFile">
<div class="modal-body">
<label>Do you really want to delete the file {{filename}}?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="buttonYes">Yes</button>
<button type="button" class="btn btn-default" id="buttonNo">No</button>
</div>
</form>

View File

@@ -0,0 +1,23 @@
<form id="formExtractFile">
<fieldset>
<div class="modal-body">
<label>Extract {{filename}} to:</label>
<div class="input-group">
<span class="input-group-addon"><input type="radio" name="extractTargetLocation" value="./" checked="checked"></span>
<span class="form-control">./</span>
</div>
<div class="input-group">
<span class="input-group-addon"><input type="radio" name="extractTargetLocation" value="./{{destination}}"></span>
<span class="form-control">./{{destination}}</span>
</div>
<div class="input-group">
<span class="input-group-addon"><input type="radio" name="extractTargetLocation" value="custom"></span>
<input id="extractCustomLocation" type="text" class="form-control" placeholder="custom location" value="">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="buttonExtract">extract</button>
<button type="button" class="btn btn-default" id="buttonCancel">cancel</button>
</div>
</fieldset>
</form>

View File

@@ -0,0 +1,21 @@
<form id="formFile">
<div class="modal-body">
<fieldset>
<label>Filename:</label>
<input type="text" class="form-control" name="filename" value="{{filename}}"><br>
<div id="content" name="content"></div><br>
<button type="button" class="btn btn-default" id="editoroptions">editor options</button>
<div class="hide" id="editoroptions-head">options</div>
<div class="hide" id="editoroptions-content">
<input type="checkbox" id="editor-wordwrap"> word wrap</input><br>
<input type="checkbox" id="editor-softtabs"> use soft tabs</input>
<div class="input-group"><span class="input-group-addon">tabsize</span><input class="form-control" type="text" size="2" id="editor-tabsize"title="tabsize"></div>
</div>
</fieldset>
</div>
<div class="modal-footer">
<button type="button" id="buttonSave" class="btn btn-default">Save</button>
<button type="button" id="buttonSaveNotClose" class="btn btn-default">Save without closing</button>
<button type="button" id="buttonClose" class="btn btn-default">Close</button>
</div>
</form>

View File

@@ -0,0 +1,9 @@
<form id="formDeleteFiles">
<div class="modal-body">
<label>Do you really want to delete these {{count}} files?</label>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" id="buttonYes">Yes</button>
<button type="button" class="btn btn-default" id="buttonNo">No</button>
</div>
</form>

View File

@@ -0,0 +1,16 @@
<form id="formRemoteUpload">
<div class="modal-body">
<fieldset>
<label>Remote upload URL</label><br>
<input class="form-control" type="text" id="url" name="url" required><br>
<label>Filename (required)</label>
<input class="form-control" type="text" id="filename" name="filename" required><br>
<label>Method</label>
<input type="radio" name="method" value="curl" checked="checked">cURL<input type="radio" name="method" value="file">file</input><br>
</fieldset>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="buttonUpload">Upload</button>
<button type="button" class="btn btn-default" id="buttonCancel">Cancel</button>
</div>
</form>

View File

@@ -0,0 +1,10 @@
<div class="modal-body">
<form id="formRenameFile">
<fieldset>
<label>Rename {{filename}} to:</label>
<input class="form-control" type="text" name="newname" /><br>
<button class="btn btn-default" id="buttonRename">Rename</button>
<button class="btn btn-default" id="buttonCancel">Cancel</button>
</fieldset>
</form>
</div>

View File

@@ -0,0 +1,14 @@
<form id="formUploadFile">
<div class="modal-body">
<fieldset>
<label>Upload file</label><br>
<input class="file" type="file" name="ufile" id="ufile"><br>
<label>new filename</label>
<input class="form-control" type="text" name="newfilename"><br>
</fieldset>
</div>
<div class="modal-footer">
<button class="btn btn-default" id="buttonUpload">Upload</button>
<button class="btn btn-default" id="buttonCancel">Cancel</button>
</div>
</form>