Compare commits

...

27 Commits

Author SHA1 Message Date
Adphi 7453afcd93 v0.0.5 2020-01-26 15:59:19 +01:00
Adphi 3c90233c24 build dist 2020-01-26 15:53:43 +01:00
Adphi 659216dbd0 fix app compliance 2020-01-26 15:38:26 +01:00
Adphi 1d7ad2331b Fix core server lib import path 2019-07-20 14:02:52 +02:00
Adphi 533accb9dd updated build 2019-06-04 19:45:21 +02:00
Adphi 57d13d236b added version 16 to info.xml 2019-06-04 19:43:58 +02:00
Adphi 21988b68f5 removed memory infos 2019-01-24 20:22:55 +01:00
Adphi 507b630c41 rebuild 2019-01-24 20:04:24 +01:00
Adphi e882bc3354 rebuild 2019-01-24 20:02:06 +01:00
Adphi 47f7ecbb00 Merge branch 'master' of http://git.adphi.net/Adphi/OCCWeb 2019-01-24 19:56:32 +01:00
Adphi 045df01234 fix #1 remove dirty hack 2019-01-24 19:56:20 +01:00
Adphi c1687a8f69 added screenshot to README.md 2019-01-21 18:27:30 +01:00
Adphi 77a647c448 fix website url in info.xml 2019-01-21 18:24:52 +01:00
Adphi 3db25f358d improved app infos, add website 2019-01-21 18:22:46 +01:00
Adphi 3de7d661d7 improved app infos, fix app category 2019-01-21 18:17:23 +01:00
Adphi 461f2184b8 build 2019-01-21 17:57:25 +01:00
Adphi 12f727d918 Updated README.md 2019-01-19 21:00:53 +01:00
Adphi 1a3d8ebcc5 fix greetings not updating date on exit 2019-01-19 19:15:22 +01:00
Adphi 0722adf0b5 updated build 2019-01-19 18:45:27 +01:00
Adphi 6f370bb567 fixed android keyboard 2019-01-19 18:43:06 +01:00
Adphi 3d5a5806b9 updated build 2019-01-19 18:25:55 +01:00
Adphi b96cff9bde add scroll down on key down 2019-01-19 18:09:39 +01:00
Adphi 4fcfcc12bf Updated README.md 2019-01-19 17:45:02 +01:00
Adphi 45b142e46d Fix Controllers Files names 2019-01-19 17:20:07 +01:00
Adphi 0dba9d5c44 again... sorry. 2019-01-19 17:07:42 +01:00
Adphi c1a21cafb0 fix dist 2019-01-19 16:58:37 +01:00
Adphi ea221aa95f build dist 2019-01-19 16:52:40 +01:00
12 changed files with 97 additions and 439 deletions

View File

@ -1,4 +1,12 @@
# Test Nextcloud App
# OCCWeb terminal
### A web terminal for admins to launch Nextcloud's occ commands
![occweb](https://git.adphi.net/Adphi/OCCWeb/raw/master/appinfo/screenshot.png)
##
Place this app in **nextcloud/apps/**
## Building the app
@ -11,16 +19,24 @@ This requires the following things to be present:
* make
* which
* tar: for building the archive
* curl: used if phpunit and composer are not installed to fetch them from the web
* npm: for building and testing everything JS, only required if a package.json is placed inside the **js/** folder
* curl: used if phpunit and composer are not installed to fetch them
from the web
* npm: for building and testing everything JS, only required if a
package.json is placed inside the **js/** folder
The make command will install or update Composer dependencies if a composer.json is present and also **npm run build** if a package.json is present in the **js/** folder. The npm **build** script should use local paths for build systems and package managers, so people that simply want to build the app won't need to install npm libraries globally, e.g.:
The make command will install or update Composer dependencies if a
composer.json is present and also **npm run build** if a package.json
is present in the **js/** folder. The npm **build** script should use
local paths for build systems and package managers, so people that
simply want to build the app won't need to install npm libraries
globally, e.g.:
**package.json**:
```json
"scripts": {
"test": "node node_modules/gulp-cli/bin/gulp.js karma",
"prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
"prebuild": "npm install && node_modules/bower/bin/bower install &&
node_modules/bower/bin/bower update",
"build": "node node_modules/gulp-cli/bin/gulp.js"
}
```
@ -28,20 +44,25 @@ The make command will install or update Composer dependencies if a composer.json
## Publish to App Store
First get an account for the [App Store](http://apps.nextcloud.com/) then run:
First get an account for the [App Store](http://apps.nextcloud.com/)
then run:
make && make appstore
The archive is located in build/artifacts/appstore and can then be uploaded to the App Store.
The archive is located in build/artifacts/appstore and can then be
uploaded to the App Store.
## Running tests
You can use the provided Makefile to run all tests by using:
make test
This will run the PHP unit and integration tests and if a package.json is present in the **js/** folder will execute **npm run test**
This will run the PHP unit and integration tests and if a package.json
is present in the **js/** folder will execute **npm run test**
Of course you can also install [PHPUnit](http://phpunit.de/getting-started.html) and use the configurations directly:
Of course you can also install
[PHPUnit](http://phpunit.de/getting-started.html) and use the
configurations directly:
phpunit -c phpunit.xml

View File

@ -3,16 +3,19 @@
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>occweb</id>
<name>OCC Web</name>
<summary>OCC Command in a web terminal</summary>
<summary>OCC Commands in a web terminal</summary>
<description><![CDATA[Run OCC Commands in a web terminal]]></description>
<version>0.0.1</version>
<version>0.0.6</version>
<licence>agpl</licence>
<author mail="adphi.apps@gmail.com" >Adphi</author>
<namespace>OCCWeb</namespace>
<category>security</category>
<bugs>https://git.adphi.net/Adphi/TestNextcloudApp</bugs>
<category>tools</category>
<website>https://git.adphi.net/Adphi/OCCWeb</website>
<bugs>https://git.adphi.net/Adphi/OCCWeb/issues</bugs>
<repository>https://git.adphi.net/Adphi/OCCWeb</repository>
<screenshot>https://git.adphi.net/Adphi/OCCWeb/raw/master/appinfo/screenshot.png</screenshot>
<dependencies>
<nextcloud min-version="13" max-version="15"/>
<nextcloud min-version="13" max-version="18"/>
</dependencies>
<navigations>
<navigation role="admin">

BIN
appinfo/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

Binary file not shown.

View File

@ -55,10 +55,10 @@
position: absolute;
left: -16px;
top: 0;
width: 16px;
height: 16px;
/*width: 16px;*/
/*height: 16px;*/
/* this seems to work after all on Android */
/*left: -99999px;
left: -99999px;
clip: rect(1px,1px,1px,1px);
/* on desktop textarea appear when paste */
/* opacity is needed for Edge and IE
@ -76,7 +76,7 @@
white-space: pre;
text-indent: -9999em; /* better cursor hiding for Safari and IE */
top: calc(var(--cursor-line, 0) * 1em);
visibility: hidden;
/*visibility: hidden;*/
}
.cmd .noselect, .cmd [role="presentation"]:not(.cursor-line) > span:last-child,
.cmd .cursor-line > span:last-child > span:last-child {

View File

@ -32,7 +32,9 @@
});
}
}, {
greetings: '[[;green;]' + new Date().toString().slice(0, 24) + "]\n\nPress [[;#ff5e99;]Enter] for more information on [[;#009ae3;]occ] commands.\n",
greetings: function (callback) {
callback('[[;green;]' + new Date().toString().slice(0, 24) + "]\n\nPress [[;#ff5e99;]Enter] for more information on [[;#009ae3;]occ] commands.\n")
},
name: 'occ',
prompt: 'occ $ ',
completion: response,
@ -41,5 +43,8 @@
}
});
});
$('html').keypress(function(){
scrollToBottom()
})
});
})(OC, window, jQuery);

Binary file not shown.

View File

@ -1,125 +0,0 @@
<?php
/**
* Created by IntelliJ IDEA.
* User: philippe-adrien
* Date: 2019-01-19
* Time: 12:27
*/
namespace OCA\TestNextcloudApp\Controller;
/**
* Converts an ANSI text to HTML5.
*/
class AnsiToHtmlConverter
{
protected $charset;
protected $inlineStyles;
protected $inlineColors;
protected $colorNames;
public function __construct($inlineStyles = true, $charset = 'UTF-8')
{
$this->inlineStyles = $inlineStyles;
$this->charset = $charset;
$this->colorNames = array(
'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
'', '',
'brblack', 'brred', 'brgreen', 'bryellow', 'brblue', 'brmagenta', 'brcyan', 'brwhite',
);
}
public function convert($text)
{
// remove cursor movement sequences
$text = preg_replace('#\e\[(K|s|u|2J|2K|\d+(A|B|C|D|E|F|G|J|K|S|T)|\d+;\d+(H|f))#', '', $text);
// remove character set sequences
$text = preg_replace('#\e(\(|\))(A|B|[0-2])#', '', $text);
$text = htmlspecialchars($text, PHP_VERSION_ID >= 50400 ? ENT_QUOTES | ENT_SUBSTITUTE : ENT_QUOTES, $this->charset);
// carriage return
$text = preg_replace('#^.*\r(?!\n)#m', '', $text);
$tokens = $this->tokenize($text);
// a backspace remove the previous character but only from a text token
foreach ($tokens as $i => $token) {
if ('backspace' == $token[0]) {
$j = $i;
while (--$j >= 0) {
if ('text' == $tokens[$j][0] && strlen($tokens[$j][1]) > 0) {
$tokens[$j][1] = substr($tokens[$j][1], 0, -1);
break;
}
}
}
}
$html = '';
foreach ($tokens as $token) {
if ('text' == $token[0]) {
$html .= $token[1];
} elseif ('color' == $token[0]) {
$html .= $this->convertAnsiToColor($token[1]);
}
}
if ($this->inlineStyles) {
$html = sprintf('<span style="background-color: %s; color: %s">%s</span>', $this->inlineColors['black'], $this->inlineColors['white'], $html);
} else {
$html = sprintf('<span class="ansi_color_bg_black ansi_color_fg_white">%s</span>', $html);
}
// remove empty span
$html = preg_replace('#<span[^>]*></span>#', '', $html);
return $html;
}
protected function convertAnsiToColor($ansi)
{
$bg = 0;
$fg = 7;
$as = '';
if ('0' != $ansi && '' != $ansi) {
$options = explode(';', $ansi);
foreach ($options as $option) {
if ($option >= 30 && $option < 38) {
$fg = $option - 30;
} elseif ($option >= 40 && $option < 48) {
$bg = $option - 40;
} elseif (39 == $option) {
$fg = 7;
} elseif (49 == $option) {
$bg = 0;
}
}
// options: bold => 1, underscore => 4, blink => 5, reverse => 7, conceal => 8
if (in_array(1, $options)) {
$fg += 10;
$bg += 10;
}
if (in_array(4, $options)) {
$as = '; text-decoration: underline';
}
if (in_array(7, $options)) {
$tmp = $fg;
$fg = $bg;
$bg = $tmp;
}
}
if ($this->inlineStyles) {
return sprintf('</span><span style="background-color: %s; color: %s%s">', $this->inlineColors[$this->colorNames[$bg]], $this->inlineColors[$this->colorNames[$fg]], $as);
} else {
return sprintf('</span><span class="ansi_color_bg_%s ansi_color_fg_%s">', $this->colorNames[$bg], $this->colorNames[$fg]);
}
}
protected function tokenize($text)
{
$tokens = array();
preg_match_all("/(?:\e\[(.*?)m|(\x08))/", $text, $matches, PREG_OFFSET_CAPTURE);
$offset = 0;
foreach ($matches[0] as $i => $match) {
if ($match[1] - $offset > 0) {
$tokens[] = array('text', substr($text, $offset, $match[1] - $offset));
}
$tokens[] = array("\x08" == $match[0] ? 'backspace' : 'color', $matches[1][$i][0]);
$offset = $match[1] + strlen($match[0]);
}
if ($offset < strlen($text)) {
$tokens[] = array('text', substr($text, $offset));
}
return $tokens;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace OCA\OCCWeb\Controller;
use OC\AppFramework\Http\Request;
class FakeRequest extends Request {
public $server = array(
'argv' => ["occ"],
);
/**
* FakeRequest constructor.
*/
public function __construct() {
}
}

View File

@ -2,7 +2,10 @@
namespace OCA\OCCWeb\Controller;
use Exception;
use OC;
use OC\Console\Application;
use OC\MemoryInfo;
use OCP\IRequest;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Http\DataResponse;
@ -11,7 +14,7 @@ use OCP\ILogger;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;
class OCCController extends Controller implements IRequest
class OccController extends Controller
{
private $logger;
private $userId;
@ -19,50 +22,42 @@ class OCCController extends Controller implements IRequest
private $application;
private $output;
public $server;
public function __construct(ILogger $logger, $AppName, IRequest $request, $UserId)
public function __construct(ILogger $logger, $AppName, IRequest $request, $userId)
{
parent::__construct($AppName, $request);
$this->logger = $logger;
$this->userId = $UserId;
$this->userId = $userId;
$this->server = array(
'argv' => ["occ"],
);
$this->application = new Application(
\OC::$server->getConfig(),
\OC::$server->getEventDispatcher(),
$this,
\OC::$server->getLogger(),
\OC::$server->query(\OC\MemoryInfo::class)
OC::$server->getConfig(),
OC::$server->getEventDispatcher(),
new FakeRequest(),
OC::$server->getLogger(),
OC::$server->query(MemoryInfo::class)
);
$this->application->setAutoExit(false);
// $this->output = new OCCOutput();
$this->output = new OCCOutput(OutputInterface::VERBOSITY_NORMAL, true);
$this->output = new OccOutput(OutputInterface::VERBOSITY_NORMAL, true);
$this->application->loadCommands(new StringInput(""), $this->output);
}
/**
* CAUTION: the @Stuff turns off security checks; for this page no admin is
* required and no CSRF check. If you don't know what CSRF is, read
* it up in the docs or you might create a security hole. This is
* basically the only required method to add this exemption, don't
* add it to any other method if you don't exactly know what it does
*
* @NoCSRFRequired
*/
public function index()
{
return new TemplateResponse('occweb', 'index'); // templates/index.php
return new TemplateResponse('occweb', 'index');
}
/**
* @param $input
* @return string
*/
private function run($input)
{
try {
$this->application->run($input, $this->output);
return $this->output->fetch();
} catch (\Exception $ex) {
} catch (Exception $ex) {
exceptionHandler($ex);
} catch (Error $ex) {
exceptionHandler($ex);
@ -70,20 +65,15 @@ class OCCController extends Controller implements IRequest
}
/**
* @NoAdminRequired
* @param string $command
* @return DataResponse
*/
public function cmd($command)
{
$this->logger->info($command);
$this->logger->debug($command);
$input = new StringInput($command);
// TODO : Interactive ?
$response = $this->run($input);
// $this->logger->info($response);
// $converter = new AnsiToHtmlConverter();
// return new DataResponse($converter->convert($response));
$this->logger->debug($response);
return new DataResponse($response);
}
@ -95,264 +85,6 @@ class OCCController extends Controller implements IRequest
}
return new DataResponse($cmds);
}
/**
* Lets you access post and get parameters by the index
* In case of json requests the encoded json body is accessed
*
* @param string $key the key which you want to access in the URL Parameter
* placeholder, $_POST or $_GET array.
* The priority how they're returned is the following:
* 1. URL parameters
* 2. POST parameters
* 3. GET parameters
* @param mixed $default If the key is not found, this value will be returned
* @return mixed the content of the array
* @since 6.0.0
*/
public function getParam(string $key, $default = null)
{
// TODO: Implement getParam() method.
}
/**
* Returns all params that were received, be it from the request
*
* (as GET or POST) or through the URL by the route
*
* @return array the array with all parameters
* @since 6.0.0
*/
public function getParams(): array
{
// TODO: Implement getParams() method.
}
/**
* Returns the method of the request
*
* @return string the method of the request (POST, GET, etc)
* @since 6.0.0
*/
public function getMethod(): string
{
// TODO: Implement getMethod() method.
}
/**
* Shortcut for accessing an uploaded file through the $_FILES array
*
* @param string $key the key that will be taken from the $_FILES array
* @return array the file in the $_FILES element
* @since 6.0.0
*/
public function getUploadedFile(string $key)
{
// TODO: Implement getUploadedFile() method.
}
/**
* Shortcut for getting env variables
*
* @param string $key the key that will be taken from the $_ENV array
* @return array the value in the $_ENV element
* @since 6.0.0
*/
public function getEnv(string $key)
{
// TODO: Implement getEnv() method.
}
/**
* Shortcut for getting cookie variables
*
* @param string $key the key that will be taken from the $_COOKIE array
* @return string|null the value in the $_COOKIE element
* @since 6.0.0
*/
public function getCookie(string $key)
{
// TODO: Implement getCookie() method.
}
/**
* Checks if the CSRF check was correct
*
* @return bool true if CSRF check passed
* @since 6.0.0
*/
public function passesCSRFCheck(): bool
{
// TODO: Implement passesCSRFCheck() method.
}
/**
* Checks if the strict cookie has been sent with the request if the request
* is including any cookies.
*
* @return bool
* @since 9.0.0
*/
public function passesStrictCookieCheck(): bool
{
// TODO: Implement passesStrictCookieCheck() method.
}
/**
* Checks if the lax cookie has been sent with the request if the request
* is including any cookies.
*
* @return bool
* @since 9.0.0
*/
public function passesLaxCookieCheck(): bool
{
// TODO: Implement passesLaxCookieCheck() method.
}
/**
* Returns an ID for the request, value is not guaranteed to be unique and is mostly meant for logging
* If `mod_unique_id` is installed this value will be taken.
*
* @return string
* @since 8.1.0
*/
public function getId(): string
{
// TODO: Implement getId() method.
}
/**
* Returns the remote address, if the connection came from a trusted proxy
* and `forwarded_for_headers` has been configured then the IP address
* specified in this header will be returned instead.
* Do always use this instead of $_SERVER['REMOTE_ADDR']
*
* @return string IP address
* @since 8.1.0
*/
public function getRemoteAddress(): string
{
// TODO: Implement getRemoteAddress() method.
}
/**
* Returns the server protocol. It respects reverse proxy servers and load
* balancers.
*
* @return string Server protocol (http or https)
* @since 8.1.0
*/
public function getServerProtocol(): string
{
// TODO: Implement getServerProtocol() method.
}
/**
* Returns the used HTTP protocol.
*
* @return string HTTP protocol. HTTP/2, HTTP/1.1 or HTTP/1.0.
* @since 8.2.0
*/
public function getHttpProtocol(): string
{
// TODO: Implement getHttpProtocol() method.
}
/**
* Returns the request uri, even if the website uses one or more
* reverse proxies
*
* @return string
* @since 8.1.0
*/
public function getRequestUri(): string
{
// TODO: Implement getRequestUri() method.
}
/**
* Get raw PathInfo from request (not urldecoded)
*
* @throws \Exception
* @return string Path info
* @since 8.1.0
*/
public function getRawPathInfo(): string
{
// TODO: Implement getRawPathInfo() method.
}
/**
* Get PathInfo from request
*
* @throws \Exception
* @return string|false Path info or false when not found
* @since 8.1.0
*/
public function getPathInfo()
{
// TODO: Implement getPathInfo() method.
}
/**
* Returns the script name, even if the website uses one or more
* reverse proxies
*
* @return string the script name
* @since 8.1.0
*/
public function getScriptName(): string
{
// TODO: Implement getScriptName() method.
}
/**
* Checks whether the user agent matches a given regex
*
* @param array $agent array of agent names
* @return bool true if at least one of the given agent matches, false otherwise
* @since 8.1.0
*/
public function isUserAgent(array $agent): bool
{
// TODO: Implement isUserAgent() method.
}
/**
* Returns the unverified server host from the headers without checking
* whether it is a trusted domain
*
* @return string Server host
* @since 8.1.0
*/
public function getInsecureServerHost(): string
{
// TODO: Implement getInsecureServerHost() method.
}
/**
* Returns the server host from the headers, or the first configured
* trusted domain if the host isn't in the trusted list
*
* @return string Server host
* @since 8.1.0
*/
public function getServerHost(): string
{
// TODO: Implement getServerHost() method.
}
/**
* @param string $name
*
* @return string
* @since 6.0.0
*/
public function getHeader(string $name): string
{
// TODO: Implement getHeader() method.
}
}
function exceptionHandler($exception)

View File

@ -1,19 +1,17 @@
<?php
/**
* Created by IntelliJ IDEA.
* User: philippe-adrien
* Date: 2019-01-18
* Time: 19:08
*/
namespace OCA\OCCWeb\Controller;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
class OCCOutput extends BufferedOutput implements ConsoleOutputInterface
/**
* @method ConsoleSectionOutput section()
*/
class OccOutput extends BufferedOutput implements ConsoleOutputInterface
{
/**
@ -31,4 +29,8 @@ class OCCOutput extends BufferedOutput implements ConsoleOutputInterface
{
}
public function __call($name, $arguments) {
// TODO: Implement @method ConsoleSectionOutput section()
}
}