2009年2月26日 星期四

Flash的鳥問題

這幾天用Flash做事老是碰到一堆鳥問題, 首先是前一版本編輯的class拿來cs4使用會有“The name of this class conflicts with the name of another class that was loaded”, 表示類別名稱有衝突, google一下之後, 把系統時間用NTPClock校正再重新除錯就沒問題, 這部份還好...。

昨天重灌了cs3, 為什麼呢?因為cs4 bug實在太多了, 例如像是MovieClip裡有很多文字欄位, 整個編輯的速度就會慢到不像話, 但換回去之後麻煩才大, 首先把原本的fla存成cs3格式, 打開cs3後相當順暢, Ctrl + Enter compile過程也很順利, 放到網站上, 咦...怎麼某一部份的元件就是無法addChild上去?原本還以為程式寫錯, 但是本機執行一切正常, 網路上就會有問題, 找到今天才想到會不會是版本, 開啟cs4重新編譯放上去後...正常= =, 差點跌到在座位前。

然後之前flp的檔案cs4也沒有了, 硬是要用AIR作一個檔案管理的組件放在cs4中, 又不好操作。所以最後還是換回cs3比較保險。

感覺Adobe也開始和微軟一樣想靠軟體亂改版賺錢...大公司的詬病!總是想把使用者榨乾

2009年2月18日 星期三

程式語言版本的相容性

雖然Sam不會因為程式語言版本相容問題而去學習一種語言, 但最近的情況的確困擾了我。

首先是前年ActionScript2.0晉升3.0, 完全變成一種類Java語言, 當然2.0之前的概念已不復見, 即使這樣但看見優化多倍的效能時還是得盡力去學習, 目前也在專案當中使用, 事實上對於像Sam這種物件導向起家的工程師來說如此的改變再好不過, 因為有Java的底子所以學起來事半功倍, 但對於那些程序導向的工程師就...

然後PHP6的改變, 最恐怖的就是資料庫這段上面統一了(使用PDO), 不再像以前一樣每種資料庫要調用不同的module, 基於oo的立場是很好的改變但對大多數現在線上仍然使用php4, 5的朋友就是惡夢了, 表示你們要砍掉重練!

其他的變革像是:

1.支援Unicode

2.Register Globals將被移除

3.Magic Quotes 將消失

4.Safe Mode Out

5.類別內的Var宣告變成public

6.Return by Reference will error

7.Freetype1 and GD1 support 將不見

8.dl() 加載extension Libs現在被移到 SAPI

9.一些Extension的變更

10.告別ASP風格的啟始標籤

而重點是php6效能不一定比php5好, 所以國內大概也都不會換成php6, 下場應該和Windows Vista一樣。

再來, 我最近很努力的想學python(因為這個語言很簡單上手, 直譯式語言, 可以作到幾乎任何事情, 跨平台, 有規則的約束撰寫縮排等...), Google和Youtube都把python當成開發語言之一(NASA也是), 可以用document執行, 也能用script在終端運作, 可以套用不同的library寫出視窗軟體, 也能寫網頁, 以上已經表示它很強了, 另外還有加速器。

記得去年國外某知名linux討論區評選python榮登年度最佳語言, 國內市場上應用的卻很少, 最近幾年有在某些大專院校研究所聽到在使用而已。Anyway, 我想要得就是它的簡單中帶著強大, 有時候我只是單純的想寫出某些功能卻還是要用Eclipse在那裡搞來搞去實在很煩, 所以就想說不如來學一下python, 沒想到它也改版為3.01, 而且官網上說

Python 3.0 (a.k.a. "Python 3000" or "Py3k") is a new version of the language that is incompatible with the 2.x line of releases.

講到這裡我不知道要該哭還是該笑, 學最新版本的感覺有很多套件來不及搭配, 但是還好他的教學頁面裡有蠻多補充http://docs.python.org/3.0/, 還是來花時間研究一下好了。

總之最近都東一個西一個語言在大幅度的更新, 真是無言~

2009年2月7日 星期六

一些php網頁驗證小技巧

一般而言坊間教學書上對於登入驗證這塊都沒有說明的很詳細, 但其實還有一些技巧需注意, 雖然這些技巧並不能讓你網站100%安全, 但是至少能夠防範"輕易"的被誤用或連結.

例如說處理表單的php來源照理來說應該要是表單的html等, 如果不判斷來源, 就能夠使用一些機器人隨意新增或修改資料, 下段程式判斷時否有來源網頁, 有的話是否是你設定的網頁.

<?php
$referrer = $_SERVER['HTTP_REFERER'];
if($referrer == '' || strpos($referrer, 'your_php.php') == false){        // 判斷來源網頁
    die('來源網頁錯誤');
}
?>

至於登入很多人可能會忘記要驗證當初登入的瀏覽器與IP位址, 你可以在登入時註冊一個session為如下:

<?php
$_SESSION['userAgent'] = $_SERVER['REMOTE_ADDR'].$_SERVER['HTTP_USER_AGENT'];
?>

如果要使用cookie記憶長時效, 為了不讓cookie被破解我們會做一些混淆動作, 但要還原卻又很困難, 所以我們把混淆後的cookie記錄在資料庫中, 並設定時效, 每次瀏覽需要驗證的頁面時都去比對, 直到cookie過期失效.

<?php
$cookie = md5($date.$mail.$identify.rand(0, 1000000));
$cookieexpiry = (time() + 21600);
setcookie('memberVerify', $cookie, $cookieexpiry); //時效驗證
?>

如果有看過我之前破解一些網站的文章就知道上述方法還是能夠被手動取代, 但至少不會輕易讓門外漢利用!

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();
    }
}
?>