2009年1月22日 星期四

HP Scrawlr 幫你偵測網站安全性

今天朋友傳了一個軟體給我玩, 主要是這隻程式會用盡辦法去入侵你輸入的網站, 也就是SQL Injection, 以香港AAStock來作測試, 發現一堆頁面都可以被攻擊, 也能掃出資料表內容...實在很可怕。

但是我是那種唯恐天下不亂的人, 所以搞不好能拿來幹些見不得人的事 :P

下載HP Scrawlr

2009年1月15日 星期四

去除UTF-8 BOM表頭的InputStreamReader

如果你曾經使用Java遇過讀一些文字檔前面出現??等亂碼的話, 表示該份文件有加入bom(byte order mark), 基本上bom的用途原本是用來讓程式辨別該份文檔的編碼格式, Microsoft在Windows 2000以後的Notepad存UTF-8的檔案會加上 BOM(Byte Order Mark, U+FEFF), 主要是因為UTF-8和ASCII是相容的, 為了避免使用者自己忘記用甚麼存, 造成UTF-8檔案用 ASCII 模式開看到是亂碼, 所以在檔案最前面加上BOM.

看到這裡可能有很多人會開始%#~!微軟...

但是碰到問題總是得解決的, 下面這個改寫InputStreamReader的UnicodeInputStreamReader就能夠讓你免去上述煩惱, 所有功能仍然和InputStreamReader相同, 只是在constructor中加入判斷bom表頭的機制, 使用PushbackInputStream當讀取到相關格式時能夠移動檔案開始位置.

使用方式: BufferedReader in = new BufferedReader(new UnicodeInputStreamReader(new FileInputStream(file), encode));

UnicodeInputStreamReader:

package com.progking.io;

import java.io.IOException;
import java.io.PushbackInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import sun.nio.cs.StreamDecoder;

public class UnicodeInputStreamReader extends InputStreamReader
{
   private static final int BOM_SIZE = 4;
   private final StreamDecoder decoder;
   private PushbackInputStream pushBack;
   private String encode;
   private String defaultEnc;

   public UnicodeInputStreamReader(InputStream input, String defaultEnc) throws UnsupportedEncodingException
   {
       super(input);
       try {
           this.defaultEnc = defaultEnc;
           pushBack = new PushbackInputStream(input, BOM_SIZE);
           init();
       } catch (Exception e) {
           e.printStackTrace();
       }
       decoder = StreamDecoder.forInputStreamReader(pushBack, this, encode);
   }

   private void init() throws IOException
   {
       byte[] bom = new byte[BOM_SIZE];
       int n, unread;

       // 初始讀取一次
       n = pushBack.read(bom, 0, bom.length);

       // 比對表頭
       if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) {
           encode = "UTF-32BE";
           unread = n - 4;
       } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE) && (bom[2] == (byte) 0x00) && (bom[3] == (byte) 0x00)) {
           encode = "UTF-32LE";
           unread = n - 4;
       } else if ((bom[0] == (byte) 0xEF) && (bom[1] == (byte) 0xBB) && (bom[2] == (byte) 0xBF)) {
           encode = "UTF-8";
           unread = n - 3;
       } else if ((bom[0] == (byte) 0xFE) && (bom[1] == (byte) 0xFF)) {
           encode = "UTF-16BE";
           unread = n - 2;
       } else if ((bom[0] == (byte) 0xFF) && (bom[1] == (byte) 0xFE)) {
           encode = "UTF-16LE";
           unread = n - 2;
       } else {
           // 如果沒有找到任何表頭, 則退回長度等於原先總長
           encode = defaultEnc;
           unread = n;
       }
       System.out.println("has BOM=" + ((unread == n) ? false : true) + ", encode=" + encode + ", read=" + n + ", unread=" + unread);
       // 計算應該退回多少byte
       if (unread > 0)
           pushBack.unread(bom, (n - unread), unread);
   }

   public String getEncoding()
   {
       return encode;
   }

   public int read() throws IOException
   {
       return decoder.read();
   }

   public int read(char cbuf[], int offset, int length) throws IOException
   {
       return decoder.read(cbuf, offset, length);
   }

   public boolean ready() throws IOException
   {
       return decoder.ready();
   }

   public void close() throws IOException
   {
       decoder.close();
   }
}

2009年1月12日 星期一

維護的重要性

"對於日前桃園機場境管電腦大當機事件, 最後還是內政部長廖了以親自指揮, 才叫得動這套系統創始的資深工程師章毅昌上陣解決問題, 而面對這套只有「少數人才懂」的電腦系統, 移民署已決定更換。"

不知道大家對這新聞有何想法?

為什麼開放原始碼觀念帶給我們如此大的震撼?
為什麼近年來技術增長的如此快速?
為什麼Google產品能夠如此強大?

因為他們都採取"讓越多人瞭解, 讓越多人能輕易的上手"這種觀念, 唯有如此產品或技術才能以倍數發展或散佈

寫軟體, 不是自己寫了幾百支檔案, 能夠運作正常就洋洋自得。一個公司裡, 最講求的部分絕對包括效率, 當然人員的流動性也必須考慮進去, 當接手的人必須花比原開發時間多一倍甚至兩倍去閱讀程式碼, 進而重構(Refactoring), 途中更必須承擔毀損舊有結構的風險, 這時候你才會把當初懶惰, 不以為意的想法(不寫說明文件, 詳細註解, 物件設計藍圖等...)丟置在一旁, 並深深警惕自己下次不要再犯(雖然再犯率和煙毒犯假釋出獄的機率差不多...很高), 但至少你學到一個經驗, 不過在這當中老闆已經燒掉多少薪水了?一個人一個月加上詢問原開發者半個月...。

台灣早期很多軟體都是這樣的做法, 我把它稱之為偉人模式, 因為總是有個很厲害的工程師獨自(或帶領)寫出一套大型軟體, 當然箇中奧妙也只有他自己知道, 接下來的幾十年, 維護的人來來去去, 一知半解的祈求著不要crash。比較倒楣的就是碰到類似桃園機場的狀況, 客戶再也不使用該軟體。

近年來架構設計的觀念從國外引入, 慢慢的大家也開始著重這一塊, 為了寫出易擴展好維護的軟體, 會把類別說明當作教科書來寫, 當然由於文件齊全, 接手的人又可以使用擴展手法不更動到既有程式, 不但時程縮短許多, 也能做到版本控管。

並不是指早期的軟體很糟糕, 那是因為環境使然, 而是說軟體始終是一個跟著時間一同前進的東西, 要做到長久當初設計時就應該把眼光放遠, 尤其是現在進步速度如此之快, 擴展已經是見怪不怪的事情, 不, 應該是必備!

做為一個老闆, 你覺得留住一個人十年簡單還是做出一套軟體能夠方便擴充功能, 架構良好讓來來去去員工維護簡單?

2009年1月6日 星期二

在ActionScript based應用程式中管理記憶體

Sam目前的對於Flash應用程式的作法都是"骨肉分離", 也就是畫面與程式徹底分開, 全部的動作均是由程式控制。 當然這麼作必須搭配各種事件的處理, 所以當使用者反覆開啟彈出視窗時, 偵聽事件的新增與移除就變得非常重要(因為關係到記憶體的消耗)。

Essential Actionscript 3.0作者Colin Moock說:

當一個Sprite物件被 removeChild掉了,參照也移除了,裡頭的Listener並不會立刻停止監聽/執行,除非它被GC收走(並無法預期GC何時會來),如果這個Sprite一直都有其他物件或變數參照到,那該Listener永遠不會消失,裡頭的程式也會不斷地執行!! 這是很糟糕的問題,所以他建議對於每個類別,都要記得規劃類似: destroy(); kill(); dispose(); deactivate();等函式:清除物件裡頭的物件資料,以便幫助GC來收取記憶體。

另外如果你想用System.gc();的話, 它是For Flash Player debugger version only. 對於一般的FlashPlayer無效

所以記得每次事件處理程式加上weak reference: addEventListener(eventType, listener, false, 0, true); 也記得要常常對不必要的listener寫: removeEventListener(type, listener);

weak reference Listener Example:

someObj.addEventListener("eventName", myFunct, false, 0, true);
addEventListener("eventName", function(evt) { ...code... }, false, 0, true);

Ticore大哥的blog有介紹錯誤使用weak reference的例子: http://ticore.blogspot.com/2006/12/as3-weakly-referenced-listener.html

基本上如果將weak reference listener的function獨立出來管理的話則在移除該listener時也要一併處理function所在的物件, 設置為null或remove等...。

另外Grant Skinner在他的blog中提到一種強制執行GC的方式:

try {
  new LocalConnection().connect('foo');
  new LocalConnection().connect('foo');
} catch (e:*) {}

這個方式為使用LocalConnection重複連結兩次則絕對會throw Error出來, 而Grabage Collection則會在出錯時強制執行一次, 以清除記憶體中無效的變數與物件來減少資源耗用

不過他也有說明:

“Again, this should only be used as a development aid. It should never be used in production code!”
意思是這種投機的方法只能用在develope階段, 絕對不要用在產品上。因為try catch耗用的效能實在很可怕

2009年1月5日 星期一

php與MySQL連接類別

日前整理的一個類別, 特殊的地方在於使用了Array儲存查詢結果, 所以能夠依照查詢時給予的ID重新調用結果。
此外設定檔存放在db_mysql.inc中, 如果臨時需要調換資料庫則在建構子中填入即可。

db_mysql.inc :

<?php

// or set date.timezone = Asia/Taipei in php.ini
if(function_exists("date_default_timezone_set")) date_default_timezone_set('Asia/Taipei');

// database configure.
define(DB_HOST, 'localhost');
define(DB_USER, 'root');
define(DB_PASSWD, 'root1234');
define(DB_DATABASE, 'PHP_MODULE_TEST');
define(DB_PERSISTENT, false);
define(DB_UTF, true);

?>

database_mysql.php :


<?php

require('db_mysql.inc');

class DataBase 
{
    // Connection parameters
    var $host = '';
    var $user = '';
    var $password = '';
    var $database = '';
    var $persistent = false;
    var $utf = true;

    // Database connection handle
    var $conn = null;
    var $connected = false;
 
    // Query result
    var $result = array();
    var $insert_id = array();
    
    // constructor.
    function DataBase($otherDatabase = null)
    {
        $this->host = DB_HOST;
        $this->user = DB_USER;
        $this->password = DB_PASSWD;
        if($otherDatabase == null)
            $this->database = DB_DATABASE;
        else
            $this->database = $otherDatabase;
        $this->persistent = DB_PERSISTENT;
        $this->utf = DB_UTF;
    }

    // open database connection.
    function connect() 
    {
        // Choose the appropriate connect function
        if ($this->persistent){
            $func = 'mysql_pconnect';
        }else{
            $func = 'mysql_connect';
        }
     
        // Connect to the MySQL server
        $this->conn = $func($this->host, $this->user, $this->password) or die($this->error());
        if($this->utf == true){
            mysql_query("SET NAMES 'utf8'");
        }

        // Select the requested database
        mysql_select_db($this->database, $this->conn) or die($this->error());

        $this->connected = true;
    }

    // database query.
    function query($rs_id='RESULT_ID', $sql='', $debug=0)
    {
        if($this->connected == false) $this->connect();
        if($debug!=0) $this->debug($sql, $debug);
        $this->result[$rs_id] = mysql_query($sql, $this->conn) or die($this->error());
        $this->insert_id[$rs_id] = mysql_insert_id();
        return ($this->result[$rs_id] != false);
    }
    
    // 取得先前操作MySQL所受到影響的列的數目
    function affectedRows()
    {
        return (mysql_affected_rows($this->conn));
    }

    // 取得結果中列的數目
    function numRows($rs_id)
    {
        return (mysql_num_rows($this->result[$rs_id]));
    }
    
    // 取得查詢後的物件結果(使用$row->user_id查詢)
    function fetchObject($rs_id)
    {
        return (mysql_fetch_object($this->result[$rs_id]));
    }
    
    // 取得查詢後的陣列結果(使用$row['user_id']查詢)
    function fetchArray($rs_id)
    {
        return (mysql_fetch_array($this->result[$rs_id]));
    }
    
    // 回傳一個欄位的值(ex: select name from school where id=1, 傳回$rs['name'])
    function fetchAssoc($rs_id)
    {
        return (mysql_fetch_assoc($this->result[$rs_id]));
    }

    // 釋放�憶體
    function freeResult($rs_id)
    {
        return (mysql_free_result($this->result[$rs_id]));
    }

    // 移動內部指標
    function resetResult($rs_id)
    {
        return (mysql_data_seek($this->result[$rs_id], 0));
    }
    
    // 取得先前insert操作的id主鍵(AUTO_INCREMENTED)
    function getInsertID($rs_id)
    {
        return $this->insert_id[$rs_id];
    }

    // show database configure.
    function showDetail()
    {
        $str = '';
        $str .= 'DB_HOST: '.DB_HOST.'<br>';
        $str .= 'DB_USER: '.DB_USER.'<br>';
        $str .= 'DB_PASSWD: '.DB_PASSWD.'<br>';
        $str .= 'DB_DATABASE: '.DB_DATABASE.'<br>';
        if(DB_PERSISTENT == true)
            $str .= 'DB_PERSISTENT: true<br>';
        else
            $str .= 'DB_PERSISTENT: false<br>';
        if(DB_UTF == true)
            $str .= 'DB_UTF: true<p>';
        else
            $str .= 'DB_UTF: false<p>';

        return $str;
    }

    // debug sql condition.
    function debug($sql,$debug)
    {
        if($debug==1)echo "<script>alert(\"".$sql."\");</script>";
        else if($debug==2)echo '<p><b>'.$sql.'</b></p>';
        else if($debug==3)exit;
    }                                 

    // close connection. (無法關閉pconnect所開啟的連線)
    function close()
    {
        $this->connected = false;
        return mysql_close($this->conn);
    }

    function error()
    {
        return mysql_error();
    }
}
?>

集中注意力

最近發現寫專案的時候越來越容易分心, 一方面是這個案子沒有太多相關文件卻突然空降到我身上要作大幅度的修改, 自然就興趣缺缺..., 以致於只要周遭有一點點噪音或突發事件心思就馬上被吸了過去, 自我檢討了一番發現這是沒有目標(目標過於不明確)所致, 以打靶來說, 在一片遼闊的草原上放置靶紙, 你會一直開槍直到打中靶, 但是沒有靶紙的話, 東邊飛過來一隻鳥你會打鳥, 草地上跑出一隻兔子則會去打兔子...。

所以為什麼要事先規劃 -> 定義焦點快速功能性的執行, 防止浪費時間。 但自我鍛鍊也是很重要, 不可能每次都有人幫你把事情整理好放在你跟前(又不是大老闆), 所以下列方式供大家參考一下:

一. 設立期限

一般人做事時,以剛開始與快結束時效率最高, 在兩者之間,將會出現效率低落、缺乏注意力的現象,心理學上稱之為“中間鬆懈”,了維持注意力,可以象馬拉松選手一樣,設立中間站,更能發揮全力。中途站的時間不能太長也不能太短,否則會帶來反效果。

二. 設定競爭對象

假設有人在和你搶這份工作, 你的表現必須超越他, 可以把同事或上司設成假想敵(但千萬不要在日常生活中也變成敵對狀態), 為了打敗這個對手就需要不斷鼓勵自己。

三. 積極的自我暗示

就是類似催眠的方式, 每天到公司的時候先花個5分鐘想像自己是公司最重要的人, 這個案子到了你手上一定會成功之類。

四. 循序漸進式

精神不集中的最大原因之一,就是缺乏欲望。不過一次接觸一些些, 慢慢熟悉了之後就能得心應手, 心防自然而然打開。

下次你也無法對某些事情專心時, 可以試試看使用以上的方法。

2009年1月2日 星期五

部落格手動張貼程式碼

偶然間發現一個網站Online syntax highlighting, 之前我一直都是使用SyntaxHighlighter, 但是載入速度實在太慢, 換行等又有一堆問題, 還不如用貼HTML碼的方式.

這個網站有支援幾乎所有的code種類, 算是相當齊全, 也有樣式可以選擇. 更改背景色或是自動換行只要在開頭pre後面style中加上如同下列HTML就可以了.

<pre style='color:#000000;background:#f0f0f0;overflow:auto;border: 1px solid #ccc;'>

貼個Java範例:

public class SyntaxTester
{
    public static void main(String[] args)
    {
        // test for code attach.
        System.out.println("I use syntax highlighting for Java in my blogger");
    }
}

不用重新計算效能真是好^^