2010年5月31日 星期一

PHP實作安全機制(3)

php.ini裡儲存了關於php設定的資訊
有些設定可以考慮做些改變
另外這篇文章也會提到避免網站受到script攻擊

用文字編輯器打開php.ini

safe_mode  
假如設定成on,會使得php網頁不能夠被相同伺服器上其他使用者的php網頁呼叫

open_basedir = directory[:...]
限制了PHP對目錄的存取能力,也影響了他能夠執行的檔案

display_errors
假如網站錯誤而不想讓網站參觀者知道,把這選項設定成off

log_errors
將錯誤送到錯誤記錄,是個檢查錯誤的好地方
如果display_errors設定成off,這個選項設定成on
可以讓自己知道問題原因而不會洩露給網站參觀者

error_log
這裡會儲存錯誤記錄的存放位置, log_errors設定成on時可以關注這個選項


script攻擊是應用injection attack
將script碼輸入到沒有防範的填寫格中
可以做到重新導引瀏覽器到別的網站,例如類似的假網站來盜取資料
又或者是盜取cookie等手法

防範的手法包括盡量做驗證或者是使用PHP函式
PHP函式中的strip_tags()可以幫助去除script攻擊
另外,以正規表示法防範不該出現的字元或符號也是一種方法

PHP筆記(4)

這篇會提到
1.xml相關處理
2.錯誤與例外處理
3.善用print_r()函式 

1.xml相關處理
範例:
$xml = simplexml_load_file($URL);
$entry = $xml->entry;
$attrs = $xml->entry->video->duration->attributes();
$media = $entry->children($URL);


使用simplexml_load_file()可以簡單取得XML的資訊
使用時加入XML提供方的link當參數即可
存取XML資料時會以階層性質來使用,指定標籤名稱可存取階層資訊
->符號以物件形式來看,會是從xml物件裡面取得子元素
然後利用 attributes()取得資訊

第三個程式碼擷取duration標籤,並用attributes()以陣列形式取得值
children這個方法可以回傳一個內含特定名稱空間內所有子元素的陣列
也可以跟 attributes()做很好的搭配

2.錯誤與例外處理
分別從MySQL與PHP來看錯誤處理

通常SQL碼的處理方式是
mysqli_connect(localhost, $account, $pw) or die('Couldn't connect !');
die()這個函式會在錯誤發生時被呼叫
但假如想知道詳細發生的訊息可用 or die(mysqli_error($db_link)) 來得知

PHP部分的例外處理則類似Java的例外處理,如下範例

function checkBalance($balance){
  if($balance < 1000) {
     throw new Exception("Balance is less than 1000");
   }
   return true;
}

try{
checkBalance(1);
echo 'Balance is above 1000';
}catch(Exception $e){
  echo 'Error:'.$e->getMessage();
}

執行結果是Balance is less than 1000

3.善用print_r()函式 

print_r()函式可以用來輸出變數的值或是字串
debug時最需要的就是確認變數的值有沒有如想像中的傳入或改變
善用這點就可以確認變數的狀態

2010年5月30日 星期日

PHP繪圖處理

這篇會寫到PHP實作圖像的部分
會寫一個 CAPTCHA的範例
提到的函式還有轉換數字到ASCII字元的chr()函式、rand()函式等

CAPTCHA是指確認使用者端是否為人類使用,而非機器人程式的測試
一般常見的圖像認證碼即是一例

CAPTCHA範例:
 首先實作一個隨機產生文字字串的程式

define('CAPTCHA_NUMBER', 5);
//generate the random pass-phrase
$pass_phrase = "";
for($i = 0; $i < CAPTCHA_NUMBER; i++){
  $pass_phrase .= chr(rand(97, 122));
}

chr()函式將數字轉換成相對應的ASCII字元,數字97會被轉換成'a'
rand()函式當然就是回傳隨機整數
回傳範圍可以設定在指定範圍,或者是o與內建常數RAND_MAX(server設定)內

接著就是要產生圖像

define('CAPTCHA_WIDTH', 100);
define('CAPTCHA_HEIGHT', 25);

//create the image, make black background
$img = imagecreatetruecolor(CAPTCHA_WIDTH, CAPTCHA_HEIGHT);

//set a white background with black text and gray graphics
$bg_color = imagecolorallocate($img, 255, 255, 255);  //white
$text_color = imagecolorallocate($img, 0, 0, 0);  //black
$graphic_color = imagecolorallocate($img, 64, 64, 64); //dark gray

//fill the background
imagefilledrectangle($img, 0, 0, CAPTCHA_WIDTH, CAPTCHA_HEIGHT, $bg_color);

//draw some random lines
for($i = 0, $i < 5, $i++){
  imageline($img, 0, rand() % CAPTCHA_HEIGHT, CAPTCHA_WIDTH,
            rand() % CAPTCHA_HEIGHT, $graphic_color);
}

//sprinkle in some random dots
for($i = 0, $i < 50, $i++){
  imagesetpixel($img, rand() % CAPTCHA_WIDTH, rand() % CAPTCHA_HEIGHT, $graphic_color);
}

//draw the pass-phrase string
imagettftext($img, 18, 0, 5,CAPTCHA_HEIGHT - 5, $text_color,
             'Courier New Bold.ttf', $pass_phrase);

//output the image as a PNG using a header
header("Content-type: image/phg");
imagepng($img);

//release the image  from memory
imagedestroy($img);

imagecreatetruecolor()函式在記憶體裡準備繪製用的空白圖像
imagefilledrectangle()可用來填滿它的顏色
回傳值是一個image identifier,通常是大多數GD繪圖函式需要的第一個參數

imagecolorallocate()是用來配色用的
第一個參數是image identifier,後面三個參數是RGB數值
imageline()函式的第2、3參數是線段起點
imagerectangle()會填滿顏色在參數裡所涵蓋的範圍

imagegettftext()可以用特定字體來繪製文字
使用時必須先將字體放置於伺服器上
字體檔通常具有.ttf副檔名
函式的座標位置是第一個字元的左下角
第2個參數是大小,第3個參數是字體角度,以逆時針方向計算
第4、5參數則是作標位置

imagepng()函式會將圖像輸出到client端的browser上
假如選擇直接創造圖象到記憶體中,而不是採取賦予檔名的方式
要呼叫header()函式幫助傳送到瀏覽器
如果要儲存圖像則在imagepng()函式裡面多加入參數
imagepng($img, $filename, 5);
$filename要輸入完整的路徑名稱,參數5代表壓縮程度中等
這樣就能夠寫入檔案到png檔
imagedestroy() 函式可以釋放記憶體空間
使用完畢之後就用imagedestroy($img);
成功清除會回傳true

沒提到的函式還有

imageellipse()是在這例子裡面沒有的畫圓函式
他的參數是中心點位置和寬度、高度,顏色用最後一個參數填入
imagefilledellipse()可以用來填滿它,接受的參數和圓函式相同

imagestring()函式則是用PHP的內建字體來繪製字串圖像
使用範例為 imagestring($img, 3, 75, 75, 'Sample', $color);
第2個參數是字體大小,範圍是1~5
第3、4函數是座標
imagestringup()類似imagestring(),參數也一樣
但顯示效果則是逆時針旋轉90度的直立輸出

接著寫一段html碼
<label for="verify">Verification:</label>
<input type="text" id="verify" name="verify" value="Enter the phrase" />
<img src="source.php" alt="Verification pass-phrase" />

圖片來源就修改下本篇的程式碼來接上即可

PHP與正規表示法

正規表示法是用來比對使用者所輸入的字串規格
例如今天需要使用者輸入一筆電話號碼
要求的格式假如是 000 - 11111111
使用者假如輸入00 - 11111111,就會被事先設定的正規表示法排除
本篇介紹PHP使用正規表示法的方式
 ps.提醒:正規表示法在不同語言裡有些微不同

正規表示法範例:
/^\(?[2-9]\d{2}\)?[-\s]\d{3}-\d{4}$/

每一段正規表示法都必需要包含在 / / 裡面
若要比對確實的數字如1234,那就用/1234/
^ 代表以所要的字串規則開頭,ex. ^abc =  比對abc開頭的字串
$則代表以所要的字串規則結束
如果不加$的話,^abc 的比對也會判定abcd是對的
所以如果要做嚴格的判斷一定要加上頭尾的符號
\d 比對 0 - 9的數字的一個字元
\w 比對 a - z & A -Z & 0 - 9的一個字元
\s可以比對一個空白字元,包含Tab的跳格字元或是換行字元等
. 比對出換行字元以外的任何字元

{ }裡面可以放進最多兩個參數,作用是設定前面字元或中介符號應有的數目
例如\d{2, 4} ,表示可比對出連續2到4個數字的資料,00、111、1234都對
[]可對比出設定範圍的字元,[0-2]對比出0、1、2,[A-C]對比出A、B、C
在[]中使用^的意思表示不要符合,[^3-9]可比對出0、1、2
+表示前面的字元或中介符號應該出現一次或多次
*表示前面的字元或中介符號應該出現一次或多次或是都不要出現
?表示前面的字元或中介符號應該出現一次或是都不要出現
注意若只是想使用這類符號就必須脫逸,例如\?就可比對出?

至於PHP裡面用來做比對的函式有preg_match()
它的使用方式是preg_match($regex, $my_string)
第一個參數是正規表示法,第二個參數是要比對的目標字串
因為傳進來的類別都必須是字串,正規表示法要記得加上引號
成功就回傳true

另外一個跟正規表示法有關的函式是preg_replace()
它的使用方法是preg_replace($pattern, $replacement, $my_string)
他的功用是找出不想要的字元並將他替換成我們需要的字元
例如:
$test = preg_replace('/100[0-9]/', '100', 'I have 1000 dollars');
修正後的結果會存到$test裡,成為I have 100 dollars

PHP裡提供一個checkdnsrr()函式
他可以檢查網域名稱的正確性
在裡面以字串的方式輸入@以後的網址,例如'yahoo.com.tw'
假使網域存在時會回傳1,否則就回傳0
注意一點是這個函式在Linux上沒有問題
在windows若發生問題的話則使用以下程式碼

function win_checkdnsrr($domain, $recType = ''){
  if(!empty($domain)){
    if($recType == '')  $recType = "MX";
     exec(nslookup -type=$recType $domain", $output);
     //exe呼叫執行在伺服器上的外部程式
     foreach($output as $line){
     if(preg_match("/^$domain/", $line)){
     return true;
     }
     }
    return false;
    }
  return false;
}

結合起來的驗證用戶e-mail網址的程式如下
    if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9\._\-&!?=#]*@/', $email)) {
      // $email is invalid because LocalName is bad
      echo '<p class="error">Your email address is invalid.</p>';
      $output_form = 'yes';
    }
    else {
      // Strip out everything but the domain from the email
      $domain = preg_replace('/^[a-zA-Z0-9][a-zA-Z0-9\._\-&!?=#]*@/', '', $email);
      // Now check if $domain is registered
      if (!checkdnsrr($domain)) {
         echo '<p class="error">Your email address is invalid.</p>';
         $output_form = 'yes';
      }
     }

2010年5月29日 星期六

PHP筆記(3)

這篇提到
1.SQL碼整理
2.拆解字串函式 explode() 與結合字串函式 implode()
3.字元替換函str_replace()
4.擷取字元函式 substr() 
5.自訂函式寫法 

1.SQL碼整理
    $query = "SELECT mr.response_id, mr.topic_id, mr.response, mt.name AS topic_name " .
      "FROM mismatch_response AS mr " .
      "INNER JOIN mismatch_topic AS mt USING (topic_id) " .
      "WHERE mr.user_id = '" . $_SESSION['user_id'] . "'";

SQL的AS關鍵字建立別名,如例子中的mismatch_topic被命名為mt
INNER JOIN是內聯結的陳述,會從FROM與JOIN提到的表作查詢
只有符合條件的查詢才回被回傳
USING是簡化SQL碼的陳述
例子中可改成ON( mismatch_topic.topic_id = mismatch_response.topic_id)
兩張表裡都有相同的欄位名稱才可以這樣使用

  SELECT xx FROM table WHERE xx LIKE '%world%'

Like的關鍵字可以尋找跟引號裡的字眼不完全相同的模糊比對
萬用字元%可以替代單字前後的任何字元
MySQL預設的搜尋是不分大小
所以可以找到 HelloWorld、 hello world 、wOrld這些資料

SELECT xx FROM table WHERE xx LIKE '____world%'
搜尋到的資料就必須是任意四字元後接world的資料,例如aaaaworldd
前面的字元數端視 _ 符號的數量

SELECT * FROM table ORDER BY title  LIMIT 5
ORDER BY這個關鍵詞用在句尾,可以針對查詢做排列
針對title作降冪排列
當然排列的依據是數值或字元的大小順位
LIMIT 5 讓不管查詢到多少資料都只回傳前五筆
如果是 LIMIT 10, 5
第一個引數是要跳過多少筆資料,第二個引數是要擷取多少資料
善用的話可以做出將查詢結果分頁的效果

搭配PHP函式使用時有一些地方要注意
使用mysqli_query()時,若查詢的變數型別並不符合該欄位的型別
會回傳一個false值
而若查詢到的是空值(null)
則必須要進一步使用mysqli_fetch_array()函式
這函式會在查詢到空值時回傳false

2.拆解字串函式 explode() 與結合字串函式 implode()

explode()使用範例
$sarray = explode(' ', 'Tyler Clippard is awsome !');
第二個參數是想分解的字串,依照第一個參數代表的分隔符號來分割
之後在將分割後的字串重送到字串陣列裡面
$sarray[0] = Tyler

implode()使用範例
$clause = implode(' ', $sarray);
第二個參數是要結合的字串陣列,第一個參數是分隔符號

3.字元替換函式str_replace()
使用範例
$replace_string = str_replace('thousands', 'hundreds', 'It worths two thousands ');
第一個參數是要被替換的字元,第二個參數是取代前者的自元,第三個參數是是目標字串

4.擷取字元函式 substr()

使用範例
substr('Yes, we can !', 5, 2)
上式可取得字串 - we
第一個參數是原始字串,第二個參數是擷取位置
字元在原始字串中被給予從0開始的編號,在5這個位置上的字元就是w
第三個參數是截取的字元數
如果在第二個參數輸入負數表是從字串尾巴開始取字元
第三個參數假如不輸入則代表要擷取剩下所有的字元
假如要取得的字元數比剩下字元數還多也是如此,不會在字串尾補上空值

假如要做出常見的"繼續閱讀"之類的功能可以考慮用這個做

5.自訂函式寫法

  function funcname($param){
  ...........
  return $var;
}

寫自訂函式時的寫法很寬鬆,宣告不用宣告函式回傳型態
缺點是若有疏忽會比較難以察覺

2010年5月26日 星期三

PHP樣板概念

程式碼的重複利用相當重要
它可以幫助減少程式碼的量以及撰寫時花的力氣
最大的好處是提供了維護的方便性
樣板就是將程式分成功能性元件
以下將提供範例:

原件成員:


startsession.php==========

<?php
  session_start();

  // If the session vars aren't set, try to set them with a cookie
  if (!isset($_SESSION['user_id'])) {
    if (isset($_COOKIE['user_id']) && isset($_COOKIE['username'])) {
      $_SESSION['user_id'] = $_COOKIE['user_id'];
      $_SESSION['username'] = $_COOKIE['username'];
    }
  }
?>

header.php======

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<?php
  echo '<title>Mismatch - ' . $page_title . '</title>';
?>

  <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>

<?php
  echo '<h3>Mismatch - ' . $page_title . '</h3>';
?>

navmenu.php==========

<?php
  // Generate the navigation menu
  echo '<hr />';
  if (isset($_SESSION['username'])) {
    echo '<a href="index.php">Home</a> &#10084; ';
    echo '<a href="viewprofile.php">View Profile</a> &#10084; ';
    echo '<a href="editprofile.php">Edit Profile</a> &#10084; ';
    echo '<a href="logout.php">Log Out (' . $_SESSION['username'] . ')</a>';
  }
  else {
    echo '<a href="login.php">Log In</a> &#10084; ';
    echo '<a href="signup.php">Sign Up</a>';
  }
  echo '<hr />';
?>

footer.php==========

  <hr />
  <p class="footer">Copyright &copy;2008 Mismatch Enterprises, Inc.</p>
</body>
</html>

完成品:

index.php============

<?php
  // Start the session
  require_once('startsession.php');

  // Insert the page header
  $page_title = 'Where opposites attract!';
  require_once('header.php');

  require_once('appvars.php');
  require_once('connectvars.php');

  // Show the navigation menu
  require_once('navmenu.php');

  // Connect to the database
  $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

  // Retrieve the user data from MySQL
  $query = "SELECT user_id, first_name, picture FROM mismatch_user WHERE first_name IS NOT NULL ORDER BY join_date DESC LIMIT 5";
  $data = mysqli_query($dbc, $query);

  // Loop through the array of user data, formatting it as HTML
  echo '<h4>Latest members:</h4>';
  echo '<table>';
  while ($row = mysqli_fetch_array($data)) {
    if (is_file(MM_UPLOADPATH . $row['picture']) && filesize(MM_UPLOADPATH . $row['picture']) > 0) {
      echo '<tr><td><img src="' . MM_UPLOADPATH . $row['picture'] . '" alt="' . $row['first_name'] . '" /></td>';
    }
    else {
      echo '<tr><td><img src="' . MM_UPLOADPATH . 'nopic.jpg' . '" alt="' . $row['first_name'] . '" /></td>';
    }
    if (isset($_SESSION['user_id'])) {
      echo '<td><a href="viewprofile.php?user_id=' . $row['user_id'] . '">' . $row['first_name'] . '</a></td></tr>';
    }
    else {
      echo '<td>' . $row['first_name'] . '</td></tr>';
    }
  }
  echo '</table>';

  mysqli_close($dbc);
?>

<?php
  // Insert the page footer
  require_once('footer.php');
?>

PHP筆記(2)

這篇寫比較多關於註冊、登入相關的項目
1.使用MySQL函式做密碼加密
2.Cookie使用方法
3.session設置

1.使用MySQL函式做密碼加密

當在寫註冊、登入功能時
密碼的加密是一個要點
他可以在即使資料庫不幸被盜取後,損失也能降低
駭客即使取得加密之後的密碼也無能為力

加密的方法是使用資料庫的SHA()函式,例如:
INSERT INTO table (account, pw) VALUES ('John', SHA('password'))
上式使用的SHA()函式會將password這個字轉換成40個字元的16進位制字串
注意一點是SHA()函式是不可逆的過程,這是它防止被盜取的機制

假如要進行密碼驗證
只要再輸入一樣的字串,經過SHA()函式變換之後能得到相同的字串,例如:
SELECT * FROM table WHERE pw = SHA('password')
上式的password是使用者輸入的密碼
這樣一來就可以做好驗證的工作了

當然還有非常重要的一點一定要提醒
因為轉換的字串長度問題
一開始在建造密碼欄位時要改成40個字元長度

其他類似的函式有
MySQL的MD5()    //安全性較差
PHP的sha1()及md5()

2.Cookie使用方法

當有必要持續記錄使用者登入狀態時,Cookie是一個選擇
他會將資料記錄並儲存在使用者端電腦裡

使用方法例如:
setcookie('index', 'value', time() + (60 * 60 * 1));
第三個變數會是cookie的存活時間值,沒有設置就是關掉瀏覽器即失效
範例中的例子是存活時間點會是現在過後的一個小時(3600s)內
讀取時使用$_COOKIE['index']來存取變數
假如要登出的話只要使存活時間成為過去時間即可
例如改成:
setcookie('index', 'value', time() - (60 * 60 * 1));

需要注意一點是Cookie設置仰賴瀏覽器協助
假使使用者關掉Cookie功能的話也不能成功

3.session設置

session可以視作另一種的Cookie
只不過資料儲存的地點是伺服器端,另外只要瀏覽器關閉即失效
這樣也可以避免瀏覽器關閉Cookie功能帶來的影響
假使cookie真的被停用時
server上的php.ini裡的session.use_trans_id 設定成1
這樣就可以克服問題了

使用session的範例:
logout.php======

<?php
//if the user is logged in, delete the session vars to log them out
session_start();
if(isset($_SESSION['id'])){
//delete the session vars by clearing the $_SESSION array
$_SESSION = array();
//delete the session cookie by setting its expiration to an hour ago
if(isset($_COOKIE[session_name()])){
setcookie(session_name(), "", time()-3600);
}
//Destroy the session
session_destroy();
}
?>

session_start()函式會啟動session功能,然後伺服器會發一組辨識ID
即使在登出也要使用這個函式才能存取session
尾部的session_destroy()結束session,瀏覽器關閉時也會自動呼叫這個函式
使用session的方法一樣是用全域函數 ,如$_SESSION['id'] = 'john'
要清除session的方法除了關掉瀏覽器外
$_SESSION = array()這樣,指定session為空陣列也可以

實際在運作時,session也很有可能在私底下會用到cookie
畢竟cookie是最佳化的資料儲存方式
session_name()可以幫助我們找到該cookie,這樣一來就可以握到完整的清除

2010年5月23日 星期日

PHP實作安全機制(2)

Injection attack是一種常見的網路攻擊型態
表單欄位是Web應用程式安全上的弱點
它提供了使用者輸入資料的空間卻又難以預料使用者的行為

攻擊作法的舉例:

假設有個欄位輸入的資料會以以下SQL指令存入到SQL資料庫裡
INSERT INTO table1 VALUES(0, $name, $score, 0)
假如在欄位裡面故意輸入10000', 1) --
注意--後面有一格空格, 這會讓資料庫儲存的資料被更改
被更改的原因是因為SQL的註解方式是( -- )
存入之後便會讓後方的資料被忽略,而存進他填進來的資料

這種注入攻擊是極端麻煩的一件事,而且SQL的註解方式不只這一種
即使對於資料竄改沒有湊效,填進的特殊字元也可能讓資料庫產生錯誤
所以一定要設法避免這種情形發生

解決方法:

利用PHP的 trim()、mysqli_real_escape_string()兩個函式來處理
trim($_POST['XXX']),這個函式可以去除資料表單頭尾處的空白
mysqli_real_escape_string($dbc, trim($_POST['XXX']))
函式的第一個參數是資料庫連結變數
它可以將有危險性字元脫逸掉,使他們可以出現在表單裡,但卻不會影響正常查詢

另外有幾個作法可以幫助資料輸入的安全性
INSERT查詢寫成明確指出要將哪個值插入哪個欄位
有自動產生值或是預設值的欄位就不提供輸入的機會

減少注入攻擊的方法還有進行表單驗證、
檢查檔案類型或是尺寸是否在規範內、
檢查欄位是否有填入資料、
預期只會填入數字的欄位也可以用 is_numeric()函式來做驗證
這些方法都能將安全性再提高些。

PHP實作安全機制(1)

使用PHP有幾種方法來保護安全:
這邊介紹用Http認證來防護
也會提到header()函式的用法

Http認證

Http認證的方式是透過伺服器與瀏覽器之間的header溝通來運作
header在每個瀏覽器請求或是伺服器回應裡頭傳送
這使得我們可以用它來中斷從伺服器到瀏覽器的頁面傳送
要求使用者輸入正確的帳號與密碼後才能繼續讀取頁面

實作程式碼如下:
auth.php===========

<?php
//user name and password for authentication
$username = 'admin';
$password = "pw";

if(!isset($_SERVER['PHP_AUTH_USER']) ||
   !isset($_SERVER['PHP_AUTH_PW']) ||
   $_SERVER['PHP_AUTH_USER'] != $username ||
   $_SERVER['PHP_AUTH_PW'] != $password
   ){
   //The user name/password are incorrect to send the authentication headers
   header('HTTP?1.1 401 Unauthorized');
   header('WWW-Authenticate:Basic realm = "test"');
   exit('<h2>Fail Login!!!</h2>');
   }else{
   header('Location:http://www.google.com');
   }
?>

這段程式碼要求使用者輸入帳密
如果帳密輸入錯誤,則會跳出Fail Login的字眼
成功則轉送到Google的頁面

程式碼裡用了兩個PHP超全域變數
$_SERVER['PHP_AUTH_USER'] 這個變數存放使用者輸入認證視窗的名稱
$_SERVER['PHP_AUTH_PW']則存入輸入認證視窗的密碼

header()函式控制伺服器傳送給瀏覽器的資訊
但是這個在使用上有一個限制,標頭必須在任何html內容之前被傳送
即使前面有個空白字元也不行

header('HTTP?1.1 401 Unauthorized')讓瀏覽器知道使用者尚未通過認證,無法觀看頁面
下一行的裡的WWW-Authenticate要求瀏覽器提示使用者輸入帳號密碼進行認證
Basic realm則是用來辨識這個特別認證的用語
它的值會出現在認證視窗中,一旦使用者正確輸入帳密,在同樣realm的面即可通行無阻

exit()當然就是提供了離開認證之後出現的畫面
這段程式碼設定了當前兩個header執行結束(認證失敗)時才會被執行到

header的使用上相當有彈性,不只是這例子裡面的認證。
header('Location:http://.....')能轉移網頁到別的頁面
header('Refresh: 5; url = http://......')這個則是重新整理header,每一段時間就重整一次
header('Content-Type:text/plain')這樣的header則可以控制伺服器遞送的資料形態

當然這段程式碼有需要的話能夠寫成單獨頁面,其他有需要使用的頁面用include方式引入即可

2010年5月22日 星期六

上傳圖片到資料庫使用PHP

搭配MyySQL來建造一個上傳圖片的功能,但這篇不說明SQL部分的處理

首先先建立html表單如下:

<form enctype="multipart/form-data" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
//enctype 這個表單屬性讓表單使用特殊的編碼類別進行檔案上傳,影響POST時的打包及傳送
<input type="hidden" name="MAX_FILE_SIZE" value="32768" />
//建立檔案上傳容量限制(32KB)
<label for="name">Name:</label>
<input type="text" id="name" value="<?php if(!empty($name)) echo $name; ?>" />
<br />
<label for="score">Score:</label>
<input type="text" id="score" value="<?php if(!empty($score)) echo $score; ?>" />
<br />
<label for="screenshot">Screen shot:</label>
<input type="file" id="screenshot" name="screenshot" />
<hr />
<input type="submit" value="add" name="submit" />


接下來是獲得上傳檔案的名稱
這邊使用PHP的$_FILES超全域變數
他有以下幾個常用的用法
$_FILES['screenshot']['name']            //上傳檔案的名稱
$_FILES['screenshot']['type']             //上傳檔案的類別
$_FILES['screenshot']['size']              //上傳檔案的大小
$_FILES['screenshot']['tmp_name']    //伺服器上暫時性的檔案儲存位置
$_FILES['screenshot']['error']            //檔案上傳的錯誤碼;0表示成功,其他表示失敗

使用['name']建立在資料庫的欄位
儲存在外部檔案的資料比較好的應用方式是在資料庫裡儲存指向參考
假使儲存在資料庫中,則會面臨資料形態的轉換(2進制 -> 10進制)
讀取時又會面臨另外一次的資料形態轉換

注意一點,檔案上傳到伺服器後,會被儲存在暫時性的目錄
所以接下來要做的動作就是將檔案從暫時性的目錄搬到你想要放的位置
使用 move_upload_file($_FILES['screenshot']['temp_name'], $target) 函式
第一個參數前面說明過了,第二個參數放目標位置,包含路徑與檔案名稱

目標路徑的部分可以設置成常數,例如:
define('PIC_PATH','images/');
$target = PIC_PATH.time().$_FILES['screenshot']['name'];
//time()是用來增加獨特性,避免同檔名的檔案覆蓋
這樣一來萬一路徑有變就只需要改寫定義路徑(define)即可

當然在不同的頁面要使用共享的指令,例如這個路徑位置定義
使用include方法來處理
appvars.php============

<?php
//Define application constants
define('PIC_PATH','images/');
?>

在其他要使用的php頁面加上require_once('appvars.php'); 這行即可
require與include的差別在於發生錯誤時require會產生錯誤訊息,include則不會
視情況挑選自己想用的函式
PHP頁面中的DB部分設置如user、pw等常數也能這樣處理。

最後 is_file() 與  filesize() 這兩個函式也能幫助辨識檔案的存在與大小
以避免檔案不存在或建立0位元的空資料,可以好好利用
假使有檔案想刪除的話可以使用 @unlink() 函式
函式前面的@在檔案上傳實際上並不成功時可以抑制錯誤

2010年5月21日 星期五

PHP筆記(1)

最近回頭唸了些PHP的東西
回溫一點記憶跟想一些以前沒有體會的想法
把零零碎碎的部分寫成記事

寫PHP時先要準備撰寫環境
需要的有Apache、PHP(以上必備)、MySQL(或其他資料庫軟體)
單獨抓或者 是用套裝軟體都可以
常見的有AppServ、Xampp
套裝軟體管理上比較方便,而且整合比較好
管理資料庫上 通常也使用整合好的phpmyadmin圖形介面
算是方便的選擇
安裝完記得把WWW資料夾下除了phpMyAdmin以外的全砍了以保持安全
php.ini 裡的 extension=php_mysqli.dll    extension=php_gd2.dll
這兩行如果前面有;要先去掉

這篇談的有:
1."和'的差別
2.連結資料庫使用的語法
3.SQL陳述式分號
4. mysqli_query()函式結果輸出
5.PHP寄送mail的方法
6.使用驗證變數的php函式
7.超全域變數$_SERVER['PHP_SELF'] 的使用方法

1."和'的差別

PHP裡對字串的處理方式有兩種,以單引號或雙引號括起來
不同的地方是單引號表達未經處理的字串
變數在裡面不會特別轉換出來
雙引號則是會將句子裡的變數自動替換成變數裡的值
 當然如果有需要,是可以兩種引號混合使用

另外 . 這個符號也可以拿來分隔字串
例如 $a = "I eat ".$lunch." for lunch"怎樣處理都可以
維持易讀易修改的文章風格即可

下面有一個稍微特殊的例子

      for($i = 1; $i <=3; $i++){
        if(!empty($_POST["p$i"])){
            $var = "p$i";
            $$var = mysqli_real_escape_string($dbc, strip_tags(trim($_POST["p$i"])));
            $query =  'SELECT `state` FROM `commodity_state` WHERE `serial number` ='.$$var;
      .........

上面這個例子要做到動態物件配置
$$var可以先視作 以$var的值,再做一次變數宣告,但是在雙引號中只能變換一次
所以query那行只能用此寫法,否則字串的值會變成  "$$var"  =  $p1

2.連結資料庫使用的語法

$dbc = mysqli_connect('localhost','root','pw','db') or die("can't link to database");
//mysqli_connect第四個參數可以不輸入,而改用加上mysqli_select_db()進行
$query = "INSERT INTO xxx (firstname, lastname, tel) VALUES ('Salty','Jobs', 'kkkk')";
//result僅儲存是否執行成功,回傳一個query物件
$result = mysqli_query($dbc, $query) or die('Error ocurr');
mysqli_close($dbc);                     //只能在已經確實呼叫資料庫時才能夠呼叫此函數
 //SELECT * form dbname WHERE xxx = 'yes';  這串是查詢用的展示碼
 
mysqli是舊有的mysql函式的改良(improved)


3.SQL陳述式分號

只有在MySQL的終端執行SQL碼時需要加上分號,使用PHP的mysqli_query則不需要

4. mysqli_query()函式結果輸出

這個函式需要搭配mysqli_fetch_array()函式
否則只會看到伺服器所給定的資源編號
使用上為mysqli_fetch_array(query結果),每做一次擷取一筆資料
傳回結果當然是陣列,要讀取值的話就要用 $Array名稱[' column名稱  ']
搭配while迴圈更可以寫成如下範例:
while($row = mysqli_fetch_array($result)){
  echo $row['name'].' '.$row['email'].'<br />';
}

當沒有傳回資料,這個就會被判定成false
而PHP判定true的條件很寬鬆,只要不為0都可以,所以可以這樣寫

另外有一個跟mysqli_fetch_array()相似的mysqli_fetch_field()函式
它的用途是取得傳回資料的相關資訊
例如:
while($row = mysqli_fetch_field($result)) {
$column = $row->name;
...

在每一次查詢的迴圈裡都會回傳一個欄位名稱
上述的範例可以求得該張資料表的所有欄位

5.PHP寄送mail的方法

mail($To, $subject, $msg, $from ); //msg就使用跳脫符號 \n 打成長長的字串吧

6.使用驗證變數的php函式

isset()與empty()這兩個函式可以幫助辨識變數的值
isset() 會確認變數是否存在,而empty則可以進一步辨識函數是否為空值
$v1 = 'a';   $v2 = '';
以這兩個變數來說,即使v2是空值,但是isset還是會判定成true

這個函式最常被使用的方法是確認表單的資訊是否有傳過來
接著再用empty判 斷變數值
每個表單裡一定也會有submit按鈕
if(isset($_POST['submit']))可以讓我們知道表單是不是有被遞送,這也是使 用上的一個方法

7.超全域變數$_SERVER['PHP_SELF'] 的使用方法

當 有個頁面需要傳送資料給自己
例如:<form action="index.php" method="post">
改 成<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
這項改變當然不是那麼誇張,但他是使頁面更易於維護的小方法之一

2010年5月10日 星期一

定時任務:Java中Timer和TimerTask的使用

java.util.Timer定時器,實際上是個線程,定時調度所擁有的TimerTasks。
一個TimerTask實際上就是一個擁有run方法的類別,需要定時執行的code放到run method內,TimerTask一般是以匿名類的方式創建。



一個完整的Timer:
Java  code

   java.util.Timer timer = new java.util.Timer(true);  
   // true 說明這個timer以daemon方式運行(優先級低,  
   // 程序結束timer也自動結束),注意,javax.swing  
   // 包中也有一個Timer類,如果import中用到 swing包,
   // 要注意名字的衝突。  
     
   TimerTask task = new TimerTask() {  
   public void run() {  
   ... //每次需要執行的代碼放到這裡面。  
   }  
   };  
    
   //以下是幾種調度task的方法:  
    
   timer.schedule(task, time);  
   // time為Date類型:在指定時間執行一次。  
    
   timer.schedule(task, firstTime, period);  
   // firstTime為Date類型,period為long  
   // 從firstTime時刻開始,每隔period毫秒執行一次。  
    
   timer.schedule(task, delay)  
   // delay 為long類型:從現在起過delay毫秒執行一次  
    
   timer.schedule(task, delay, period)  
   // delay為long,period為long:從現在起過delay毫秒以後,每隔period  
   // 毫秒執行一次。

下面是一個完整的例子,由兩個類組成,一個定製任務,一個調用java.util.Timer

定製任務:
Java code

    import java.util.Timer; 
     
    public class TimerTaskTest extends java.util.TimerTask{ 
     
    @Override 
      public void run() { 
       // TODO Auto-generated method stub 
       System.out.println("start"); 
      } 
    } 

使用java.util.Timer
Java code

    import java.util.Timer; 
     
    public class Test { 
    public static void main(String[] args){ 
       Timer timer = new Timer(); 
       timer.schedule(new TimerTaskTest(), 1000, 2000); 
    } 
    } 

根據上面的介紹,便可以在1秒後,每隔2秒執行一次程序

2010年5月8日 星期六

Tomcat設定

建立JSP Server的先決條件當然是要先有Java的環境
設定完之後,安裝Tomcat這個軟體以便撰寫Java Web Application
選取路徑安裝完後,在環境變數裡作修改
變數  CATALINA_HOME=C:\Tomcat 6.0   (Tomcat的實體目錄位置)
如此就大功告成
啟動只要到bin裡用tomcat6w.exe控制即可

Tomcat要與其他資料庫連結的話
必須下載connector來達成
例如要與MySQL連結就必須到 http://dev.mysql.com/downloads/connector/j/ 下載
解壓縮後將裡面的mysql-connector-java-5.1.12-bin.jar放到Tomcat/lib裡
其他資料庫軟體亦是同樣作法

Tomcat作為Apache計畫裡面的其中一支,使用的port是8080
由於架站伺服器軟體之間會有互相排斥的情形
實際上Apache對於其他的語言的支援性較高
想做整合的話可以考慮用Xampp整合Apache和Tomcat

JDK安裝環境設定

http://www.oracle.com/technetwork/java/javase/downloads/index.html
到Oracle下載最新版的jdk

接著到  [電腦] -> 右鍵[內容] -> [進階系統設定] -> [環境變數]  中做設定




JAVA_HOME         C:\Program Files\Java\jdk1.7.0_09      
PATH                     ;%JAVA_HOME%\bin      (附加在現有內容後)
CLASSPATH         %JAVA_HOME%\lib

不存在的變數以新增建立即可
建立 JAVA_HOME 變數儲存 Java路徑資料
日後昇級版本只要修改 JAVA_HOME 就能一併修改其他套用 JAVA_HOME 的環境變數
PATH 設定能簡化呼叫 JDK 的路徑宣告
CLASSPATH 則是簡化了編譯 Java 檔案時要引用的工具路徑

如此一來就完成最基礎的設定
接著可以到windows的search裡面打上cmd
輸入 java -version可以看到java版本的資訊
再輸入javac,如果有出現許多指令就代表成功囉