2011年11月30日 星期三

[NET].免費限速軟體 NEGiES 1.57(中文繁體)

(日文)官方網站: http://hp.vector.co.jp/authors/VA036210/download1.html
軟體下載: http://www.mediafire.com/?oiogmziy5n2

通常 browser 都沒有限速的功能, 由於現在要寫檔案上傳的功能, 需要做測試, 區網(或本機) 的傳輸速度太快, 會看不到進度bar(progress bar). 所以必需使用限速軟體.


(中文版)設定的範例:



(日文版)設定的範例:


相關文章1: http://save-coco.blogspot.com/2009/01/negies-157_6710.html

相關文章2: http://freesoft.tw/?p=1471

2011年11月29日 星期二

[HTML].flash 上傳元件的限制

前言:
最近在開發一個 HTML 上傳檔案的 form, 類似 gmail 的界面, 可以在同一個畫面裡, 透過 flash 上傳多筆的附件檔案.

這次的遇到的問題是, 我在畫面中使用了2個 swfupload 物件, 一個要上傳附件, 一個要上傳多媒體檔案, 上傳多媒體檔案時, 無法讓檔案上傳, 可是上傳附件的部份都ok.


* 附註:上傳多媒體檔案的方式是透過 jQuery 的 blockUI plugin 彈出一個 dialog:

說明: 這個彈出的方式, 比較特別, 是先選取檔案, 等使用者按下 "上傳"按鈕, 才開始上傳資料.


debug swfupload 的 message 如下:




假設, 是因為放了2個 swfupload object 造成的沖衝. 我的除錯流程如下:
挑戰解法 1, 增加一個新的區塊, 把這種"確定後才開始上傳" 的範例用的程式都放到新的區塊. 測試結果, 是ok的. 測試的範例, 可以2段式的, 按下確定後, 才開始上傳.

挑戰解法 2. 移掉 附件在用的 swfupload object, 讓畫面只剩下 上傳多媒體用的 swfupload object, 結果,還是不能 work.

所以, 這一個假設, 應該不成立, 於是比較看看目前的顯示方式, 與"確定後才開始上傳" 的範例的差異, 假設要存取 user client 端的資料時, swfupload 必需是 visible(也許看的到), 結果可行, 似乎是安全性造成的.

結果: flash 上傳元件的限制, 就是 user 要開始上傳 client 端資料時的一瞬間, swfupload object 需要要顯示在畫面裡可能被看的見的區塊裡, 用 "一瞬間" 的意思指, 原本隱藏的區塊, 在 "選取" 檔案時, 和 "開始上傳時" 這2個時間點, 都顯示在畫面上, 是可以上傳成功的.


* 附註: 當 user 選好 client 端的檔案後, "可能" 不能對這一個 flash object 做搬移區塊的動作. 搬了的話會造成 flash UI 的重新整理, 也會造成無法上傳檔案. 會用 "可能", 是指我的手法可能太遜, 搬失敗, 可能有其他的方法, 可以正常地去搬移 flash object 的位置, 而且還可以正常的上傳, 這個部份先跳過...



相關文章:
swfupload Document:
http://demo.swfupload.org/Documentation/#startUpload

swfupload project:
http://code.google.com/p/swfupload/

後記: 原來是 blockUI 裡的 bindEvents, 把事件吃掉..., 我最後的解法是 bindEvents: false, 然後再把 flash 放在一個奇怪的 position:absolute;z-index:9999; left:-100px; top:-50px; 的 layer 裡, 應該可以有更好的解法 XD

2011年11月24日 星期四

JQuery 背景變暗dialog example

新版本的 jQuery 有提供 .dialog(),參考看看.
http://jqueryui.com/demos/dialog/
or
http://docs.jquery.com/UI/API/1.7.2/Dialog
or
http://hi.baidu.com/li_mingzhu/blog/item/1279308d319a0ea60e2444fd.html

另一個不錯的解決方案是使用 blockUI:
http://www.malsup.com/jquery/block/
or
http://jsgears.com/thread-72-1-2.html

blockUI 用起來真的很方便,參數:
showOverlay: false , 就是指,modalless, 背景不會被 lock 住.
showOverlay: true , 就是指,modal, 背景會 lock 住.


google keyword: jquery blockUI, 這個似乎比較好用.

主機移機(或重灌)的check list

公司的主機進行重灌工程, 我有幾個項目沒有做到, 造成... 多花了一些時間進行重新設定, 所以, 把這次裝機的 check list 出來, 希望日後會有幫助:

 ● 重灌前要備份的項目
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 1. Database 備份.
 2. Database 維護計畫 備份..
 3. IIS site + IIS application pool 的 XML file 匯出.
 4. IIS site 的 SSL 憑證匯出.
 5. hosts 檔案備份.
 6. ipsec 的原則匯出.
 7. 防火牆的例外規則.
 8. 排程 \windows\tasks\ 項目備份.
 9. 程式+附件.
 10. 檢查看看, 有沒有一些舊網站在用的外部 app 需要備份.(例如: 自行開發的浮水印app, 或 single site on 之類的 web service)


 ● 重灌後的工作項目
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 1. 建新低權限的 匿名存取用的帳號(IUSER).
 2. 先安裝元件, 並允許新建的 IUSER 有 create owner 權限.
 3. 還原 程式+附件, 並設定 IUSER 有讀取權限, 部份上傳用的資料夾, 有寫入權限.
 4. 還原 Database + 維護計畫.
 5. 匯入 IIS site XML file + IIS application pool.
 6. 匯入 SSL 憑證.
 7. 還原(或修改) host.
 8. 設定防火牆例外規則 + ipsec 原則.
 9. 排程的 job 檔還原回去 \windows\tasks\ , 如果有修改帳號/密碼的話, 記得要重設jobs裡的帳/密.
 10. 還原舊網站在用的外部 app 需要備份.(例如: 自行開發的浮水印app, 或 single site on 之類的 web service)


附註: 上面應該是比較簡單(典型)的主機的備份/還原的流程, 每個案子可能會有些額外的客製化的流程, 例如: SQL Server 的全文檢索的設定, 或 Asp.Net framework 4.0的安裝, 還是 php 或 java 的安裝...etc.

2011年11月23日 星期三

IIS 只能執行"靜態網頁"時的設定方法

系統重灌後, 又卡關了, 這次遇到的是 靜態網頁(.html 或 .xml) 可以執行, 動態網頁(.asp or .aspx) 無法執行, 會傳回 404 - file not found.



google 了 keyword: IIS 6.0 asp 無法執行 html 可以
厲害的 google, 傳了很多資料回來, 研究了幾篇之後, 發現應該是 "網頁服務延伸" 的問題, 果然, 在 IIS 裡, 網頁服務延伸 是空空的, 什麼也沒有.


google 改下 keyword: 網頁服務延伸 asp,
又看了幾篇文章, 挑戰使用 aspnet_regiis –i 指令, 進行重新安裝, 並重新啟動 www service, 結果, 無效.

挑戰自行新增 "網頁服務延伸":



輸入 Asp, 並選取 asp.dll

結果, 新增失敗, 他說該 dll 已被 active server pages 所使用,


挑戰, 使用 aspnet_regiis –ga 試試看, 也無效,
挑戰, aspnet_regiis –c 試試看, 也無效,
最後, 再使用 aspnet_regiis –i 試試看, 結果, 回來重新整理 "網頁服務延伸" 的目錄, 有效, 而且神奇的是他預設就幫 Asp 設成 "已允許"...



資料來源: ASP.NET 4.0 安裝在 IIS6 最常遇到的四個問題
http://blog.miniasp.com/post/2010/06/22/IIS-6-ASPNET-4-Installation-Notes.aspx#continue

附註: 理論上 IIS 的匿名使用者, 用 IUSER 應該就很安全了, 萬一如果您想要自定使用者的話, 請請記得把這個 User 做以下的設定:
1. user 的群組, 請移掉 User group, 加入 guests group.
2. 不允許 遠端登入.
3. 不允許 登入伺服器.

參考 URL: http://maxtellyou.blogspot.com/2011/12/iis-iuser.html

IIS 設定Application Pool 和 新使用者的標準作業流程(SOP)

開Asp網頁的IIS主機重灌, 其實重灌前, 我就有發現 c:\windows\temp\ 的資料夾裡, 被放了奇怪跳板程式, 8成是這台主機的權限之前沒設好, 所以這次用比較高的安全性的設定方式來設定 IIS, 結果一設下去, 就出現:
Service Unavailable 的錯誤.

連直接執行 html file, 都出現 Service Unavailable 的錯誤訊息, 錯誤發生的原因是, 修改 IIS 中應用程式集區(Application Pool)的身份識別, 如果設回去使用 網路服務(NETWORK SERVICE), 網頁就又可以正常執行, 但會彈出安全性的登入框, 因為 test.html 檔案, 網路服務(NETWORK SERVICE)帳號目前沒有存取的權限.


修改 Application Pool 的步驟是,
1.先在系統中新增一位新的使用者 (假設叫做 webUser )
2.修改 IIS 中應用程式集的身份識別設定 (如下圖)





如果在事件檢視器中查詢錯誤紀錄,你就會發現是 Application Pool 發生錯誤, 造成 Service Unavailable.



下就是正確設定的標準作業流程(SOP):
1.新增使用者
2.將該使用者加入 IIS_WPG 群組
3.修改系統暫存目錄 ( C:\WINDOWS\Temp ) 權限, 為特殊權限, 只需要和 NETWORK SERVICE 的設定值一樣,只要有「列出資料夾/讀取資料」與「刪除」兩種權限就可以正常運行 ASP.NET 了。


檢視/設定使用者詳細權限(進階安全性設定)的方法如下:
1.按右鍵, 設定安全性.
2.按 "進階" 按鈕.
3.選 webUser(新增的使用者).
4.按 "編輯" 按鈕.



資料來源: IIS應用程式集區自訂身份識別後如何讓 ASP.NET 正常執行
http://blog.miniasp.com/post/2008/12/ASPNET-and-Application-Pool-Identity-setting-in-IIS-60.aspx

保哥說:
我們都知道 ASP.NET 在 IIS 6.0 中運行的時候,真正的執行權限使用者是應用程式集區(Application Pool)的身份識別(Identity)頁籤中定義的那位使用者,預設的使用者是「網路服務(NETWORK SERVICE)」,而且實際在執行的程序名稱(Process Name)為 w3wp.exe,各位可以從工作管理員中看到。

我們先設定一種情境,如果一台 IIS 中有兩個 ASP.NET 網站,分別由不同的開發團隊或客戶所管理,而兩個網站都有設定檔案上傳的功能,因此兩個網站一定會有特定目錄需要賦予 ASP.NET 可寫入權限,因為在 IIS 中 ASP.NET 的預設權限使用者就等於應用程式集區中定義的身份識別使用者,也就是所謂的 NETWORK SERVICE 系統使用者。

如果當其中第一個網站被入侵或值入後門程式時,也代表著這些後門程式正以 NETWORK SERVICE 的身份在你的主機中肆虐,攻擊的對象即便僅限於「網站」,但當然也包括第二個網站的部分可寫入路徑。

若要增強網站間的安全性與隔離性,我們這時就會需要修改應用程式集區(Application Pool)的身份識別設定,讓兩個 ASP.NET 網站個別使用不同的身份識別來執行所有的程式。


* 附註: IUSER 請使用 guests group 而不是 user group, 以避免權限的問題.

[SQL].資料庫主體在資料庫中擁有 結構描述 且無法卸除。

情況: 有一台主機重灌, 所以備份了 sql server 的 db, 重灌之後, 用 restore 貼回 database, 但原本 db 裡的 userid 不能登入, 也刪不掉, 用指令來刪, 會出現下面的錯誤:
DROP USER [myAccount]



後來, 再多建立一個 dbo 的帳號後, 就可以把舊的, 不能刪除的帳號刪掉, 似乎一個 database , 最少要有一個 dbo 帳號的樣子.

2011年11月21日 星期一

[js].避免 window.onload 被覆蓋,請用 addLoadEvent()

如果, 你的程式使用 window.onload = function()
後面的 javascript 會覆蓋掉前面先使用的人的程式碼.
建議解法, 透過下面的 function 來把您要加入到 onload 裡的程式做append.

function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof oldonload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}

Windows 排程指令 SCHTASKS

如果您設排程, 使用指令的方式來下達, 
好處:
1. 移機時也方便把排程帶到別台主機上.
2. 可以動態產生相關的排程工作.


排程工作被產生之後, 會在 c:\Windows\Tasks\ 的隱藏目錄下增加一個 taskname.job
把 taskname.job 複製出來, 就可以拿到別台主機上去使用了, .job 拿到別台主機記得要重新設定一下執行帳號及密碼, 因為可能不太一樣.

--------------------------------------------------

SCHTASKS /parameter [arguments]

描述:
    讓系統管理員能夠在本機或遠端系統上建立、刪除、查詢、
    結束排程工作。取代 AT.exe

--------------------------------------------------

SCHTASKS  /query /fo csv /v > tasklist.csv

描述:
    查詢目前所有的排程, 輸出到文字檔 tasklist.csv。

--------------------------------------------------

SCHTASKS /Create [/S system [/U username [/P [password]]]]
    [/RU username [/RP password]] /SC schedule [/MO modifier] [/D day]
    [/M months] [/I idletime] /TN taskname /TR taskrun [/ST starttime]
    [/RI interval] [ {/ET endtime | /DU duration} [/K] ]
    [/SD startdate] [/ED enddate] [/IT] [/Z] [/F]


描述:
   讓系統管理員可以在本機或遠端系統上建立排程工作。

參數清單:
    /U           username          指定要執行命令的使用者內容。

    /P           password          指定使用者密碼。

    /RU          username          指定要執行工作的使用者
                                   帳戶 (使用者內容)。
                                   系統帳戶的有效值是
                                  "","NT AUTHORITY\SYSTEM" 或
                                   "SYSTEM"。

    /RP          password          指定排程執行頻率。
                                   如果要詢問密碼,參數值必須
                                   設定成 "*" 或不設定。

    /SC          schedule          指定排程執行頻率。
                                   有效的排程類型: MINUTE,HOURLY,
                                   DAILY,WEEKLY,MONTHLY,ONCE,
                                   ONSTART,ONLOGON,ONIDLE。

    /MO          modifier          重新調整排程類型,
                                   來改善週期性的排程控制。
                                   有效值列在下列的 "Modifiers"
                                   區段中。

    /D           days              指定工作執行的日期。
                                   有效值是: MON,TUE,WED,
                                   THU,FRI,SAT,SUN。還有
                                   MONTHLY 排程 1 - 31 (以月份
                                   為主的天數)。

    /M           months            指定排程工作的月份。
                                   預設值是每月的第一天。
                                   有效值是: JAN, FEB, MAR,
                                   APR, MAY, JUN, JUL, AUG, SEP, OCT,
                                   NOV, DEC.

    -i           idletime          指定閒置時間的長短,
                                   過了這個時間就會執行排定的
                                   ONIDLE 工作。
                                   有效範圍是: 1 - 999 分鐘。

    /TN          taskname          指定可以用來識別
                                   這個排程工作的唯一性名稱。

    /TR          taskrun           指定這個排程工作執行
                                   程式的路徑及檔案名稱。
                                 
                                   範例: C:\windows\system32\calc.exe

    /ST          starttime         指定工作的執行時間。
                                   時間格式是 HH:MM (24 小時制)
                                   範例,14:30 代表2:30 PM。

    /SD          startdate         指定工作第一次執行
                                   的日期。格式是 yyyy/mm/dd。
                                   預設成目前的日期。
(這不適用於以下排程類型: ONCE、ONSTART、ONLOGON 和 ONIDLE。)

    /ET          endtime           指定執行工作的結束
                                   時間。時間格式是 HH:MM
                                   (24 小時制) 範例: 14:50 代表下午 2:50。
(這不適用於以下排程類型: ONSTART、 ONLOGON、和 ONIDLE。)

    /ED          enddate           指定工作最後一次執行的日期。
                                   格式是 "yyyy/mm/dd"。

(這不適用於以下排程類型: ONCE、ONSTART、ONLOGON 和 ONIDLE。)

    /Z                             如果不須再次執行工作
                                   請將它刪除。

    /F                             如果指定的工作已經存在,
                                   則強制建立工作
                                   和抑制警告。

    /?                             顯示這個說明訊息。


修飾元: 每個排程類型的/MO 參數有效值:
    MINUTE:  1 - 1439 分鐘。
    HOURLY:  1 - 23 小時。
    DAILY:   1 - 365 天。
    WEEKLY:  1 - 52 週。
    ONCE:    沒有修飾元。
    ONSTART: 沒有修飾元。
    ONLOGON: 沒有修飾元。
    ONIDLE:  沒有修飾元。
    MONTHLY: 1 - 12 或 FIRST,SECOND,THIRD,FOURTH,LAST,LASTDAY。

----------------------------------------
Examples:


Ex: To schedule a task to run every 20 minutes
schtasks /create /sc minute /mo 20 /tn "Security Script" /tr "\"d:\test.bat\" 123" /rU administrator /rP ********

Ex: 每10分鐘執行某一個網址, 用來批次重新計算某些數值.
schtasks /create /sc minute /mo 3 /tn "compute_node_counter" /tr "D:\元件\tinyget.exe -srv:\"www.mysite.com.tw\" -uri:\"/mytask/ws/computeNodeCounter.asp\"" /rU administrator /rP ********

Ex: MyApp 程序在每天的 8:00 A.M. 運行一次。每天運行命令。
schtasks /create /tn "My App" /tr c:\apps\myapp.exe /sc daily /st 08:00:00

Ex: Create a task to run at 11 pm every weekday
SCHTASKS /Create /SC weekly /D MON,TUE,WED,THU,FRI /TN MyDailyBackup /ST 23:00:00 /TR c:\backup.cmd /RU MyDomain\MyLogin /RP MyPassword

Ex: delete a job.
SCHTASKS /Delete /TN "Security Script" /f

----------------------------------------

附註 1: 如果你用的是Win 2003 Server 中文版,daily、weekly、monthly的指令必須轉成中文。

Daily: 每日
Weekly: 每週
Monthly: 每月


例如:
D:\>schtasks /create /sc DAILY /mo 1 /tn "檢查庫存 0200" /tr "checkstock.exe" /rU administrator /rP *********
錯誤: 指定的排程類型不正確。
請輸入 "SCHTASKS /CREATE /?" 來查閱使用方式。

D:\>schtasks /create /sc 每日 /tn "檢查庫存 0200" /tr "checkstock.exe" /st 08:00 /rU administrator /rP ********
成功: 排程工作 "檢查庫存 0200 (2011)" 已成功建立。


附註 2: 有些主機, 使用的 time format 是 hh:mm:ss.

附註 3: 有些主機, 使用的 date format 可能是 mm/dd/yyyy.

附註 4:排程在使用的執行的帳號, 如果重設密碼後, 不知道需不需要回來重設一定排程裡的密碼.(待測)

2011年11月20日 星期日

Windows Server 在資訊安全性上的建議

為了避免公司內部使用中的主機中毒(或被入侵), 被裝了後門(backdoor) 或木馬(Trojan), 或跳板(Proxy), 小弟有幾點建議:

 ● 1. Server主機上都需要安裝防毒軟體, 沒$$的話, 可以暫時用Windows 免費防毒軟體 - Microsoft Security Essentials.

 ● 2. 在遠端登入的地方, 設定增加 Log來記錄, 是誰登入進來. 看看是不是Hacker, 手動連進來, 如果可以的話, 最好還是把 3389 改成別的 Port, 避免在同一個LAN 裡, 有已經中毒的電腦監聽(sniffer) 3389 port.

 ● 3. Administrator 應該定期更換密碼.

 ● 4. 定時使用 Microsoft Baseline Security Analyzer 檢查主機, 是否有系統的設定上或Windows Update上需要加強的地方.

 ● 5. 加裝 Microsoft Port Report + PortReportParser (Log 分析) 監控封包是否有異常, 有機會可以在出事後, 看到攻擊的過程與方法.

 ● 6. 針對各個的主機, 設定 IPSEC 規則, 在內網(intranet的部份) 不要允許 access (連入/連出) 自己單位的主機以外的 IP(和Port), 避免在主機被中毒(或入侵)後對別台主機做攻擊, 而且打掛別人在用的主機.

一般公司都會有外部防火牆來防止駭客直接入侵,應該還需要內部防火牆,來防內賊的,一般無內部防火牆的情況下,只要知道資料庫的IP、帳號及密碼,在內網就能夠去取得資料庫的資料,如果使用IPSec來建立內部防火牆,就能設定IP白名單,僅允許白名單上的伺服器可連接資料庫,也可以減少主機被內部的PC(或NB) 攻破的機率,也可以減少中毒(或被入侵)後對別台主機的影響.

 ● 7. 建議不要使用 c$ ... 的共享.
--------------------------------------------------------
禁止C$、D$ 等管理分享
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters
Name:AutoShareServer
Type:DWORD
Value:0
--------------------------------------------------------
禁止ADMIN$ 預設共享
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters
Name:AutoShareWks
Type:REG_DWORD
Value:0x0
--------------------------------------------------------
  * 附註: 強烈建議別分享 c$, 因為帳號/密碼等的資料可能會被偷走, 而且還有可能被放入開機自動執行的批次檔, 或 Windows 資料夾裡被放入有風險的執行檔.


 ● 8. 建議可以"停用"掉下列的服務,提高安全性和系統效率. (如果該服務沒有在被使用的話)
--------------------------------------------------------
Computer Browser 維護網絡上計算機的最新列表以及提供這個列表
Routing and Remote Access在局域網以及廣域網環境中為企業提供路由服務
Removable storage 管理可移動媒體、驅動程序和庫
Remote Registry Service 允許遠程註冊表操作
Print Spooler 將文件加載到內存中以便以後打印。要用打印機的朋友不能禁用這項
Distributed Link Tracking Client 當文件在網絡域的NTFS卷中移動時發送通知
Alerter 通知選定的用戶和計算機管理警報
Error Reporting Service 收集、存儲和向Microsoft報告異常應用程序
Messenger 傳輸客戶端和服務器之間的NET SEND和警報器服務消息
Telnet 允許遠程用戶登錄到此計算機並運行程序
DHCP (主機應該幾乎沒有人在用 DHCP 取得 ip 的吧).


 ● 9. 避免使用 "共用(通用)" 的密碼, 例如: 我們常用的那幾組.
--------------------------------------------------------
因為, 如果密碼是通用的, 一台主機被攻破(或被登入)後, 其他台主機就 "很容易" 被攻破.


對於, 已被入侵過的, 重要的主機, 建議找時間重灌, 不重要的 + 上面的案子(系統) 太多的話, 建議把封包監控+防毒+防火牆的規則設好.

當然, 定時對主機做 windows update, 是一定的, 就不列了.

2011年11月19日 星期六

ipsec 相關工具與指令研究

IPsec 主要可提供兩種功能:認證功能(Authentication)和保密功能(Confidentiality), IPsec 功能之一也可以拿來當作主機的防火牆, 避免主機中毒或被攻擊, 或在中毒後, 避免去攻擊別人. 聽說, ipsec 的設定值在 Windows 裡不能直接匯入/匯出, 所以如果我們用指令, 似乎會比較方便一點, 主機重灌後, 也可以直接套用到之前的設定值. 可以研究了一下下, 這些相關文章:


 ● IP Security(IPsec)是針對位於網路層的Internet Protocol所提出的安全性協定。
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄http://structedtext.appspot.com/csec/ipsec.html



 ● 建立 IPsec 原則以限制連接埠
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://technet.microsoft.com/zh-tw/library/cc736995(WS.10).aspx

 step 1.從 [開始] 選單,先依序指向 [所有程式] 和 [ 系統管理工具],然後選取 [本機安全性設定值]。
 step 2.在 [本機安全性設定] 對話方塊中,按一下 [在本機電腦的 IP 安全性原則]。右邊的窗格會顯示預設的 Windows Server 2003 原則。
 step 3.在右邊的窗格上按一下滑鼠右鍵,然後按一下 [管理 IP 篩選器清單和篩選器動作]。
 step 4.於 [管理 IP 篩選器清單和篩選器動作] 對話方塊中,在 [管理 IP 篩選器清單] 索引標籤上,按一下 [新增]。
 step 5.在 [IP 篩選器清單] 對話方塊中的 [名稱] 方塊內,輸入您的篩選器清單名稱 (例如:輸入 HTTP)。若您想要的話,也可輸入相關的描述。此為套用到所有的輸入 HTTP 連線的篩選器清單。
 step 6.按一下 [新增]。出現 [IP 篩選器精靈]。建立篩選器清單...





 ● 如何設定 RPC 使用特定連接埠,以及如何使用 IPsec 以協助保護這些連接埠
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://support.microsoft.com/kb/908472/zh-tw

指派 IPsec 原則常用的3個參數 -x, -y, -o
*注意 本節中的命令會立即生效。

使用下列命令指派原則:
%IPSECTOOL% -w REG -p "Block RPC Ports" –x

注意 如果要立即取消指派原則,請使用下列命令:
%IPSECTOOL% -w REG -p "Block RPC Ports" –y

注意 如果要從登錄中刪除原則,請使用下列命令:
%IPSECTOOL% -w REG -p "Block RPC Ports" -o

* 您必須重新啟動主機,變更才會生效。



 ● ipseccmd 的用法….這東西蠻好用的耶^^
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://blog.pigbaby.com/?p=180


 ● ipseccmd 範例
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://forum.slime.com.tw/thread88684.html


 ● 加固基於Windows 2003平臺的WEB伺服器
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://forum.icst.org.tw/phpbb/viewtopic.php?t=8239


 ● 使用批处理启用或禁用端口
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://oce.blog.163.com/blog/static/1141177200802145013597/



 ● 如何使用 IPSec 來封鎖特定網路通訊協定和連接埠
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://support.microsoft.com/kb/813878/zh-tw


 ● 利用IPSec建立內部防火牆以提升資料庫安全
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://adalf0722.blogspot.com/2011/10/ipsec.html


 ● Internet Protocol Security (IPSec) policies 說明/用法
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ipsecmd.mspx?mfr=true




一般公司都會有外部防火牆來防止駭客直接入侵,應該還需要內部防火牆,來防內賊的,一般無內部防火牆的情況下,只要知道資料庫的IP、帳號及密碼,在內網就能夠去取得資料庫的資料,如果使用IPSec來建立內部防火牆,就能設定IP白名單,僅允許白名單上的伺服器可連接資料庫,也可以減少主機被內部的PC(或NB) 攻破的機率,也可以減少中毒(或被入侵)後對別台主機的影響.

2011年11月18日 星期五

如何記錄連線進來的程式以及相關資訊(Port Log)?

方案有2,
 ● 方案1: 安裝有 traffic (或 connection) log 功能的防火牆.
 ● 方案2: 使用 portRporter + Port Reporter Parser.
資料來源: http://support.microsoft.com/kb/837243/zh-tw


 ● 工具介紹
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
Port Reporter 所記錄的資訊,來追蹤連接埠的使用狀況,並疑難排解特定問題。Port Reporter 工具所記錄的資訊對於維護安全性可能也很有幫助。

Port Reporter Parser 工具是 Port Reporter 記錄檔的記錄剖析器。 Port Reporter Parser 中有許多功能,可以協助分析 Port Reporter 記錄檔。



 ● 安裝 Port Reporter 服務
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 ◆ 執行安裝程式 (Pr-Setup.exe) 來安裝 Port Reporter
 ◆ 設定和啟動 Port Reporter 服務
如果要確認 Port Reporter 服務是否安裝成功並啟動服務,請依照下列步驟執行:
  1. 按一下 [開始],用滑鼠右鍵按一下 [我的電腦],再按一下 [管理]。
  2. 展開 [服務及應用程式],再展開 [服務]。
  3. 在右邊窗格中,確認是否列出 Port Reporter 服務。
  4. 如果要啟動服務,請按兩下服務名稱,再按一下以選取 [啟動] 按鈕。按一下 [確定]。

 * 附註: Port Reporter 服務的啟動類型是設定為使用 [手動] 設定。如果您希望此服務可以在 Windows 啟動時自動啟動,請將啟動類型設定為 [自動]。




 ● 安裝 Port Reporter 服務
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
Log 記錄檔的位置
依預設,Port Reporter 工具會嘗試在下列資料夾中建立記錄檔:
%systemroot%\System32\LogFiles\PortReporter
如果這個資料夾不存在,系統就會為您建立資料夾。您可以使用 [Port Reporter] 服務對話方塊中 [一般] 索引標籤上所指定的啟動參數,來設定記錄檔的位置。如果要指定記錄檔資料夾,請在 -ld 命令列選項後面加上您想要使用的資料夾名稱。請確認您在資料夾的名稱前後加上單引號 (')。例如,如果您指定下列啟動參數,Port Reporter 服務就會在啟動時,在 C:\Program Files\Port Reporter 資料夾中建立記錄檔:
-ld 'c:\program files\port reporter'



 ● Log 記錄檔的大小
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
Port Reporter 預設會持續寫入記錄檔,直到記錄檔達到 5 MB。記錄檔達到 5 MB 之後,就會建立新的記錄檔。如果要設定記錄檔的大小,請使用 -ls 命令列選項。您可以將大小指定在 1000 KB 到 102400 KB 之間。例如,如果您指定下列啟動參數,Port Reporter 服務就會在每次記錄檔達到 7000 KB 時,建立新的記錄檔:
-ls 7000

使用 ipsec 做 ip address 或 封包的過濾

Windows 內建有一個 IPSEC 的服務,ipsec 功能之一就是在設定主機的防火牆, 避免主機中毒或被攻擊, 或在中毒後, 避免去攻擊別人.

這次的需求是,
1. 限制 所有的 IP 網段都不可以連目的協定udp, Port 137+139(網芳),
2. 允許 10.10.x.x 網段, 才可以連目的協定udp, Port 137+139(網芳),

ipsec的設定很方便,由於都有 GUI 使用起來還滿方便,設定也很直覺,建立 rule 的方式如下:

Create IPSec Policy
Typically, a Windows Server 2003 gateway is not a member of a domain, so a local IPSec policy is created. If the Windows Server 2003 gateway is a member of a domain that has IPSec policy applied to all members of the domain by default, this prevents the Windows Server 2003 gateway from having a local IPSec policy. In this case, you can create an organizational unit in Active Directory, make the Windows Server 2003 gateway a member of this organizational unit, and assign the IPSec policy to the Group Policy object (GPO) of the organizational unit. For more information, see the "Creating, modifying, and assigning IPSec policies" section of Windows Server 2003 online Help.
Click Start, click Run, and then type secpol.msc to start the IP Security Policy Management snap-in.
Right-click IP Security Policies on Local Computer, and then click Create IP Security Policy.
Click Next, and then type a name for your policy (for example, IPSec Tunnel with non-Microsoft Gateway). Click Next.

Note You can also type information in the Description box.
Click to clear the Activate the default response rule check box, and then click Next.
Click Finish (leave the Edit check box selected).
Note The IPSec policy is created with default settings for the IKE main mode. The IPSec tunnel is made up of two rules. Each rule specifies a tunnel endpoint. Because there are two tunnel endpoints, there are two rules. The filters in each rule must represent the source and destination IP addresses in IP packets that are sent to that rule's tunnel endpoint.



Build a Filter List from NetA to NetB
In the new policy properties, click to clear the Use Add Wizard check box, and then click Add to create a new rule.
Click the IP Filter List tab, and then click Add.
Type an appropriate name for the filter list, click to clear the Use Add Wizard check box, and then click Add.
In the Source address box, click A specific IP Subnet, and then type the IP Address and Subnet maskto for NetA.
In the Destination address box, click A specific IP Subnet, and then type the IP Address and Subnet mask for NetB.
Click to clear the Mirrored check box.
Click the Protocol tab. Make sure that the protocol type is set to Any, because IPSec tunnels do not support protocol-specific or port-specific filters.
If you want to type a description for your filter, click the Description tab. It is generally a good idea to give the filter the same name that you used for the filter list. The filter name appears in the IPSec monitor when the tunnel is active.
Click OK.


Build a Filter List from NetB to NetA
Click the IP Filter List tab, and then click Add.
Type an appropriate name for the filter list, click to clear the Use Add Wizard check box, and then click Add.
In the Source address box, click A specific IP Subnet, and then type the IP Address and Subnet mask for NetB.
In the Destination address box, click A specific IP Subnet, and then type the IP Address and Subnet mask for NetA.
Click to clear the Mirrored check box.
If you want to type a description for your filter, click the Description tab.
Click OK.


Configure a Rule for a NetA-to-NetB Tunnel
Click the IP Filter List tab, and then click to select the filter list that you created.
Click the Tunnel Setting tab, click The tunnel endpoint is specified by this IP Address box, and then type 3rdextip (where 3rdextip is the IP address that is assigned to the non-Microsoft gateway external network adapter).
Click the Connection Type tab, click All network connections (or click Local area network (LAN) if WIN2003extIP is not an ISDN, PPP, or direct-connect serial connection).
Click the Filter Action tab, click to clear the Use Add Wizard check box, and then click Add to create a new filter action because the default actions allow incoming traffic in clear text.
Keep the Negotiate security option enabled, and then click to clear the Accept unsecured communication, but always respond using IPSec check box. You must do this for secure operation.

Note None of the check boxes at the bottom of the Filter Action dialog box are selected as an initial configuration for a filter action that applies to tunnel rules. Only the Use session key perfect forward secrecy (PFS) check box is a valid setting for tunnels if the other end of the tunnel is also configured to use PFS.
Click Add, and keep the Integrity and encryption option selected (or you can select the Custom (for expert users) option if you want to define specific algorithms and session key lifetimes). Encapsulating Security Payload (ESP) is one of the two IPSec protocols.
Click OK. Click the General tab, type a name for the new filter action (for example, IPSec tunnel: ESP DES/MD5), and then click OK.
Click to select the filter action that you just created.
Click the Authentication Methods tab, configure the authentication method that you want (use preshared key for testing, and otherwise use certificates). Kerberos is technically possible if both ends of the tunnel are in trusted domains, and each trusted domain's IP address (IP address of a domain controller) is reachable on the network by both ends of the tunnel during IKE negotiation of the tunnel (before it is established). But this is rare.
Click Close.


Configure a Rule for a NetB-to-NetA Tunnel
In IPSec policy properties, click Add to create a new rule.
Click the IP Filter List tab, click to select the filter list that you created (from NetB to NetA).
Click the Tunnel Setting tab, click The tunnel endpoint is specified by this IP Address box, and then type WIN2003extIP (where WIN2003extIP is the IP address that is assigned to the Windows Server 2003 gateway external network adapter).
Click the Connection Type tab, click All network connections (or click Local area network (LAN) if WIN2003extIP is not an ISDN, PPP, or direct-connect serial connection). Any outbound traffic on the interface type that matches the filters tries to be tunneled to the tunnel endpoint that is specified in the rule. Inbound traffic that matches the filters is discarded because it must be received secured by an IPSec tunnel.
Click the Filter Action tab, and then click to select the filter action that you created.
Click the Authentication Methods tab, and then configure the same method that you used in the first rule (the same method must be used in both rules).
Click OK, make sure both rules that you created are enabled in your policy, and then click OK again.


資料來源:
http://support.microsoft.com/kb/816514/en-us#21

遠端桌面(Terminal Client)登入紀錄的簡易方法

1. 在Serve上建立一個.vbs的檔案,
並把該檔案建立捷徑, 把捷徑放置在 開始 - 程式集 - "啟動" 的路徑內
tslog.vbs 的檔案內容如下:
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run chr(34) & ".\tslog.bat" & Chr(34), 0
Set WshShell = Nothing


2. 建立一個批次檔,命名為 tslog.bat, 跟 tslog.vbs 放在一起.
檔案內容如下:
echo off
echo ********************************************************* >>c:\tslog.log
echo 時間:%date% %time% >>c:\tslog.log
echo 遠端電腦: %CLIENTNAME% >>c:\tslog.log
echo 登入網域: %USERDOMAIN% >>c:\tslog.log
echo 登入帳號: %USERNAME% >>c:\tslog.log
echo 遠端連線狀況 >>c:\tslog.log
netstat -n -p tcp | find ":3389" >>c:\tslog.log
echo ********************************************************************* >>c:\tslog.log


3. 重新登入此伺服器,即可以在 c:\tslog.log的檔案裡看到以下資訊:
(每次登入的紀錄都會被紀錄在前一次登入的下一行, 以*行隔開)

*********************************************************
時間:2011/XX/XX 星期五 11:11:57.37
遠端電腦: XX-XX-PC
登入網域: XX-SERVER
登入帳號: Administrator
遠端連線狀況
TCP xxx.xxx.xxx.xxx:3389 x.x.x.x:2421 ESTABLISHED
*********************************************************************

2011年11月17日 星期四

[HTML].DOCTYPE tag 的差別

設計師給的 UI(上圖) 與套出來的 UI(下圖):




研究後是因為 !DOCTYPE tag 忘了加, 造成的跑版,
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

結論: 下次畫面跑版, 先來檢查看看, 是不是 tag 忘了加.

經測試發現, 我猜測可能大部份的情況下, 應該都要加到 html tag 前, 如果沒有加的話, 會套用到 "user agent stylesheet" (browser 預設的 style).

2011年11月16日 星期三

寫多檔上傳的AP的劇本

寫多檔上傳的AP, 這個還滿常見的, 像是 gmail 裡, 可以在form 沒有submit 的情況下, 上傳很多的附件檔案.

我想到寫法, 有2種:
方案 1. 使用 tokenID 與 temp table 來暫存資料, 會多2個步驟, 但可以適用於比較多的情況.
方案 2. 不使用 tokenID, 適用於比較簡單的情況, user 在上傳檔案後, 就把實體檔案名稱暫存到 user 正在輸入的form 裡的一個 hidden 欄位裡.

名詞解釋: tokenID 就是到 bank 排隊時抽的號碼牌, 每個人都會抽到不同的流水號.


這次要詳細介紹的是方案1裡的細部流程.
下面的劇本, 總共用到
table 1: 流水號專用table.
table 2: 資料主檔table.
table 3: 附件temp table.
table 4: 附件主檔table.

1. 產生 Form, 並抽一張號碼牌 (tokenID).
1.1 是 "新增" 的情況下, 先顯示一個 html form 給 user 做輸入,
1.2 是 "修改" 的情況下. 要先把附件, 從 附件主檔table copy 資料到 附件temp table 裡.

2. 在 form 裡, 放一個 flash object, 讓 user 可以選取用戶端檔案,

3. 在 user 選完檔案時, 透過 flash 先存取到用戶端檔案名稱, 再透過 javascript, 動態產生一行附件的資訊(包括 附件在用的 fileid)在 form 的 UI 中.

4. 在 flash 上傳中, 不斷地更新目前上傳的進度. (ex: 上傳了 ??%) 在畫面上, 或使用 progress bar.

5. 在 flash 上傳完成後, 更新附件的資訊為 "已完成" 在 form 的 UI 中. 如果是選擇方案2的話, 就是在這一個步驟, 把附件的流水號, 或是實體檔名 queue 在 hidden 欄位即可完成多檔上傳.

6. 在這一個步驟, 使用ajax 的方式, 把一開始取得到的 tokenID 和檔案資訊(包括 附件在用的 fileid), 存入 附件temp table.

7. user 按下 "新增存檔" 或 "確認編輯" 的 submit button.

8. 把 user 所填的內容, insert (或 update) 進主檔, 並取得主檔資料的流水號(identity).

9. 把透過 flash object 上傳到 附件temp folder 裡的實體檔案, 搬家到系統專門放附件的資料夾裡.

10. 透過 tokenID(號碼牌) 到 附件temp table 裡, 把 user 剛輸入的附件的資訊(例如:標題,或是否公開...等欄位)再更新回 附件temp table 中.

11. user 有 "新增" 附件的時候, 就透過 tokenID(號碼牌) 取出 附件temp table 裡的資料, 再加上 主檔資料的流水號(identity) 後, insert 回去附件主題 table, 如果是 "編修" 即有附件的狀態, 就是用 update 的方式, 把 temp table 裡的值, update 回去 附件 table.

12. 刪掉 temp table 裡的同一個 tokenID 的資料.

13. 完成.


如果, 要參考看看相關的 table schema 或 code, 可以與我聯絡.

附註: 我使用的 flash 上傳元件, 是 swfupload, google 一下 swfupload 就可以看到很多相關教學與範例.

[SQL].A table 的部份欄位 update 到 B table

假設, 我有一堆的欄位內容, 要從 A table 更新到 B table, 有沒有可能一個 SQL 的句子就搞定?


參考看看:
update b
set b.title = a.title
from demo_a_tablename a
inner join demo_b_tablename b on b.id=33
where a.id = 5925


很怕會下錯, 改到一堆奇怪的東西
所以, 要先用 select 一下做測試:
select b.*
from demo_a_tablename a
inner join demo_b_tablename b on b.id=33
where a.id = 5925

應避免使用全域變數(Global variable)

全域變數(Global variable), 是很好用, 避免使用全域變數從軟體工程的角度來看, 應該是可以降低這些底層的副程式們的耦合力(Coupling), 有助於提升副程式們在其他的個案下被重用(Reuse)的機會.

舉例: 假設 database 的 adodb.connection 是一個全域變數, 所以我在寫副程式時, 就直接存取 conn, 副程式名稱: getFieldFromTable(...)
假設, 現在有一個案子, 要同時使用了2個 connection, 這個副程式馬上就不能用了, 因為他固定存取 conn 這一個 connection.

結論1: 建議把 connection 當作參數傳進副程式裡.
結論2: 就如同 session 一樣, 只在盡量是在主程式裡使用 全域變數, 盡量在底層的副程式裡少用 全域變數 .

有一個情況, 假設您的畫面裡, 有超級多的欄位(可能20~50個欄位), 要一一把參數放到副程式裡會很辛苦, 建議您自訂一個物件(class 或 Xml Dom object) 來當作副程式間參數傳遞用, 以避免呼叫一個副程式時, 要打很多字(或很多欄名,變數名).


附註: 這個觀念, 並不適用於所有的個案與情況, 我所說的, 所寫的也不一定正確, 僅供參考.


內聚力(Cohesion)與耦合力(Coupling)似乎已經是個系統設計的必考題,也是很基本的概念。但是在實際在系統設計上卻常被遺忘與忽略,如果在設計複雜與高度擴充的系統時,保持這樣的理念將會提高系統的維護與重用性 (Reusable)。這個原則是相當重要的參考依據,身為系統設計與開發人員,銘記在心是必要的。

2011年11月11日 星期五

應該避免在副程式裡存取session值,而是透過主程式存取

個案:
-----------------------------
之前寫的程式, 在 IE 瀏覽器裡做開發, 發現 IE 裡的 flash 在呼叫外部的程式(例如: service.asp)時, 可以直接 access 到 browser 已取得的 session, 但改用 chrome(or firefox)時, 安全性比較高, 不能共用, 寫在外部的程式(service.asp) 裡, 有一些副程式直接存取 session, 在 chrome(or firefox)的環境下, 無法存取到正確的 session, 所以要修改成從 request 來取得. 所以之前寫的副程式們, 要重新被改寫.

如果, 一開始寫的副程式們, 在取得 session 這一段, 是透過主程式來取得 session, 從軟體工程的角度來看, 應該是可以降低這些底層的副程式們的耦合力(Coupling), 有助於提升副程式們在其他的個案下被重用(Reuse)的機會.

內聚力(Cohesion)與耦合力(Coupling)似乎已經是個系統設計的必考題,也是很基本的概念。但是在實際在系統設計上卻常被遺忘與忽略,如果在設計複雜與高度擴充的系統時,保持這樣的理念將會提高系統的維護與重用性 (Reusable)。這個原則是相當重要的參考依據,身為系統設計與開發人員,銘記在心是必要的。

結論: 應該避免在副程式裡存取session值, 而是透過主程式存取. (以上為個人觀點, 不一定正確.)


相關文章參考看看:
http://www.slidefinder.net/%E7%B5%90/%E7%B5%90%E6%A7%8B%E5%8C%96%E6%8A%80%E8%A1%93/11594726/p2

google keyword: 耦合力



舊的 code:
----------------------
sub doUplaod()
  '// 這時候直接存在 sub 裡 access session 值,
  upload_path=session("uploadPath") + "/" + filename
end sub



建議的 code:
----------------------
sub doUplaod(uploadPath)
  '// 這時候直接存在 sub 裡 access session 值,
  upload_path=uploadPath + "/" + filename
end sub

* 說明: 參數 uploadPath 不是透過 session 取得,
優點: sub 就可以被沒有 session 的副程式來使用, 只要把 uploadPath 放到 sub 的 參數即可.
缺點: sub 要多傳一個參數進去, 會比較麻煩一點點.


* 附註: 把 database 的 connection object 寫成 global 有時候也會很麻煩, 因為您寫的舊的程式碼變成必需固定有一個global 的 connection object, 當您的 app 需要處理2個 connection 時, 之前寫的程式就會不能直接使用, 所以建議您在寫副程式時, 要把 connection 當成參數傳進去, 在"某些" 情況下會比較好.

網頁裡的 swf object 存取外部URL時,ie與chrome 安全性上的差別

在網頁 test1.asp 的程式碼裡, 寫入
session("test")="chrome"

在網頁 service.asp 的程式碼裡, 寫入
session("test")= session("test") & "-test"

接著透過 swf 呼叫 service.asp, 在 IE 6.0 的環境下,
swf 與瀏覽器使用同一個 session, 所以 service.asp 傳回的結果:
chrome-test

但是在 chrome (或 firefox) 的環境下執行,
swf 與瀏覽器, 不是使用同一個 session, 所以 service.asp 傳回的結果:
-test

結論: chrome 與 firefox 安全性比較高, 您在使用 swf 呼叫外部的 URL 時, 需要把相關的參數, 一併傳給 service.asp.

2011年11月7日 星期一

常用的 javascript function

在研究 htmlEditor 時, 先到 google blog, 看看 google 怎麼去處理他的 htmlEditor(Rich Editor), 在 trace javascript 時, 看到別人的常用function, 也許可以參考看看別人的寫法.

/**
* Author: Chris Wetherell
* BrowserDetector (object)
*
* A class for detecting version 5 browsers by the Javascript objects
* they support and not their user agent strings (which can be
* spoofed).
*
* Warning: Though slow to develop, browsers may begin to add
* DOM support in later versions which might require changes to this
* file.
*
* Warning: No one lives forever. Presumably.
*
* Typical usage:
* Detect = new BrowserDetector();
* if (Detect.IE()) //IE-only code...
*/
function BrowserDetector()
{

//IE 4+
this.IE = function()
{
try {
return this.Run(document.all && !document.contains) != false;
} catch (e) {
/* IE 5.01 doesn't support the 'contains' object and
fails the first test */
if (document.all) return true;
return false;
}
}

//IE 5.5+
this.IE_5_5_newer = function()
{
try {
return (this.Run(this.IE() && Array.prototype.pop && !this.OPERA()) !=
false);
} catch (e) {return false;}
}

//IE 5, Macintosh
this.IE_5_Mac = function()
{
try {
return (true == undefined);
} catch (e) {
return (document.all &&
document.getElementById &&
!document.mimeType &&
!this.OPERA()) != false;
}
}

//Opera 7+
this.OPERA = function()
{
try {
return this.Run(window.opera) != false;
} catch (e) {return false;}
}

//Gecko, actually Mozilla 1.2+
this.MOZILLA = function()
{
try {
return this.Run(
document.implementation
&& document.implementation.createDocument
&& !document.contains
&& !this.OPERA()
) != false;
} catch (e) {return false;}
}

//Safari
this.SAFARI = function()
{
try {
return this.Run(
document.implementation
&& document.implementation.createDocument
&& document.contains
) != false;
} catch (e) {return false;}
}

//Any browser which supports the W3C DOM
this.DOM = function()
{
return (document.getElementById);
}

this.Run = function(test)
{
if (test == undefined) {
return false;
} else {
return test;
}
}

// This uses useragent for finer detection. If people spoof it, it's their
// own fault when things break
this.geckoVersion = function() {
var matches = navigator.userAgent.match(/Gecko\/(\d*)/);
if (matches && matches.length > 1) {
return matches[1];
}

return null;
}
}


// Commonly-used functions, reduced.

function d(s) {return document.getElementById(s);}
function dE(o,s) {return o.getElementsByTagName(s);}

/**
* toggleDisplay()
*
* Will toggle the display property of the style object for any
* DOM element or object that supports style as a property.
*
* Warning: This'll wreak havoc if applied to <TR> elements. Those
* babies got different types "table-row" | "block" dependant on
* what browser's being used.
*
* Warning: Written in Texas. Yeehaw.
*
* Typical usage:
* toggleDisplay(document.getElementById("foo"));
*/
function toggleDisplay(o) {
var display = getStyle(o, 'display');

if (o.style) {
o.style.display =
(display != 'none') ? 'none' : getDisplayStyleByTagName(o);
}
}


function getDisplayStyleByTagName(o) {
var n = o.nodeName.toLowerCase();
return (n == 'span' ||
n == 'img' ||
n == 'a') ? 'inline' : 'block';
}


/**
* hideElement()
*
* Hides an element from view.
*
* Typical usage:
* hideElement(getElement("the-id-of-the-element"));
*/
function hideElement(o) {
if (o && o.style) o.style.display = 'none';
}


/**
* showElement()
*
* Shows an element that was hidden from view.
*
* Typical usage:
* showElement(getElement("the-id-of-the-element"));
*/
function showElement(o) {
if (o && o.style) o.style.display = getDisplayStyleByTagName(o);
}


/**
* getElement()
*
* Returns an element by its ID or shows an alert if it can't be found.
*
* Typical usage:
* getElement("the-id-of-the-element");
*/
function getElement(id) {
var e = d(id);
if (!e) {
alert('Cannot get element: ' + id);
}
return e;
}

/**
* setInnerHTML()
*
* Sets the innerHTML of an element or shows an alert if can't be set.
*
* Typical usage:
* setInnerHTML("the-id-of-the-element");
*/
function setInnerHTML(id, html) {
try {
getElement(id).innerHTML = html;
} catch (ex) {
alert('Cannot set inner HTML: ' + id);
}
}


/**
* setCssStyle()
*
* Sets the style of an element by its id or shows an alert if can't be set.
*
* Typical usage:
* setCssStyle("the-id-of-the-element", "display", "block");
*/
function setCssStyle(id, name, value) {
try {
getElement(id).style[name] = value;
} catch (ex) {
alert('Cannot set style: ' + id);
}
}


/**
* getStyle()
*
* Gets the computed style of any object.
*
* WARNING: Produces unexpected results in Safari. To achieve best
* results, explicitly set the style property for that browser when the
* element is rendered.
*
* Typical usage:
* getStyle(object, "display");
*/
function getStyle(el, style) {
if (!document.getElementById || !el) return;

if (document.defaultView
&& document.defaultView.getComputedStyle) {
return document.defaultView.
getComputedStyle(el, '').getPropertyValue(style);
} else if (el.currentStyle) {
return el.currentStyle[style];
} else {
return el.style.display;
}
}


/**
* getStyleAttribute()
*
* Returns the style attribute of the specified node.
*/
function getStyleAttribute(node) {
if (Detect.IE()) {
return node.getAttribute('style').value;
} else {
return node.getAttribute('style');
}
}


/*
* showProps()
*
* Displays all the properties for a given element
*/
function showProps(o) {
var s = '';
for (var p in o) {
s += p + ': '+ o[p] + '\n<br />';
}
document.write(s);
}


function setIFrameEvent(iframe, eventName, func) {
if (document.all) {
eval('getIFrameDocument(iframe).on' + eventName + ' = func;');
} else {
iframe.contentWindow.addEventListener(eventName, func, true);
}
}


function setIFrameBody(iframe, strStyle, innerHtml) {
if (!innerHtml) innerHtml = '';
if (innerHtml == '' && Detect.IE()) {
innerHtml = '<div></div>';
}
var doc = getIFrameDocument(iframe);
doc.open();
doc.write('<head></head><body style="' + strStyle + '">' +
innerHtml + '</body>');
doc.close();
}


function getIFrameDocument(iframe) {
if (Detect.IE()) {
return iframe.document;
} else {
return iframe.contentDocument;
}
}


function getIFrame(strId) {
if (Detect.IE()) {
return document.frames[strId];
} else {
return document.getElementById(strId);
}
}


function createElementandAppend(nodeName, strId, appendTo) {
var el = document.createElement(nodeName);
el.setAttribute('id', strId);
if (appendTo) {
appendTo.appendChild(el);
} else {
document.body.appendChild(el);
}
return el;
}


function createElementandInsertBefore(nodeName, strId, appendTo, sibling) {
var el = document.createElement(nodeName);
el.setAttribute('id', strId);
if (appendTo) {
appendTo.insertBefore(el, sibling);
} else {
document.body.insertBefore(el, sibling);
}
return el;
}


/**
* getXY()
*
* Returns the position of any element as an object.
*
* Typical usage:
* var pos = getXY(object);
* alert(pos.x + " " +pos.y);
*/
function getXY(el) {
var x = el.offsetLeft;
var y = el.offsetTop;
if (el.offsetParent != null) {
var pos = getXY(el.offsetParent);
x += pos.x;
y += pos.y;
}
return {x: x, y: y};
}


// The following 3 functions are taken from common.js
function hasClass(el, cl) {
if (el == null || el.className == null) return false;
var classes = el.className.split(' ');
for (var i = 0; i < classes.length; i++) {
if (classes[i] == cl) {
return true;
}
}
return false;
}


// Add a class to element
function addClass(el, cl) {
if (hasClass(el, cl)) return;
el.className += ' ' + cl;
}


// Remove a class from an element
function removeClass(el, cl) {
if (el.className == null) return;
var classes = el.className.split(' ');
var result = [];
var changed = false;
for (var i = 0; i < classes.length; i++) {
if (classes[i] != cl) {
if (classes[i]) { result.push(classes[i]); }
} else {
changed = true;
}
}
if (changed) { el.className = result.join(' '); }
}


function toggleClass(el, cl) {
if (hasClass(el, cl)) {
removeClass(el, cl);
} else {
addClass(el, cl);
}
}



function surroundFrameSelection(frame, tagName) {
var win = frame.contentWindow;
surroundSelection(win, tagName);
}


function surroundSelection(win, tagName) {
if (Detect.IE()) {
surroundSelection_IE(win.document, tagName);
} else {
var doc = (win.contentDocument) ? win.contentDocument : document;
var el = doc.createElement(tagName);
surroundSelection_DOM(win, el);
}
}


function insertNodeAtSelection(win, tag, fragment) {
if (Detect.IE()) {
var doc = win.document;
var range = doc.selection.createRange();
insertNodeAtSelection_IE(doc, tag, fragment.innerHTML);
} else {
var doc = (win.contentDocument) ? win.contentDocument : document;
var el = doc.createElement(tag);
insertNodeAtSelection_DOM(win, el, fragment);
}
}


function insertNodeAtSelection_IE(doc, tag, html) {
try {
var range = doc.selection.createRange();
var startTag = '<' + tag + '>';
var endTag = '</' + tag + '>';
var replaceString = startTag + html + endTag;

var isCollapsed = range.text == '';
range.pasteHTML(replaceString);

if (!isCollapsed) {
// move selection to html contained within the surrounding node
range.moveToElementText(range.parentElement().childNodes[0]);
range.select();
}
} catch (e) {
RichEdit.addDebugMsg('insertNodeAtSelection_IE() failed for "' + tag + '"');
}
}


function surroundSelection_IE(doc, tag) {
try {
var range = doc.selection.createRange();
var html = range.htmlText;

// get rid of beginning newline
if (html.substring(0, 2) == '\r\n') html = html.substring(2, html.length);

// resolve IE's special DIV cases
html = replaceEmptyDIVsWithBRs(html);

insertNodeAtSelection_IE(doc, tag, html);
} catch (e) {
RichEdit.addDebugMsg('surroundSelection_IE() failed for "' + tag + '"');
}
}


function surroundSelection_DOM(win, tag) {
try {
var sel = win.getSelection();
var range = sel.getRangeAt(0);
insertNodeAtSelection_DOM(win, tag, range.cloneContents());
} catch (e) {
RichEdit.addDebugMsg('surroundSelection_DOM() failed for "' + tag + '"');
}
}


/*
* This function was taken from The Mozilla Organization's Midas demo. It has
* been modified. In the future we may instead be able to use the
* surroundContents() method of the range object, but a bug exists as of
* 7/6/2004 that prohibits our use of it in Mozilla.
* (http://bugzilla.mozilla.org/show_bug.cgi?id=135928)
*/
function insertNodeAtSelection_DOM(win, insertNode, html)
{
// get current selection
var sel = win.getSelection();

// get the first range of the selection
// (there's almost always only one range)
var range = sel.getRangeAt(0);

// insert specified HTML into the node passed by argument
insertNode.appendChild(html);

// deselect everything
sel.removeAllRanges();

// remove content of current selection from document
range.deleteContents();

// get location of current selection
var container = range.startContainer;
var pos = range.startOffset;

// make a new range for the new selection
range = document.createRange();

var afterNode;

if (container.nodeType == 3 && insertNode.nodeType == 3) {
// if we insert text in a textnode, do optimized insertion
container.insertData(pos, insertNode.nodeValue);

} else {
if (container.nodeType == 3) {
// when inserting into a textnode
// we create 2 new textnodes
// and put the insertNode in between

var textNode = container;
container = textNode.parentNode;
var text = textNode.nodeValue;

// text before the split
var textBefore = text.substr(0, pos);
// text after the split
var textAfter = text.substr(pos);

var beforeNode = document.createTextNode(textBefore);
var afterNode = document.createTextNode(textAfter);

// insert the 3 new nodes before the old one
container.insertBefore(afterNode, textNode);
container.insertBefore(insertNode, afterNode);
container.insertBefore(beforeNode, insertNode);

// remove the old node
container.removeChild(textNode);
} else {
// else simply insert the node
afterNode = container.childNodes[pos];
container.insertBefore(insertNode, afterNode);
}
}

// select the modified html
range.setEnd(insertNode, insertNode.childNodes.length);
range.setStart(insertNode, insertNode);
sel.addRange(range);
}


/*
* getRangeAsDocumentFragment()
*
* Returns an HTML Document fragment representing the contents of the
* supplied selection range.
*/
function getRangeAsDocumentFragment(range) {
try {
if (Detect.IE()) {
var el = document.createElement('span');
el.innerHTML = range.htmlText;
return el;
} else {
return range.cloneContents();
}
} catch (e) {
RichEdit.addDebugMsg('--getRangeAsDocumentFragment() failed');
return null;
}
}

function setKeysetByEvent(e) {
// IE delivers a different keyset per key event type. Additionally,
// 'keydown' is different than 'keypress' but only in terms of the
// ctrl + shift combination. Ugh. Safari is more consistent. Gecko is
// right on.

// set up the globally shared variables
currentCtrlKeyPressedFromEvent = isCtrlKeyPressed(e);
currentShiftKeyPressedFromEvent = isShiftKeyPressed(e);
currentKeyFromEvent = getKey(e);

IE_CTRL_SHIFT_KEYSET = IE_KEYSET;
if (Detect.IE() && e && e.type == 'keydown') IE_CTRL_SHIFT_KEYSET = false;

BACKSPACE = (currentKeyFromEvent == BACKSPACE_KEYCODE);
DELETE = (Detect.IE()) ?
(currentKeyFromEvent == DELETE_KEYCODE)
: (getEvent(e).keyCode == 46 && getEvent(e).charCode == 0);
ESCAPE = (currentKeyFromEvent == ESC_KEYCODE);
RETURN = (currentKeyFromEvent == ENTER_KEYCODE);
SPACE = (currentKeyFromEvent == SPACE_KEYCODE);
TAB = (currentKeyFromEvent == TAB_KEYCODE);

LEFT_ARROW = (currentKeyFromEvent == LEFT_KEYCODE);
RIGHT_ARROW = (currentKeyFromEvent == RIGHT_KEYCODE);
UP_ARROW = (currentKeyFromEvent == UP_KEYCODE);
DOWN_ARROW = (currentKeyFromEvent == DOWN_KEYCODE);

DIGIT = (!currentShiftKeyPressedFromEvent &&
((currentKeyFromEvent >= 48 && currentKeyFromEvent <= 57) ||
(currentKeyFromEvent >= 96 && currentKeyFromEvent <= 105)));// keypad

// Alphabets
ALPHA = false;
if (e.type == 'keypress') {
ALPHA = ((currentKeyFromEvent >= 65 && currentKeyFromEvent <= 90) ||
(currentKeyFromEvent >= 97 && currentKeyFromEvent <= 122));
} else {
ALPHA = (currentKeyFromEvent >= 65 && currentKeyFromEvent <= 90);
}

CTRL_SHFT_A = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(1, e) :
isKeyPressedWithCtrlShift(65, e);

CTRL_SHFT_B = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(2, e) :
isKeyPressedWithCtrlShift(66, e);

CTRL_SHFT_D = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(4, e) :
isKeyPressedWithCtrlShift(68, e);

CTRL_SHFT_L = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(12, e) :
isKeyPressedWithCtrlShift(76, e);

CTRL_SHFT_P = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(16, e) :
isKeyPressedWithCtrlShift(80, e);

CTRL_SHFT_S = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(19, e) :
isKeyPressedWithCtrlShift(83, e);

CTRL_SHFT_T = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(20, e) :
isKeyPressedWithCtrlShift(84, e);

CTRL_SHFT_U = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(21, e) :
isKeyPressedWithCtrlShift(85, e);

CTRL_SHFT_Z = (IE_CTRL_SHIFT_KEYSET) ?
isKeyPressedWithCtrlShift(26, e) :
isKeyPressedWithCtrlShift(90, e);

CTRL_B = (IE_KEYSET) ?
isKeyPressedWithCtrl(66, e) : isKeyPressedWithCtrl(98, e);

CTRL_D = (IE_KEYSET) ?
isKeyPressedWithCtrl(68, e) : isKeyPressedWithCtrl(100, e);

CTRL_G = (IE_KEYSET) ?
isKeyPressedWithCtrl(71, e) : isKeyPressedWithCtrl(103, e);

CTRL_I = (IE_KEYSET) ?
isKeyPressedWithCtrl(73, e) : isKeyPressedWithCtrl(105, e);

CTRL_L = (IE_KEYSET) ?
isKeyPressedWithCtrl(76, e) : isKeyPressedWithCtrl(108, e);

CTRL_P = (IE_KEYSET) ?
isKeyPressedWithCtrl(80, e) && !isShiftKeyPressed(e) :
isKeyPressedWithCtrl(112, e) && !isShiftKeyPressed(e);

CTRL_S = (IE_KEYSET) ?
isKeyPressedWithCtrl(83, e) : isKeyPressedWithCtrl(115, e);

CTRL_Y = (IE_KEYSET) ?
isKeyPressedWithCtrl(89, e) : isKeyPressedWithCtrl(121, e);

CTRL_Z = (IE_KEYSET) ?
isKeyPressedWithCtrl(90, e) : isKeyPressedWithCtrl(122, e);

/*
We see for 'keycode' of the pressed key if the event-type is keydown or
keyup, and 'charcode' if the event-type is keypress.
*/
if (e.type == 'keydown' || e.type == 'keyup') {
PUNCTUATION =
// ! @ # $ % ^ & * ( )
(currentKeyFromEvent >= 48 && currentKeyFromEvent <= 57
&& currentShiftKeyPressedFromEvent) ||
// ; : + = , < _ - . > / ? ~ `
(currentKeyFromEvent >= 186 && currentKeyFromEvent <= 192) ||
// { [ } ] \ | ' "
(currentKeyFromEvent >= 219 && currentKeyFromEvent <= 222);
} else {
// Kepress event
var str = null;
if (Detect.IE()) {
str = String.fromCharCode(currentKeyFromEvent);
} else if (!(e.event_ && e.event_.isChar)) {
str = String.fromCharCode(e.charCode);
} else {
PUNCTUATION = false;
}

if (str) {
PUNCTUATION = PUNCTUATIONS_STR.indexOf(str) != -1;
}
}
}

/**
* isCtrlShiftKeyPressed()
*
* Determine by char index whether a certain key's been pressed in conjunction
* with the CTRL and SHIFT keys.
*/
function isKeyPressedWithCtrlShift(num, e) {
var key = getKeyAfterCtrlAndShift(e);
if (key) return (key == num);
return false;
}

function isKeyPressedWithCtrl(num, e) {
var key = getKeyAfterCtrl(e);
if (key) return (key == num);
return false;
}

function isKeyPressedWithShift(num, e) {
var key = getKeyAfterShift(e);
if (key) return (key == num);
return false;
}

// The following functions help manage some differing browser event models and
// key detection.
function getKeyAfterCtrl(e) {
if (currentCtrlKeyPressedFromEvent) { return currentKeyFromEvent; }
return false;
}

function getKeyAfterShift(e) {
if (currentShiftKeyPressedFromEvent) { return currentKeyFromEvent; }
return false;
}

function getKeyAfterCtrlAndShift(e) {
if (currentCtrlKeyPressedFromEvent && currentShiftKeyPressedFromEvent) {
return currentKeyFromEvent;
}
return false;
}

function isCtrlKeyPressed(e) {
return getEvent(e).ctrlKey;
}

function isShiftKeyPressed(e) {
return getEvent(e).shiftKey;
}

function isAltKeyPressed(e) {
return getEvent(e).altKey;
}

function getKey(e) {
var key = getEvent(e).keyCode;
if (!key) key = getEvent(e).charCode;
return key;
}

function getEventSource(evt) {
if (Detect.IE()) {
return evt.srcElement;
} else {
return evt.target;
}
}

function getEvent(e) {
return (!e) ? event : e;
}

//------------------------------------------------------------------------
// browser detection
var agent = navigator.userAgent.toLowerCase();
var is_ie = (agent.indexOf('msie') != -1);
var is_konqueror = (agent.indexOf('konqueror') != -1);
var is_safari = (agent.indexOf('safari') != -1) || is_konqueror;
var is_nav = !is_ie && !is_safari && (agent.indexOf('mozilla') != -1);
var is_win = (agent.indexOf('win') != -1);
delete agent;


var BACKSPACE_KEYCODE = 8;
var COMMA_KEYCODE = 188; // ',' key
var DEBUG_KEYCODE = 68; // 'D' key
var DELETE_KEYCODE = 46;
var DOWN_KEYCODE = 40; // DOWN arrow key
var ENTER_KEYCODE = 13; // ENTER key
var ESC_KEYCODE = 27; // ESC key
var LEFT_KEYCODE = 37; // LEFT arrow key
var RIGHT_KEYCODE = 39; // RIGHT arrow key
var SPACE_KEYCODE = 32; // space bar
var TAB_KEYCODE = 9; // TAB key
var UP_KEYCODE = 38; // UP arrow key
var SHIFT_KEYCODE = 16;

//------------------------------------------------------------------------
// Assertions
// DEPRECATED: Use assert.js
//------------------------------------------------------------------------
/**
* DEPRECATED: Use assert.js
*/
function raise(msg) {
if (typeof Error != 'undefined') {
throw new Error(msg || 'Assertion Failed');
} else {
throw (msg);
}
}

/**
* DEPRECATED: Use assert.js
*
* Fail() is useful for marking logic paths that should
* not be reached. For example, if you have a class that uses
* ints for enums:
*
* MyClass.ENUM_FOO = 1;
* MyClass.ENUM_BAR = 2;
* MyClass.ENUM_BAZ = 3;
*
* And a switch statement elsewhere in your code that
* has cases for each of these enums, then you can
* "protect" your code as follows:
*
* switch(type) {
* case MyClass.ENUM_FOO: doFooThing(); break;
* case MyClass.ENUM_BAR: doBarThing(); break;
* case MyClass.ENUM_BAZ: doBazThing(); break;
* default:
* Fail("No enum in MyClass with value: " + type);
* }
*
* This way, if someone introduces a new value for this enum
* without noticing this switch statement, then the code will
* fail if the logic allows it to reach the switch with the
* new value, alerting the developer that he should add a
* case to the switch to handle the new value he has introduced.
*
* @param {string} opt_msg to display for failure
* DEFAULT: "Assertion failed".
*/
function Fail(opt_msg) {
if (opt_msg === undefined) opt_msg = 'Assertion failed';
if (IsDefined(DumpError)) DumpError(opt_msg + '\n');
raise(opt_msg);
}

/**
* DEPRECATED: Use assert.js
*
* Asserts that an expression is true (non-zero and non-null).
*
* Note that it is critical not to pass logic
* with side-effects as the expression for AssertTrue
* because if the assertions are removed by the
* JSCompiler, then the expression will be removed
* as well, in which case the side-effects will
* be lost. So instead of this:
*
* AssertTrue( criticalComputation() );
*
* Do this:
*
* var result = criticalComputation();
* AssertTrue(result);
*
* @param {anything} expression to evaluate.
* @param {string} opt_msg to display if the assertion fails.
*
*/
function AssertTrue(expression, opt_msg) {
if (!expression) {
if (opt_msg === undefined) opt_msg = 'Assertion failed';
Fail(opt_msg);
}
}

/**
* DEPRECATED: Use assert.js
*
* Asserts that two values are the same.
*
* @param {anything} val1
* @param {anything} val2
* @param {string} opt_msg to display if the assertion fails.
*/
function AssertEquals(val1, val2, opt_msg) {
if (val1 != val2) {
if (opt_msg === undefined) {
opt_msg = 'AssertEquals failed: <' + val1 + '> != <' + val2 + '>';
}
Fail(opt_msg);
}
}

/**
* DEPRECATED: Use assert.js
*
* Asserts that a value is of the provided type.
*
* AssertType(6, Number);
* AssertType("ijk", String);
* AssertType([], Array);
* AssertType({}, Object);
* AssertType(ICAL_Date.now(), ICAL_Date);
*
* @param {anything} value
* @param {constructor function} type
* @param {string} opt_msg to display if the assertion fails.
*/
function AssertType(value, type, opt_msg) {
// for backwards compatability only
if (typeof value == type) return;

if (value || value == '') {
try {
if (type == AssertTypeMap[typeof value] || value instanceof type) return;
} catch (e) { /* failure, type was an illegal argument to instanceof */ }
}
if (opt_msg === undefined) {
if (typeof type == 'function') {
var match = type.toString().match(/^\s*function\s+([^\s\{]+)/);
if (match) type = match[1];
}
opt_msg = 'AssertType failed: <' + value + '> not typeof '+ type;
}
Fail(opt_msg);
}

var AssertTypeMap = {
'string' : String,
'number' : Number,
'boolean' : Boolean
};

/**
* DEPRECATED: Use assert.js
*
* Asserts that the number of arguments to a
* function is num. For example:
*
* function myFunc(one, two, three) [
* AssertNumArgs(3);
* ...
* }
*
* myFunc(1, 2); // assertion fails!
*
* Note that AssertNumArgs does not take the function
* as an argument; it is simply used in the context
* of the function.
*
* @param {int} number of arguments expected.
* @param {string} opt_msg to display if the assertion fails.
*/
function AssertNumArgs(num, opt_msg) {
var caller = AssertNumArgs.caller; // This is not supported in safari 1.0
if (caller && caller.arguments.length != num) {
if (opt_msg === undefined) {
opt_msg = caller.name + ' expected ' + num + ' arguments '
+ ' but received ' + caller.arguments.length;
}
Fail(opt_msg);
}
}

//------------------------------------------------------------------------
// Cookies
//------------------------------------------------------------------------
var ILLEGAL_COOKIE_CHARS_RE = /[\s;]/;
/**
* Sets a cookie.
* The max_age can be -1 to set a session cookie. To expire cookies, use
* ExpireCookie() instead.
*
* @param name The cookie name.
* @param value The cookie value.
* @param opt_max_age The max age in seconds (from now). Use -1 to set a
* session cookie. If not provided, the default is -1 (i.e. set a session
* cookie).
* @param opt_path The path of the cookie, or null to not specify a path
* attribute (browser will use the full request path). If not provided, the
* default is '/' (i.e. path=/).
* @param opt_domain The domain of the cookie, or null to not specify a domain
* attribute (brower will use the full request host name). If not provided,
* the default is null (i.e. let browser use full request host name).
* @return Void.
*/
function SetCookie(name, value, opt_max_age, opt_path, opt_domain) {
value = '' + value;
AssertTrue((typeof name == 'string' &&
typeof value == 'string' &&
!name.match(ILLEGAL_COOKIE_CHARS_RE) &&
!value.match(ILLEGAL_COOKIE_CHARS_RE)),
'trying to set an invalid cookie');

if (!IsDefined(opt_max_age)) opt_max_age = -1;
if (!IsDefined(opt_path)) opt_path = '/';
if (!IsDefined(opt_domain)) opt_domain = null;

var domain_str = (opt_domain == null) ? '' : ';domain=' + opt_domain;
var path_str = (opt_path == null) ? '' : ';path=' + opt_path;

var expires_str;

// Case 1: Set a session cookie.
if (opt_max_age < 0) {
expires_str = '';

// Case 2: Expire the cookie.
// Note: We don't tell people about this option in the function doc because
// we prefer people to use ExpireCookie() to expire cookies.
} else if (opt_max_age == 0) {
// Note: Don't use Jan 1, 1970 for date because NS 4.76 will try to convert
// it to local time, and if the local time is before Jan 1, 1970, then the
// browser will ignore the Expires attribute altogether.
var pastDate = new Date(1970, 1 /*Feb*/, 1); // Feb 1, 1970
expires_str = ';expires=' + pastDate.toUTCString();

// Case 3: Set a persistent cookie.
} else {
var futureDate = new Date(Now() + opt_max_age * 1000);
expires_str = ';expires=' + futureDate.toUTCString();
}

document.cookie = name + '=' + value + domain_str + path_str + expires_str;
}

/** Returns the value for the first cookie with the given name
* @param name : string.
* @return a string or the empty string if no cookie found.
*/
function GetCookie(name) {
var nameeq = name + '=';
var cookie = String(document.cookie);
for (var pos = -1; (pos = cookie.indexOf(nameeq, pos + 1)) >= 0;) {
var i = pos;
// walk back along string skipping whitespace and looking for a ; before
// the name to make sure that we don't match cookies whose name contains
// the given name as a suffix.
while (--i >= 0) {
var ch = cookie.charAt(i);
if (ch == ';') {
i = -1; // indicate success
break;
} else if (' \t'.indexOf(ch) < 0) {
break;
}
}
if (-1 === i) { // first cookie in the string or we found a ;
var end = cookie.indexOf(';', pos);
if (end < 0) { end = cookie.length; }
return cookie.substring(pos + nameeq.length, end);
}
}
return '';
}


//------------------------------------------------------------------------
// Time
//------------------------------------------------------------------------
function Now() {
return (new Date()).getTime();
}

//------------------------------------------------------------------------
// Dynamic HTML/DOM utilities
//------------------------------------------------------------------------
// Gets a element by its id, may return null
function MaybeGetElement(win, id) {
return win.document.getElementById(id);
}

// Same as MaybeGetElement except that it throws an exception if it's null
function GetElement(win, id) {
var el = win.document.getElementById(id);
if (!el) {
DumpError('Element ' + id + ' not found.');
}
return el;
}

// Gets elements by its id/name
// IE treats getElementsByName as searching over ids, while Moz use names.
// so tags must have both id and name as the same string
function GetElements(win, id) {
return win.document.getElementsByName(id);
}

// Show/hide an element.
function ShowElement(el, show) {
el.style.display = show ? '' : 'none';
}

// Show/hide a block element.
// ShowElement() doesn't work if object has an initial class with display:none
function ShowBlockElement(el, show) {
el.style.display = show ? 'block' : 'none';
}

// Show/hide an inline element.
// ShowElement() doesn't work when an element starts off display:none.
function ShowInlineElement(el, show) {
el.style.display = show ? 'inline' : 'none';
}

// Append a new HTML element to a HTML node.
function AppendNewElement(win, parent, tag) {
var e = win.document.createElement(tag);
parent.appendChild(e);
return e;
}

// Create a new TR containing the given td's
function Tr(win, tds) {
var tr = win.document.createElement('TR');
for (var i = 0; i < tds.length; i++) {
tr.appendChild(tds[i]);
}
return tr;
}

// Create a new TD, with an optional colspan
function Td(win, opt_colspan) {
var td = win.document.createElement('TD');
if (opt_colspan) {
td.colSpan = opt_colspan;
}
return td;
}


// Check if an element has a given class
function HasClass(el, cl) {
if (el == null || el.className == null) return false;
var classes = el.className.split(' ');
for (var i = 0; i < classes.length; i++) {
if (classes[i] == cl) {
return true;
}
}
return false;
}

// Add a class to element
function AddClass(el, cl) {
if (HasClass(el, cl)) return;
el.className += ' ' + cl;
}

// Remove a class from an element
function RemoveClass(el, cl) {
if (el.className == null) return;
var classes = el.className.split(' ');
var result = [];
var changed = false;
for (var i = 0; i < classes.length; i++) {
if (classes[i] != cl) {
if (classes[i]) { result.push(classes[i]); }
} else {
changed = true;
}
}
if (changed) { el.className = result.join(' '); }
}

// Performs an in-order traversal of the tree rooted at the given node
// (excluding the root node) and returns an array of nodes that match the
// given selector. The selector must implement the method:
//
// boolean select(node);
//
// This method is a generalization of the DOM method "getElementsByTagName"
//
function GetElementsBySelector(root, selector) {
var nodes = [];
for (var child = root.firstChild; child; child = child.nextSibling) {
AddElementBySelector_(child, selector, nodes);
}
return nodes;
}

// Recursive helper for GetElemnetsBySelector()
function AddElementBySelector_(root, selector, nodes) {
// First test the parent
if (selector.select(root)) {
nodes.push(root);
}

// Then recurse through the children
for (var child = root.firstChild; child; child = child.nextSibling) {
AddElementBySelector_(child, selector, nodes);
}
}

//------------------------------------------------------------------------
// Window/screen utilities
// TODO: these should be renamed (e.g. GetWindowWidth to GetWindowInnerWidth
// and moved to geom.js)
//------------------------------------------------------------------------
// Get page offset of an element
function GetPageOffsetLeft(el) {
var x = el.offsetLeft;
if (el.offsetParent != null)
x += GetPageOffsetLeft(el.offsetParent);
return x;
}

// Get page offset of an element
function GetPageOffsetTop(el) {
var y = el.offsetTop;
if (el.offsetParent != null)
y += GetPageOffsetTop(el.offsetParent);
return y;
}

// Get page offset of an element
function GetPageOffset(el) {
var x = el.offsetLeft;
var y = el.offsetTop;
if (el.offsetParent != null) {
var pos = GetPageOffset(el.offsetParent);
x += pos.x;
y += pos.y;
}
return {x: x, y: y};
}

function GetPageOffsetRight(el) {
return GetPageOffsetLeft(el) + el.offsetWidth;
}

function GetPageOffsetBottom(el) {
return GetPageOffsetTop(el) + el.offsetHeight;
}

// Get the y position scroll offset.
function GetScrollTop(win) {
// all except Explorer
if ('pageYOffset' in win) {
return win.pageYOffset;
}
// Explorer 6 Strict Mode
else if ('documentElement' in win.document &&
'scrollTop' in win.document.documentElement) {
return win.document.documentElement.scrollTop;
}
// other Explorers
else if ('scrollTop' in win.document.body) {
return win.document.body.scrollTop;
}

return 0;
}

// Get the x position scroll offset.
function GetScrollLeft(win) {
// all except Explorer
if ('pageXOffset' in win) {
return win.pageXOffset;
}
// Explorer 6 Strict Mode
else if ('documentElement' in win.document &&
'scrollLeft' in win.document.documentElement) {
return win.document.documentElement.scrollLeft;
}
// other Explorers
else if ('scrollLeft' in win.document.body) {
return win.document.body.scrollLeft;
}

return 0;
}

//------------------------------------------------------------------------
// String utilities
//------------------------------------------------------------------------
// Do html escaping
var amp_re_ = /&/g;
var lt_re_ = /</g;
var gt_re_ = />/g;

// Convert text to HTML format. For efficiency, we just convert '&', '<', '>'
// characters.
// Note: Javascript >= 1.3 supports lambda expression in the replacement
// argument. But it's slower on IE.
// Note: we can also implement HtmlEscape by setting the value
// of a textnode and then reading the 'innerHTML' value, but that
// that turns out to be slower.
// Params: str: String to be escaped.
// Returns: The escaped string.
function HtmlEscape(str) {
if (!str) return '';
return str.replace(amp_re_, '&amp;').replace(lt_re_, '&lt;').
replace(gt_re_, '&gt;').replace(quote_re_, '&quot;');
}

/** converts html entities to plain text. It covers the most common named
* entities and numeric entities.
* It does not cover all named entities -- it covers &{lt,gt,amp,quot,nbsp}; but
* does not handle some of the more obscure ones like &{ndash,eacute};.
*/
function HtmlUnescape(str) {
if (!str) return '';
return str.
replace(/&#(\d+);/g,
function(_, n) { return String.fromCharCode(parseInt(n, 10)); }).
replace(/&#x([a-f0-9]+);/gi,
function(_, n) { return String.fromCharCode(parseInt(n, 16)); }).
replace(/&(\w+);/g, function(_, entity) {
entity = entity.toLowerCase();
return entity in HtmlUnescape.unesc ? HtmlUnescape.unesc[entity] : '?';
});
}
HtmlUnescape.unesc = { lt: '<', gt: '>', quot: '"', nbsp: ' ', amp: '&' };

// Escape double quote '"' characters in addition to '&', '<', '>' so that a
// string can be included in an HTML tag attribute value within double quotes.
// Params: str: String to be escaped.
// Returns: The escaped string.
var quote_re_ = /\"/g;

var JS_SPECIAL_RE_ = /[\'\\\r\n\b\"<>&]/g;

function JSEscOne_(s) {
if (!JSEscOne_.js_escs_) {
var escapes = {};
escapes['\\'] = '\\\\';
escapes['\''] = '\\047';
escapes['\n'] = '\\n';
escapes['\r'] = '\\r';
escapes['\b'] = '\\b';
escapes['\"'] = '\\042';
escapes['<'] = '\\074';
escapes['>'] = '\\076';
escapes['&'] = '\\046';

JSEscOne_.js_escs_ = escapes;
}

return JSEscOne_.js_escs_[s];
}

// converts multiple ws chars to a single space, and strips
// leading and trailing ws
var spc_re_ = /\s+/g;
var beg_spc_re_ = /^ /;
var end_spc_re_ = / $/;
function CollapseWhitespace(str) {
if (!str) return '';
return str.replace(spc_re_, ' ').replace(beg_spc_re_, '').
replace(end_spc_re_, '');
}

var newline_re_ = /\r?\n/g;
var spctab_re_ = /[ \t]+/g;
var nbsp_re_ = /\xa0/g;

function HtmlifyNewlines(str) {
if (!str) return '';
return str.replace(newline_re_, '<br>');
}

// URL encodes the string.
function UrlEncode(str) {
return encodeURIComponent(str);
}

function Trim(str) {
if (!str) return '';
return str.replace(/^\s+/, '').replace(/\s+$/, '');
}

function EndsWith(str, suffix) {
if (!str) return !suffix;
return (str.lastIndexOf(suffix) == (str.length - suffix.length));
}

// Check if a string is empty
function IsEmpty(str) {
return CollapseWhitespace(str) == '';
}

// Check if a character is a letter
function IsLetterOrDigit(ch) {
return ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9'));
}

// Check if a character is a space character
function IsSpace(ch) {
return (' \t\r\n'.indexOf(ch) >= 0);
}

//------------------------------------------------------------------------
// TextArea utilities
//------------------------------------------------------------------------

function SetCursorPos(win, textfield, pos) {
if (IsDefined(textfield.selectionEnd) &&
IsDefined(textfield.selectionStart)) {
// Mozilla directly supports this
textfield.selectionStart = pos;
textfield.selectionEnd = pos;

} else if (win.document.selection && textfield.createTextRange) {
// IE has textranges. A textfield's textrange encompasses the
// entire textfield's text by default
var sel = textfield.createTextRange();

sel.collapse(true);
sel.move('character', pos);
sel.select();
}
}

//------------------------------------------------------------------------
// Array utilities
//------------------------------------------------------------------------
// Find an item in an array, returns the key, or -1 if not found
function FindInArray(array, x) {
for (var i = 0; i < array.length; i++) {
if (array[i] == x) {
return i;
}
}
return -1;
}

// Inserts an item into an array, if it's not already in the array
function InsertArray(array, x) {
if (FindInArray(array, x) == -1) {
array[array.length] = x;
}
}

// Delete an element from an array
function DeleteArrayElement(array, x) {
var i = 0;
while (i < array.length && array[i] != x)
i++;
array.splice(i, 1);
}

function GetEventTarget(/*Event*/ ev) {
// Event is not a type in IE; IE uses Object for events
// AssertType(ev, Event, 'arg passed to GetEventTarget not an Event');
return ev.srcElement || ev.target;
}

//------------------------------------------------------------------------
// Misc
//------------------------------------------------------------------------
// Check if a value is defined
function IsDefined(value) {
return (typeof value) != 'undefined';
}

function GetKeyCode(event) {
var code;
if (event.keyCode) {
code = event.keyCode;
} else if (event.which) {
code = event.which;
}
return code;
}

// define a forid function to fetch a DOM node by id.
function forid_1(id) {
return document.getElementById(id);
}
function forid_2(id) {
return document.all[id];
}

/**
* Fetch an HtmlElement by id.
* DEPRECATED: use $ in dom.js
*/
var forid = document.getElementById ? forid_1 : forid_2;



function log(msg) {
/* a top level window is its own parent. Use != or else fails on IE with
* infinite loop.
*/
try {
if (window.parent != window && window.parent.log) {
window.parent.log(window.name + '::' + msg);
return;
}
} catch (e) {
// Error: uncaught exception: Permission denied to get property Window.log
}
var logPane = forid('log');
if (logPane) {
var logText = '<p class=logentry><span class=logdate>' + new Date() +
'</span><span class=logmsg>' + msg + '</span></p>';
logPane.innerHTML = logText + logPane.innerHTML;
} else {
window.status = msg;
}
}


// ===================================================================
// HTML utilities
// ===================================================================


function removeNewlinesFromTags(s) {
s = s.replace(/<[^>]+>/g, function(ref) {
ref = ref.replace(/\r/g, '');
ref = ref.replace(/\n/g, ' ');
return ref;
});
return s;
}

function replaceEmptyDIVsWithBRs(s) {
// Remove line feeds to avoid weird spacing in Edit HTML mode
s = s.replace(/[\r]/g, '');

/*
* sometimes IE's pasteHTML method of the range object will insert
* troublesome newlines that aren't useful
*/
if (Detect.IE()) {
var blockElementsPattern = new RegExp(
'\n<(' +
'BLOCKQUOTE' + '|' +
'LI' + '|' +
'OBJECT' + '|' +
'H[\d]' + '|' +
'FORM' + '|' +
'TR' + '|' +
'TD' + '|' +
'TH' + '|' +
'TBODY' + '|' +
'THEAD' + '|' +
'TFOOT' + '|' +
'TABLE' + '|' +
'DL' + '|' +
'DD' + '|' +
'DT' + '|' +
'DIV' + '|' +
')([^>]*)>', 'gi'
);
s = s.replace(blockElementsPattern, '<$1$2>');
}

// remove newlines from inside of tags
s = removeNewlinesFromTags(s);

// convert newlines to HTML line breaks
s = s.replace(/\n/g, '<BR>');

// Attempt to persist relative paths
if (Detect.IE()) {
s = addRelativePathPlaceholders(s);
}

/*
* Append html string to element in order to remove DIVs as nodes
* rather than using string manipulation
*/
var tmp = document.createElement('div');
tmp.style.display = 'none';

// we have to wrap the string inside of a div b/c of this weird IE bug
// that causes the entire innerHTML to become empty if the first element
// is an object tag.
tmp.innerHTML = '<div>' + s + '</div>';
tmp = tmp.childNodes[0];

// determine which DIVs have NO attributes
var plainDIVs = [];
var divs = tmp.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
var hasAttributes = false;
var attrs = divs[i].attributes;
for (var x = 0; x < attrs.length; x++) {
var val = attrs[x].specified;
if (val) {
hasAttributes = true;
break;
}
}
if (!hasAttributes) plainDIVs.push(divs[i]);
}

// strip DIVs with no attributes, but keep their content!
for (var i = 0; i < plainDIVs.length; i++) {
var grandparent = plainDIVs[i].parentNode;
if (grandparent) {
plainDIVs[i].innerHTML += '<BR>';
adoptGrandchildrenFromParent(document, plainDIVs[i]);
}
}

// remove newlines
var html = tmp.innerHTML;
html = html.replace(/[\r|\n]/g, '');

// Remove dummyURL in order to make links and images with relative links
// appear as requested and defeat IE's auto-complete
if (Detect.IE()) {
var NON_EXISTENT_URLPattern = new RegExp(NON_EXISTENT_URL, 'gi');
html = html.replace(NON_EXISTENT_URLPattern, '');
}

return html;
}

function cleanMSHTML(s) {
// Remove IE's inserted comments when text is pasted into the IFRAME
s = s.replace(/<!--StartFragment -->&nbsp;/g, '');

// Try to transform into well-formed markup
s = cleanHTML(s);

s = s.replace(/<br \/>/gi, '\n');

// Remove IE's blockquote styling
s = s.replace(/<blockquote dir=\"ltr" style=\"MARGIN-RIGHT: 0px\">/g,
'<blockquote style="margin-top:0;margin-bottom:0;">');

return s;
}

/*
* cleanHTML()
*
* Attempts to transform ill-formed HTML into well-formed markup.
*/
function cleanHTML(s) {
// make node names lower-case and add quotes to attributes
s = cleanNodesAndAttributes(s);
// Midas adds colors to <br> tags! TODO: take newline conversion elsewhere
if (Detect.MOZILLA()) s = s.replace(/<br [^>]+>/gi, '\n');
// make single nodes XHTML compatible
s = s.replace(/<(hr|br)>/gi, '<$1 \/>');
// make img nodes XHTML compatible
s = s.replace(/<(img [^>]+)>/gi, '<$1 \/>');
s = addClosingEmbedTags(s);
return s;
}

/*
* addClosingEmbedTags()
*
* Adds a closing embed tag if there isn't already one.
*/

function addClosingEmbedTags(s) {
// first strip away any closing embed tags if they exist. This should only
// happen if the HTML code was pasted into the compose mode.
s = s.replace(/<\/embed>/gi, '');
// Then add the closing tag.
s = s.replace(/<(embed [^>]+)>/gi, '<$1></embed>');
return s;
}

/*
* cleanNodesAndAttributes()
*
* Attempts to transform node names to lower-case and add double-quotes to
* HTML element node attributes.
*/
function cleanNodesAndAttributes(s) {
// Get all of the start tags
var htmlPattern = new RegExp('<[ ]*([\\w]+).*?>', 'gi');
s = s.replace(htmlPattern, function(ref) {
var cleanStartTag = ''; // for storing the result

// Separate the tag name from its attributes
var ref = ref.replace('^<[ ]*', '<'); // remove beginning whitespace
var ndx = ref.search(/\s/); // returns index of first match of whitespace
var tagname = ref.substring(0 , ndx);
var attributes = ref.substring(ndx, ref.length);

// Make tag name lower case
if (ndx == -1) return ref.toLowerCase(); // no attr/value pairs (i.e. <p>)
cleanStartTag += tagname.toLowerCase();

// Clean up attribute/value pairs
var pairs = attributes.match(/[\w]+\s*=\s*("[^"]*"|[^">\s]*)/gi);
if (pairs) {
for (var t = 0; t < pairs.length; t++) {
var pair = pairs[t];
var ndx = pair.search(/=/); // index of first match of equals (=)

// Make attribute names lower case
var attrname = pair.substring(0, ndx).toLowerCase();

// Put double-quotes around values that don't have them
var attrval = pair.substring(ndx, pair.length);
var wellFormed = new RegExp('=[ ]*"[^"]*"', 'g');
if (!wellFormed.test(attrval)) {
var attrvalPattern = new RegExp('=(.*?)', 'g');
attrval = attrval.replace(attrvalPattern, '=\"$1');
// there's an IE bug that prevent this endquote from being appended
// after the backreference. no, seriously.
attrval += '"';
}
// join the attribute parts
var attr = attrname + attrval;
cleanStartTag += ' ' + attr;
}
}
cleanStartTag += '>';

return cleanStartTag;
});

// Makes all of the end tags lower case
s = s.replace(/<\/\s*[\w]*\s*>/g, function(ref) {return ref.toLowerCase();});

return s;
}


/*
* convertAllFontsToSpans()
*
* Attempts to transform deprecated FONT nodes into well-formed XHTML-compliant
* markup.
*/
function convertAllFontsToSpans(s) {
startTagPattern = RegExp('<[^/]*font [^<>]*>', 'gi');
var StartTags = s.match(startTagPattern);
if (StartTags) {
for (var i = 0; i < StartTags.length; i++) {
// adjacent tags get lost in some regexp searches in some browsers, so
// we'll catch 'em here
if (StartTags[i].indexOf('>') > 1) innerStartTags =
StartTags[i].split('>');
for (var x = 0; x < innerStartTags.length; x++) {
if (innerStartTags[x] == '') continue;
var thisTag = innerStartTags[x] + '>';
modifiedStartTag = convertTagAttributeToStyleValue(thisTag,
'face',
'font-family');
modifiedStartTag = convertTagAttributeToStyleValue(modifiedStartTag,
'size',
'font-size');
modifiedStartTag = convertTagAttributeToStyleValue(modifiedStartTag,
'color',
'color');
s = s.replace(thisTag, modifiedStartTag);

}
}
}
s = s.replace(/<font>/gi, '<span>');
s = s.replace(/<font ([^>]*)>/gi, '<span $1>');
s = s.replace(/<\/font>/gi, '</span>');

// clean up extra spaces
s = s.replace(/<span[ ]+style/gi, '<span style');
return s;
}


/*
* convertTagAttributeToStyleValue()
*
* Attempts to transfer specified HTML attributes into the 'style' attribute for
* the supplied start tag.
*/
function convertTagAttributeToStyleValue(s, attrName, styleAttrName) {
// Get the style attribute value to convert
attributePattern = new RegExp(attrName + '="([^"]*)"', 'gi');
var matched = s.match(attributePattern);
if (!matched) return s;
var attrValue = RegExp.$1;

// remove the old attribute
s = s.replace(attributePattern, '');

// add value as new style attribute value
if (attrValue) {
if (attrName == 'size') attrValue = convertFontSizeToSpan(attrValue);
stylePattern = new RegExp('(<[^>]*style="[^"]*)("[^>]*>)', 'gi');
if (stylePattern.test(s)) {
var style = RegExp.$1;
if (style.indexOf(';') == -1) style += ';';
s = s.replace(stylePattern, style + styleAttrName + ':' + attrValue +
';$2');
} else {
tagPattern = new RegExp('(<[^\/][^>]*)(>)', 'gi');
s = s.replace(tagPattern, '$1 style="' + styleAttrName + ':' + attrValue +
';"$2');
}

//prevent colors with RGB values from aggregating with keyword / hex colors
var colorPattern = new RegExp('(color\\:[\\s]*rgb[^;]*;)color\\:[^;]*;',
'gi');
if (colorPattern.test(s)) {
s = s.replace(colorPattern, '$1');
}
}

return s;
}


/*
* convertAllSpansToFonts()
*
* Attempts to transform well-formed SPAN nodes into WYSIWYG-acceptable formats.
*/
function convertAllSpansToFonts(s) {
startTagPattern = RegExp('<[^\/]*span [^>]*>', 'gi');
var StartTags = s.match(startTagPattern);
if (StartTags) {
for (var i = 0; i < StartTags.length; i++) {
// adjacent tags get lost in some regexp searches in some browsers, so
// we'll catch 'em here
if (StartTags[i].indexOf('>') > 1) {
innerStartTags = StartTags[i].split('>');
}
for (x = 0; x < innerStartTags.length; x++) {
if (innerStartTags[x] == '') continue;
var thisTag = innerStartTags[x] + '>';
modifiedStartTag = convertTagStyleValueToAttribute(thisTag,
'font-family',
'face');
modifiedStartTag = convertTagStyleValueToAttribute(modifiedStartTag,
'font-size',
'size');
modifiedStartTag = convertTagStyleValueToAttribute(modifiedStartTag,
'color',
'color');

var lastTwoCharsPattern = new RegExp(' >$', 'gim');
modifiedStartTag = modifiedStartTag.replace(lastTwoCharsPattern, '>');
modifiedStartTag = modifiedStartTag.replace(/<span /gi, '<span ');

s = s.replace(thisTag, modifiedStartTag);
}
}
}
s = s.replace(/<span ([^>]*)>/gi, '<font $1>');
s = s.replace(/<\/span>/gi, '</font>');
s = s.replace(/<span>/gi, '<font>');

return s;
}


/*
* convertTagStyleValueToAttribute()
*
* Attempts to transfer specified values within the 'style' attribute to single
* HTML attributes for the supplied start tag.
*/
function convertTagStyleValueToAttribute(s, styleVal, attrName) {
// Get the style attribute value to convert
stylePattern = new RegExp('style="[^"]*' + styleVal + ':([^;]*)[^"]*"', 'gi');
var matched = s.match(stylePattern);
if (!matched) return s;
attrValue = RegExp.$1;

if (attrValue) {

attrValue = Trim(attrValue); // extra spaces will cause problems in IE

if (styleVal == 'color') {
var rgbPattern = new RegExp(
'rgb\\([ ]*[\\d]*[ ]*,[ ]*[\\d]*[ ]*,[ ]*[\\d]*[ ]*\\)', 'gi');
if (rgbPattern.test(attrValue)) {
return s; //TODO: add RGB to Hex conversion later
}
}
if (styleVal == 'font-size') {
attrValue = convertSpanSizeToFont(attrValue);
}
// remove the old style attribute
valuePattern = new RegExp(
'(style="[^"]*)(' + styleVal + ':[^;]*)[;]*([^"]*")', 'gi');
s = s.replace(valuePattern, '$1$3');

// add value as new attribute
stylePattern = new RegExp('(<[^>]*)(style="[^>]*>)', 'gi');
s = s.replace(stylePattern, '$1' + attrName + '="' + attrValue + '" $2');
}

//remove empty style pairs
s = s.replace(/style=""/gi, '');

return s;
}

var FONT_SIZE_CONVERSIONS = [
['5', '180%'],
['4', '130%'],
['3', '100%'],
['2', '85%'],
['1', '78%']
];

function convertFontSizeToSpan(size) {
return convertFontandSpanSizes(0, 1, size);
}

function convertSpanSizeToFont(size) {
return convertFontandSpanSizes(1, 0, size);
}

function convertFontandSpanSizes(beforeIndex, afterIndex, size) {
var conv = FONT_SIZE_CONVERSIONS;
size = Trim(size);
for (z = 0; z < conv.length; z++) {
if (size == conv[z][beforeIndex]) {
size = conv[z][afterIndex];
break;
}
}
return size;
}

function Trim(s) {
return s.replace(/^\s+/, '').replace(/\s+$/, '');
}


/**
* Escape any attributes found in the given html source that could be an XSS
* attack vector.
* @param {string} s HTML source.
* @return {string} Escaped HTML source.
*/
function escapeXssVectors(s) {
var htmlDiv = document.createElement('DIV');
htmlDiv.innerHTML = s;
escapeXssVectorsFromChildren_(htmlDiv);
return htmlDiv.innerHTML;
}

/**
* Recursive helper for escapeXssVectors. Escapes attributes of any children of
* the given element that are also elements, then recurses on that child.
* @param {Element} elem Element whose children should be escaped.
*/
function escapeXssVectorsFromChildren_(elem) {
for (var node = elem.firstChild; node != null; node = node.nextSibling) {
if (node.nodeType == 1) { // 1 == ELEMENT

if (node.nodeName.toLowerCase() == 'iframe') {
// If src attribute contains scripting, it could be an XSS attack
// (see {@bug 3256288}), so rename it to blogger_src so it won't get
// executed when the html is rendered inside the editor. To be extra
// safe, we just whitelist values starting with http(s):, so that it
// catches javascript:, vbscript:, data:, and any other tricks that
// may come up in the future.
var xssSafeRegExp = /^\s*https?:/i;
var srcValue = node.getAttribute('src');
if (srcValue && !xssSafeRegExp.test(srcValue)) {
node.removeAttribute('src');
node.setAttribute('blogger_src', srcValue);
}
}

// Rename any attributes starting with "on" to "blogger_on"+whatever to
// prevent the event handler code from ever executing, as that could be an
// XSS attack.
var eventAttrs = [];
for (var i = 0; i < node.attributes.length; i++) {
var attrNode = node.attributes[i];
if (!attrNode.specified) {
continue;
}
var attrName = attrNode.name.toLowerCase();
if (/^on/.test(attrName)) {
eventAttrs.push(attrName);
}
}
for (var j = 0; j < eventAttrs.length; j++) {
var attrValue = node.getAttribute(eventAttrs[j]);
node.removeAttribute(eventAttrs[j]);
node.setAttribute('blogger_' + eventAttrs[j], attrValue);
}

escapeXssVectorsFromChildren_(node);

}
}
}

/**
* Restore any attributes found in the given html source that were escaped via
* escapeXssVectors.
* @param {string} s Escaped HTML source.
* @return {string} Restored HTML source.
*/
function restoreXssVectors(s) {
if (Detect.IE()) {
// @bug 4227170 : IE collapses line breaks when you insert source into
// innerHTML, so replace them temporarily with <br> while we restore the
// placeholders. (Not a problem when escaping xss because we convert \n to
// <br> before escaping.)
s = s.replace(/\n/g, '<br>');
}

var htmlDiv = document.createElement('DIV');
htmlDiv.innerHTML = s;
restoreXssVectorsFromChildren_(htmlDiv);
s = htmlDiv.innerHTML;

if (Detect.IE()) {
// Convert temporary <br> back to line breaks.
s = s.replace(/<br>/gi, '\n');
}

return s;
}

/**
* Recursive helper for restoreXssVectors. Restores attributes of any children
* of the given element that are also elements, then recurses on that child.
* @param {Element} elem Element whose children should be restored.
*/
function restoreXssVectorsFromChildren_(elem) {
for (var node = elem.firstChild; node != null; node = node.nextSibling) {
if (node.nodeType == 1) { // 1 == ELEMENT

if (node.nodeName.toLowerCase() == 'iframe') {
var srcValue = node.getAttribute('blogger_src');
if (srcValue) {
node.removeAttribute('blogger_src');
node.setAttribute('src', srcValue);
}
}

var eventAttrs = [];
for (var i = 0; i < node.attributes.length; i++) {
var attrNode = node.attributes[i];
if (!attrNode.specified) {
continue;
}
var attrName = attrNode.name.toLowerCase();
if (/^blogger_on/.test(attrName)) {
eventAttrs.push(attrName);
}
}
for (var j = 0; j < eventAttrs.length; j++) {
var attrValue = node.getAttribute(eventAttrs[j]);
node.removeAttribute(eventAttrs[j]);
node.setAttribute(eventAttrs[j].substr(8), attrValue);
}

restoreXssVectorsFromChildren_(node);

}
}
}

Facebook 留言板