顯示具有 asp 標籤的文章。 顯示所有文章
顯示具有 asp 標籤的文章。 顯示所有文章

2012年10月17日 星期三

[Asp].ASPStringBuilder 和 .net stringer builder 效能比較

最近遇到一個功能, 要匯出資料, 因為效能很差, 要匯很久, 想看看用純 asp 處理比較快, 還是改用 .net 比較快, 分別測試2種方法, 結論是差不多, 要快的話, 中間使用的流程/架構要翻掉, 改用其他方法, 才會有效率.

這一個個案遇到的是 XML 的內容就 71mb, 含 DOM object 一起的話RAM 大約占用 150mb, 在 apply style 套 xsl 時, 瞬間的 RAM 再會多用掉500多MB, 像寫的這麼爛的程式, 真的很怕有很多人同時點下去執行, 應該要把Web Server 弄掉 memory overflow 錯誤.


有圖有真相, 畫面如下:

測試方案1號: 使用 ASP 寫的 StringBuilder(cASPString.asp),
圖片1.1: 程式執行前的RAM, 大約用掉 127mb:

圖片1.2: 程式執行前的RAM, DOM 大約用掉 150mb 左右:
(說明: 由於沒有在用 RAM 回收的太快, 所以數字不太準.)


測試方案2號: 使用 ASP 呼叫 .Net StringBuilder
圖片2.1: 程式執行前的RAM, 大約用掉 133mb:

圖片2.2: 程式執行前的RAM, DOM 大約用掉 150mb 左右:


在 DOM ready 好的時候, 可以看到 RAM 的使用快速成長, 會從 300mb 變 500, 再長大到 700mb, 最後在 860mb 右右停留 2~3秒, 再回來 200MB 左右.

測試結論: 在 Asp 使用 StringBuilder(cASPString.asp), 和呼叫 .Net 的 String Builder 效能上差不多, 主要的瓶頸在 XML 太大.


附上, asp 版本的 StringBuilder.
'====================================
' MODULE:    cASPString.asp
' AUTHOR:    www.u229.no
' CREATED:   May 2006
'====================================
' COMMENT: A fast string class for classic ASP.               
'====================================
' ROUTINES:

' - Public Property Get NumberOfStrings()
' - Public Property Get NumberOfBytes()
' - Public Property Get NumberOfCharacters()
' - Private Sub Class_Initialize()
' - Public Sub Append(sNewString)
' - Public Function ToString()
' - Public Sub Reset()
'====================================


'====================================
Class ASPStringBuilder
'====================================

'// MODULE VARIABLES
Dim m_sArr            '// Array holding the strings.
Dim m_lResize        '// Factor for rezising the array.
Dim m_lStrings       '// Number of strings appended.

'// PROPERTIES
Public Property Get NumberOfStrings()
    NumberOfStrings = m_lStrings
End Property

Public Property Get NumberOfBytes()
    NumberOfBytes = LenB(Join(m_sArr, ""))
End Property

Public Property Get NumberOfCharacters()
    NumberOfCharacters = Len(Join(m_sArr, ""))
End Property

'------------------------------------------------------
' Comment: Initialize default values.
'------------------------------------------------------
Private Sub Class_Initialize()
    m_lResize = CLng(50)
    m_lStrings = CLng(0)
    ReDim m_sArr(m_lResize)
End Sub

'------------------------------------------------------
' Comment: Add a new string to the string array.
'------------------------------------------------------
Public Sub Append(sNewString)

    If Len(sNewString & "") = 0 Then Exit Sub
    
    '// If we have filled the array, resize it.
    If m_lStrings > UBound(m_sArr) Then ReDim Preserve m_sArr(UBound(m_sArr) + m_lResize)

    '// Append the new string to the next unused position in the array.
    m_sArr(m_lStrings) = sNewString
    m_lStrings = (m_lStrings + 1)
End Sub

'------------------------------------------------------
' Comment: Return the strings as one big string.
'------------------------------------------------------
Public Function ToString()
    ToString = Join(m_sArr, "")
End Function

'------------------------------------------------------
' Comment: Reset everything.
'------------------------------------------------------
Public Sub Reset()
    Class_Initialize
End Sub

'====================================
End Class 
'====================================

讓 asp call .net string builder 的方法:
namespace MyObject
{
    public class StringBuilderObject
    {
        public StringBuilder MyStringBuilder = new StringBuilder("");
        public StringBuilderObject()
        {
        }
        public void Append(string value)
        {
            this.MyStringBuilder.Append(value);
        }
        public void Remove(int startIndex, int length)
        {
            this.MyStringBuilder.Remove(startIndex, length);
        }
        public override string ToString()
        {
            return this.MyStringBuilder.ToString();
        }
    }
}

2012年10月16日 星期二

[Asp].實作網頁匯出為 PDF 的心得

今天分享我在實作網頁匯出為 PDF 的心得, 今天遇到2個問題:
Q 1. 在後台匯出的網頁, 要匯出的網頁必需為 "登錄狀態" 才能匯出, 可是使用 wkhtmltopdf 時, 無法取得登入狀態.
Q 2. wkhtmltopdf 元件匯出的資料夾, 無法和中文目錄相容, 會有Error 產生, 訊息如下:
匯出檔案到 E:\oooo網\ 這個 "網" 是中文字, 造成Error.


Q 1. 首先解決, 登錄狀態, 的問題,
A 1: 這個解決方法很多很多,

方案1.1: 直接輸入 userid.
這是最快的解決方法, 直接寫一支 app_sso.asp 程式, 傳入 userid 就解決了. 可是這個解法變成, 萬一我是離職的工程師, 我事先就知道這支程式, 在user 傳入 userid 就可以取得該帳號的權限, 這樣問題就比較大.


方案1.2: 修改後台的程式, 遇到某些參數傳進來時(例如: ?exportPdf=true), 就開放部份的權限, 允許顯示資料庫內容. 這個解法也不錯.


方案1.3: session userid 放database, 透過 tokenid 來存取.
和上面的方案1.1相比, 這個方案1.3 會安全一點點, app_sso.asp 處理步驟如下:

1.3.1. 後台把 session userid 放進 table,
1.3.2. 取出 table 裡的 token_id,
1.3.3. 把 token_id 給下一支沒有 session 的 app,
1.3.4. 沒有 session 的 app request token_id 後, 透過 tokenid 再到 database 裡去取出 userid.
1.3.5 模擬使用者登入, 並重導(Redirect) 到實際上要產生為 PDF 的 URL.


附上使用到的 table schema:
CREATE TABLE [dbo].[webkitHtmlToken](
 [id] [int] IDENTITY(1,1) NOT NULL,
 [tokenID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_webkitHtmlToken_tokenId]  DEFAULT (newid()),
 [userid] [nvarchar](20) NOT NULL,
 [siteid] [varchar](50) NULL,
 [status] [char](1) NULL CONSTRAINT [DF_webkitHtmlToken_status]  DEFAULT ('I'),
 [sessionid] [int] NULL,
 [createdDate] [smalldatetime] NULL CONSTRAINT [DF_webkitHtmlToken_createdDate]  DEFAULT (getdate()),
 CONSTRAINT [PK_webkitHtmlToken] PRIMARY KEY CLUSTERED
(
 [tokenID] ASC
)
)
說明: 可能專案不同, 有可能需要多增加, 或刪除相關欄位.


Q 2: 中文字,
A 2:這個要解就很簡單, 先透過副程式判斷是否有中文字, 有的話就使用暫存的資料夾.


最後, 整個流程方式, 我的做法是:
2.1 在AP設定檔裡增加2個設定值,
(a)wkhtmltopdf 執行檔路徑
(b)wkhtmltopdf 遇到中文字路徑時, 要使用的暫存資料夾, 這個記得要允許 IUSER 有寫入及刪除檔案的權限, 例如:
<wkhtmltopdf>
    <path>C:\wkhtmltopdf\wkhtmltopdf.exe</path>
    <tempFolder>C:\wkhtmltopdf\tempFolder</tempFolder>
</wkhtmltopdf>

2.2 寫副程式, 取得 tokenid
'// purpose: 放入 session info 到 database, 取得 tokenid.
'// ex: ret = getWebkitHtmlTokenID(conn, session("userid"), session.sessionid, session("siteid"))
function getWebkitHtmlTokenID(byref conn, byval userid, byval sessionid, byval siteid)
...
...
end function


2.3 寫副程式, 取得 tokenid
'// purpose: 輸出某一個 URL 到實體檔案.
'// call HtmlToPdfFile(pdfUrl, user_output_filepath)
function HtmlToPdfFile(byval pdfUrl, user_output_filepath)
...
...
end function

2.4 寫一支  export_pdf.asp
2.4.1 先呼叫 2.2 的附程式取得 tokenID.
2.4.2 再把 tokenID 放入 app_sso.asp, 並設定實際要匯入資料的 URL.
2.4.3 輸出某一個 URL 到實體檔案, 在這個副程式裡執行外部指令(要等待程式回應, Wait For Single), 等待wkhtmltopdf.exe 匯出pdf 完成程式再繼續往下執行.
2.4.4 透過 ADODB.Stream, 取出檔案內容, 並 Response.BinaryWrite objStream.Read
2.4.5 最後, 完成後, 刪掉暫存檔, 後台匯出pdf 完成.
call DeleteFile(user_output_filepath)


說明: 前台(無登錄狀態)匯出 pdf 的話就會簡單很多, 少掉取得 tokenid 這一個 step.

增加這個功能, 會用到的相關檔案及說明:


  • ApConfig.xml , 參數設定
  • app_sso.asp , 讓外部程式模擬登入
  • export_pdf.asp , 實際上匯出的 app.
  • pdf.Function.asp , 副程式.





相關文章:
[Asp].用正規表示式檢查字串是不包含中文字
http://maxtellyou.blogspot.tw/2012/10/asp.html

[Asp].用正規表示式檢查字串是不包含中文字

今天遇到一個問題, wkhtmltopdf 在輸出 pdf 時, 無法輸出檔案到中文的資料夾下, 會發生錯誤:
Error: Unable to write to destination


我想到的解決辦法是, 先放到一個暫存的資料夾, 再搬到有中文的真正的目的地, 即可, 所以要先判斷輸出的資料夾, 是否包含中文, 附件 Asp 用的檢查副程式.


'// RegExp Test.
Function RegExpTest(byval patrn, byval str)
    Dim regEx
    Set regEx = New RegExp
    regEx.Pattern = patrn
    regEx.IgnoreCase = True
    regEx.Global = True
    RegExpTest = regEx.Test(str)
End Function


'// 檢查文字中是否有中文字
'// ex: ret = IsMatchChinese(str)
'// return:
'//     True: find.
'//     False: not found.
Function IsMatchChinese(byval str)
    IsMatchChinese = RegExpTest("[一-龥]+",str)
End Function

呼叫的範例如下:
if IsMatchChinese(output_filepath) then
    response.write "<br/>bingo, match chinese folder..."
else
    response.write "<br/>ok, not chinese folder continue..."
end if


聽說 Asp.Net 的範例如下, 還沒測試過:
Regex ex = new Regex("[一-龥]"); 
bool isMatched = ex.IsMatch("jjsss 中文 ksks");

2012年10月11日 星期四

分享我在AP 裡幫圖片自動縮圖心得及做法

分享我在AP 裡幫圖片自動縮圖心得及做法, 我在做縮圖的情況有2種: 產品和AP.

首先,我用的縮圖元件是ImageMagick, 請先在下面的 URL 下載安裝檔:

http://www.imagemagick.org/script/binary-releases.php#windows

ImageMagick


32位元的Windows 請下載這個檔案:
Version: ImageMagick-6.?.?-?-Q16-windows-dll.exe
Description: Win32 dynamic at 16 bits-per-pixel

如果是 x64 平台, 也可下載這一個版本:
Version: ImageMagick-6.7.9-10-Q16-windows-x64-dll.exe
Description: Win64 dynamic at 16 bits-per-pixel




產品面和 AP 面差在產品面是專門給產品用, 其他 AP 沒辦法直接存取, 算是一種客制化, 相對輸入的參數就會很簡單, 只要傳產品的id 進來即可.

  • 1. 產品面:
    這個的輸入參數有2個, 一個是文章 ID (必填), 一個是節點的 ID (非必填).

    在縮圖的時候, 會先把縮圖的參數設定檔取出來, 看看目前的文章ID 是不是符合到設定檔裡的那一個範本, 通常會這麼做, 就是因為網站規模比較大, 又要做的有彈性和有效率.

    縮圖的設定檔的回傳規則是,
    1.1: 先以節點 ID 為模式(pattern)來搜尋, 如果有符合節點的話就傳回要產生的縮圖設定檔.
    1.2 再以文章 ID所屬的表格(Table, 或單元) 為模式(pattern)來搜尋縮圖設定檔.
    1.3 再以文章ID 所屬的範本(Definition) 為模式(pattern)來搜尋縮圖設定檔.
    1.4 最後都沒找到的話, 就傳回 "預設的縮圖設定檔".
  • 2. AP 面:
    AP 面也可以用在產品面上面, AP面使用起來也很有彈性, 和產品面的差別在, 呼叫縮圖副程式時, AP面要把實體的圖片路徑傳給副程式, 產品面的話只需要傳入文章ID.

下面就以 AP 為例, 來看看如何套用縮圖副程式:
  • step 1: include 副程式, 如果是其他物件化導向的程式語言, 這一個 step 應該是 import 或 引用縮圖元件.
  • step 2: 新增文章時增加這一行指令:
    call gen_ap_image_filename(image_filepath, fileUploadPath, "180", "", "", "180")

    副程式的參數說明:
    1. image_filepath, 上傳的圖片實體路徑.
    2. fileUploadPath, 上傳的資料夾的實體路徑.
    3. resize_outputIconFolder, 要縮圖到子資料夾的名稱, 當同一個資料夾的檔案數太多時, 系統效能會變差, 所以縮圖並不一定要和原圖放在同一個資料夾下.
    4. resize_outputIconFilePrefix, 產生的新縮圖檔名, 是不要加一個前置字串, 像是 "s_", "m_", "l_" 之類的, 以區分大中小圖.
    5. resize_outputIconFilePostfix, 產生的新縮圖檔名, 是不要加一個後置字串, 像是 "_s", "_m", "_l" 之類的, 以區分大中小圖.
    6. resize_targetWidth, 圖片的 max-height 和 max-width, 以輸入 180 來說指的是圖寬不會超過 180px, 圖高也不會超過180px.

  • step 3: 修改文章, submit 後, 在更新資料(update table)前, 增加這一行指令, 用來刪除修改前的實體縮圖檔案:
    call delete_old_resized_image_db(image_fieldName, dbTableName, myWhereCondition, fileUploadPath, "180", "", "")
    說明: 這個附程式會先透過前3個參數, 取得 image 的實體 filename, 再透過第4個參數(fileUploadPath) 取得圖片的實體檔案路徑, 然後再用 FileSystemObject 刪除實體的縮圖檔案, 所以並不一定要透過上面這一個 delete_old_resized_image_db() 副程式來刪, 也可以直接用 FileSystemObject 刪除實體的縮圖檔案
  • step 4: 修改文章, submit 後, 在 update database後, 比照 step 2, 呼叫來產生縮圖.
  • step 5: 刪除文章時, 比照 step 3, 在刪除資料(delete recored) 前, 刪除實體的縮圖檔案.

如果是產品面的更新, 參考下圖,
1. 在修改前呼叫副程式: deleteThumbImages 把文章 ID 傳入, 刪除縮圖檔案.
2. 更新文章內容,
3. 產內縮圖, 傳入文章ID, 和節點ID(NodeID).


對於已經存在的資料, 可以參考看看下面的 sample 來整批的轉檔::
dim sql
sql = "select * from tablename"

dim rs
set rs = conn.execute(sql)
if not rs.eof then
    do
        dim id
        id = trim("" & rs("id"))
        
        '// 客製化, 處理縮圖
        '// in file: ???.ImageMagic.function.asp
        dim cust_image_fieldName
        dim cust_image_fieldvalue
        cust_image_fieldName = "RoomPhoto"
        cust_image_fieldvalue = trim("" & RS("" & cust_image_fieldName))
        call gen_ap_image_filename(cust_image_fieldvalue, "(你的上傳資料夾)", "180", "", "", "180")
        
        response.write "<br/>ID:" & id
        rs.movenext
        if rs.eof then
            exit do
        end if
    loop
end if

2012年10月9日 星期二

asp 呼叫 .net 的 web service 以 "e政府服務平臺單一登入介接" 為例

asp 呼叫 .net 的 web service 以 "e政府服務平臺單一登入介接" 為例, 原來用 asp 呼叫 wsdl 還滿簡單的.


1. 認識一下 e政府服務平台(Government Service Platform, GSP):

單一登入介接說明

架構圖, 我猜主要應該要看左半邊.



單一登入介接的效益
  • 減少機關/業務重複開發成本:
    各機關業務建置入口網站及業務服務時,對使用者都有認證、授權的共同需求,使用平台的單一登入機制,可以縮短建置時間並減少開發成本。
  • 支援不同的認證方式:
    使用者可使用自然人/工商/機關憑證或一般帳號密碼為登入認證方式。
  • 使用相同的登入帳號、單一的入口網址及登入介面:
    使用者在存取各機關服務時可以不必註冊多個帳號密碼,以達簡化使用及電子化政府便民之目的。


Web Service 位址:
https://www.cp.gov.tw/SEWebApplication/RSMediator.asmx

在登入成功取得token1後,呼叫GetUserProfile傳入token1,取得
userprofile,內含<SecureLevel>可以判定使用者的登入方式
. <SecureLevel>值的意義如下:
. IDPassword:代表帳號密碼登入
. PlatformX509或AnonymousX509:代表憑證登入

單一登入介接說明
其實, 就是登入的 URL 都連到 www.cp.gov.tw , 再把登入後要連的 URL 放在 returnurl 參數裡即可.

登入完成, www.cp.gov.tw 會用 post 的方式把 token1 放在 twGovT1 這個參數傳給 returnurl 裡的程式.


2. 寫 asp 程式呼叫 wsdl:
參考看看下面2支副程式:


'// purpose: 取得 e政府服務平台註冊服務模組介接服務 web service.
function getGspServiceXml(byval twGovT1)
    dim returnValue
    returnValue = ""
    
    '// 設定 Web Service 位址:
    dim ws_HOST
    ws_HOST = "www.cp.gov.tw"
    dim ws_URL
    ws_URL = "https://"& ws_HOST &"/SEWebApplication/RSMediator.asmx"

    '// Web Service SOAP 內容:
    SoapRequestStr = ""
    SoapRequestStr = SoapRequestStr & "<?xml version=""1.0"" encoding=""utf-8""?>"
    SoapRequestStr = SoapRequestStr & "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">"
    SoapRequestStr = SoapRequestStr & "<soap:Body>"
    SoapRequestStr = SoapRequestStr & "<GetUserProfile xmlns=""http://tempuri.org/"">"
    SoapRequestStr = SoapRequestStr & "<Token1>"& twGovT1 &"</Token1>"
    SoapRequestStr = SoapRequestStr & "</GetUserProfile>"
    SoapRequestStr = SoapRequestStr & "</soap:Body>"
    SoapRequestStr = SoapRequestStr & "</soap:Envelope>"

    '// 送出 SOAP
    dim SoapRequest
    Set SoapRequest = Server.CreateObject("MSXML2.XMLHTTP")
    SoapRequest.Open "POST", ws_URL & "?wsdl", False
    SoapRequest.setRequestHeader "Content-Type", "text/xml;charset=utf-8"
    SoapRequest.setRequestHeader "HOST","www.cp.gov.tw"
    SoapRequest.setRequestHeader "Content-Length",LEN(SoapRequestStr)
    SoapRequest.setRequestHeader "SOAPAction", "http://tempuri.org/GetUserProfile"
    on error resume next
    SoapRequest.Send(SoapRequestStr)
    if err.number <> 0 then
        '// debug, show error message
    else
        '// success.
        returnValue = SoapRequest.responseXML.xml
    end if
    getGspServiceXml = returnValue
    'set SoapRequest = nothing
end function

'// purpose: 取得 e政府服務平台註冊服務模組介接服務 web service 裡的內容.
function getGspDataByXPath(byval soap_return_xml, byval queryXPath)
    dim returnValue
    returnValue = ""
    dim soap_XML
    set soap_XML = server.createObject("Msxml2.DOMDocument")
    soap_XML.async = false
    soap_XML.loadXML(soap_return_xml)
    if soap_XML.parseError.reason <> "" then
        '// load soap xml error.
    else
        if not (soap_XML.documentElement.selectSingleNode("//GetUserProfileResult") is nothing) then
            dim ResultXml
            set ResultXml = server.createObject("Msxml2.DOMDocument")
            ResultXml.async = false
            ResultXmlString = trim("" & soap_XML.documentElement.selectSingleNode("//GetUserProfileResult").text)
            ResultXml.loadXML(ResultXmlString)
            if ResultXml.parseError.reason <> "" then
                '// load soap xml error.
            else
                returnValue = nulltext(ResultXml.selectSingleNode(queryXPath))
            end if
        end if
    end if
    getGspDataByXPath = returnValue
end function

function nullText(byref xNode)
    dim xstr
    xstr = ""
    if not (xNode is nothing) then
        xstr = xNode.text
    end if
    nullText = xStr
end function

主程式如下:
dim soap_return_xml
soap_return_xml = trim("" & getGspServiceXml(twGovT1))
if soap_return_xml <> "" then
    myData = getGspDataByXPath(soap_return_xml, myXPath)
else
    '// Error.
end if

呼叫完, 透過 token 從 web service 取到的 XML 內容如下:
SOAP 回應的XML內容

以我的個案, 要取得 email 來說, 
mailXPath = "//CPWSResponse/Result/UserProfile/ContactInfo/Mail"
mailData = getGspDataByXPath(soap_return_xml, mailXPath)

就可以取到 eMail, 由於我只有要取一次, 所以程式碼沒有寫的很有效率, 如果您要取的欄位很多, 可能要換一個寫法, 會比較有效率一點點.


附註:
如果要介接 e政府, 需要先填寫申請單:
1.e政府服務平臺服務介接申請表_SSO_Service Application.doc

如果沒有填, 就會出現下面的錯誤畫面:
domain name 還沒開通前的畫面.

要解決這個問題很簡單, 設一下 host 就解決了, 先用別的已經通過申請的  domain name即可.

之前 UI 是規畫 id/password 的輸入框是在客戶的網站上, 而不是連到 eGov  去登入, 挑戰直接 submit 到 eGov 的 https://www.cp.gov.tw/portal/Login.aspx, 結果會出現下面的錯誤訊息:
超有趣的錯誤訊息. ╰( ̄▽ ̄)╭

2012年9月25日 星期二

[SQL].2個經緯度坐標,計算直線距離


有一個專案需求是, 可以查詢某中心點 2000公尺內的景點, 之前是用 between 取出資料, 但取出的資料, 無法按照距離做排序, 所以還是乖乖地透過 SQL 把距離算出來.

計算坐標距離的 SQL 呼叫範例:
SELECT
  htx.ID, Longitude, Latitude
  ,acos(sin(radians(24.80181500042168)) * sin(radians(Latitude )) + cos(radians(24.80181500042168)) * cos(radians(Latitude )) * cos(radians(120.971596999978 - Longitude))) * 6372.8
as rout_distance
  , 24.80181500042168 as route_lat
  , 120.971596999978 as route_lon
FROM Info htx 
WHERE
(Longitude BETWEEN 120.961801499978 
AND 120.981392499978) and(Latitude BETWEEN 24.7928168004217 
AND 24.8108132004217) order by rout_distance , Type desc , Name
說明1: 24.80181500042168 是我要查詢的中心點 LAT, 120.971596999978 是我的中心點 LON.

說明2: 原本用來取距離是用 between 取, 但這有一個問題, 就是在正方向角角的, 其實會超過預設的距離.


ASP 關於 sql 組合的程式碼如下:
, acos(sin(radians("& me.route_lat &")) * sin(radians(HotelLatitude )) + cos(radians("& me.route_lat &")) * cos(radians(HotelLatitude )) * cos(radians("& me.route_lon &" - HotelLongitude))) * 6372.8 as rout_distance


專案的畫面如下:


資料來源:
Distance-based JOIN given Latitude/Longitude
http://stackoverflow.com/questions/8947998/distance-based-join-given-latitude-longitude

2012年9月12日 星期三

SQL Server 斷詞後, 同義詞的問題

SQL Server 斷詞查不到資料, 有2個解決方法:

  • 方案1: 使用 舊版的斷詞DLL (CHTBRKR.DLL)
  • 方案2: 使用詞庫來解決: tcainlex.txt


如果使用了方案1之後, 就會造成同義詞功能無法使用(tsCHT.XML)
目前暫時無解, 用最兩光的自己動手寫來解決:
dim checkKeywordExpansion
checkKeywordExpansion = ap_THESAURUS_expansion(myKeywordItem)

if myFieldName = "*" then
    '// 查多個欄位, 全查.
    searchFields = "*"
    if checkKeywordExpansion = "" then
        '// 一般查詢
        myKeywordSQL = myKeywordSQL & dbQueryMethod &"("& searchFields &" ,N'" & myKeywordItem & "')"
    else
        '// expansion
        myKeywordSQL = myKeywordSQL & "("
        myKeywordSQL = myKeywordSQL & dbQueryMethod &"("& searchFields &" ,N'" & myKeywordItem & "')"
        myKeywordSQL = myKeywordSQL & " OR " & dbQueryMethod &"("& searchFields &" ,N'" & checkKeywordExpansion & "')"
        myKeywordSQL = myKeywordSQL & ")"
    end if
else
    '// 只查某個欄位.
    searchFields = myFieldName
    
    if checkKeywordExpansion = "" then
        '// 一般查詢
        myKeywordSQL = myKeywordSQL & dbQueryMethod &"("& searchFields &" ,N'" & myKeywordItem & "')"
    else
        '// expansion
        myKeywordSQL = myKeywordSQL & "("
        myKeywordSQL = myKeywordSQL & dbQueryMethod &"("& searchFields &" ,N'" & myKeywordItem & "')"
        myKeywordSQL = myKeywordSQL & " OR " & dbQueryMethod &"("& searchFields &" ,N'" & checkKeywordExpansion & "')"
        myKeywordSQL = myKeywordSQL & ")"
    end if
end if


'// purpose: 是否符合 THESAURUS_expansion rule.
'// return: 傳回 keyword expansion 後結果.
'// ps: 這個不是 "最佳解法", 只是為了立刻解掉這個問題.
'// 由於換掉斷詞工具. 無法使用 THESAURUS
function ap_THESAURUS_expansion(byval keywordItem)
    dim returnValue
    returnValue = ""
    
    dim isMatchExpansion
    isMatchExpansion = not True
    
    dim currentPat
    currentPat = ""
    
    if not isMatchExpansion then
        currentPat = "台"
        currentSub = "臺"
        if instr(keywordItem, currentPat) > 0 then
            isMatchExpansion = true
            returnValue = replace(keywordItem, currentPat, currentSub)
        end if
    end if
    
    if not isMatchExpansion then
        currentPat = "臺"
        currentSub = "台"
        if instr(keywordItem, currentPat) > 0 then
            isMatchExpansion = true
            returnValue = replace(keywordItem, currentPat, currentSub)
        end if
    end if

    ap_THESAURUS_expansion = returnValue
end function




相關文章:
[SQL]使用SQL2005全文檢索功能
http://www.dotblogs.com.tw/dotjum/archive/2009/08/01/9796.aspx

SQL Server 2005/2008 斷字詞 DLL
http://byronhu.wordpress.com/2009/03/13/sql-server-20052008-%E6%96%B7%E5%AD%97%E8%A9%9E-dll/

2012年9月11日 星期二

多個景點(坐標), 套google map 的方法

資料來源:
http://stackoverflow.com/questions/3059044/google-maps-js-api-v3-simple-multiple-marker-example


多個坐標, 就要透過 google map API 來呼叫, 才能使用, 用法也很簡單, html 範例如下:
<script src="http://maps.google.com/maps/api/js?sensor=false&language=zh_TW" type="text/javascript"></script>
<div id="map" style="width: 500px; height: 400px;"></div>
<script type="text/javascript">
var locations = [
  ['Bondi Beach', -33.890542, 151.274856, 4],
  ['Coogee Beach', -33.923036, 151.259052, 5],
  ['Cronulla Beach', -34.028249, 151.157507, 3],
  ['Manly Beach', -33.80010128657071, 151.28747820854187, 2],
  ['Maroubra Beach', -33.950198, 151.259302, 1]
];

var map = new google.maps.Map(document.getElementById('map'), {
  zoom: 10,
  center: new google.maps.LatLng(-33.92, 151.25),
  mapTypeId: google.maps.MapTypeId.ROADMAP
});

var infowindow = new google.maps.InfoWindow();

var marker, i;
for (i = 0; i < locations.length; i++) {  
  marker = new google.maps.Marker({
    position: new google.maps.LatLng(locations[i][1], locations[i][2]),
    title: locations[i][0],
    zIndex: locations[i][3],
    map: map
  });
  google.maps.event.addListener(marker, 'click', (function(marker, i) {
    return function() {
      infowindow.setContent(locations[i][0]);
      infowindow.open(map, marker);
    }
  })(marker, i));
}
</script>

套出來的畫面如下:


說明1: 原來的範例裡的 marker, 沒有加 title, 所以滑鼠移過去時(onMouseOver) 不會顯示出 坐標的名稱(Title), 上面的code 裡, 已經有加 title 進去 market 裡.

說明2: sensor=false&language=zh_TW, 代表要使用繁體中文介面, 沒加的話browser 會挑戰去偵測目前使用的語系, 有可能會猜錯.


如果想要換 marker 的 icon image 的話, 可以試試看下面的範例:
<div id="map_canvas" style="width: 500px; height: 400px;"></div>
<script type="text/javascript">
var beaches = [
  ['Bondi Beach', -33.890542, 151.274856, 4],
  ['Coogee Beach', -33.923036, 151.259052, 5],
  ['Cronulla Beach', -34.028249, 151.157507, 3],
  ['Manly Beach', -33.80010128657071, 151.28747820854187, 2],
  ['Maroubra Beach', -33.950198, 151.259302, 1]
];

function initialize() {
  var myOptions = {
    zoom: 10,
    center: new google.maps.LatLng(-33.9, 151.2),
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
  var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
  setMarkers(map, beaches);
}

function setMarkers(map, locations) {
  var image = new google.maps.MarkerImage('images/beachflag.png',
      new google.maps.Size(20, 32),
      new google.maps.Point(0,0),
      new google.maps.Point(0, 32));
  var shadow = new google.maps.MarkerImage('images/beachflag_shadow.png',
      new google.maps.Size(37, 32),
      new google.maps.Point(0,0),
      new google.maps.Point(0, 32));
  var shape = {
      coord: [1, 1, 1, 20, 18, 20, 18 , 1],
      type: 'poly'
  };
  for (var i = 0; i < locations.length; i++) {
    var beach = locations[i];
    var myLatLng = new google.maps.LatLng(beach[1], beach[2]);
    var marker = new google.maps.Marker({
        position: myLatLng,
        map: map,
        shadow: shadow,
        icon: image,
        shape: shape,
        title: beach[0],
        zIndex: beach[3]
    });
  }
}

initialize();
</script>



最後完成的asp sample code 如下:
'// google map API.
Response.Write("<script src=""http://maps.google.com/maps/api/js?sensor=false&language=zh_TW"" type=""text/javascript""></script>"&vbCrLf)

Response.Write("<script type=""text/javascript"">"&vbCrLf)
Response.Write("function initialize_map(map_id, locations) {"&vbCrLf)
Response.Write("    var map = new google.maps.Map(document.getElementById(map_id), {"&vbCrLf)
Response.Write("      zoom: 10,"&vbCrLf)
Response.Write("      center: new google.maps.LatLng(locations[0][1], locations[0][2]),"&vbCrLf)
Response.Write("      mapTypeId: google.maps.MapTypeId.ROADMAP"&vbCrLf)
Response.Write("    });"&vbCrLf)
Response.Write("    var infowindow = new google.maps.InfoWindow();"&vbCrLf)
Response.Write("    var marker, i;"&vbCrLf)
Response.Write("    for (i = 0; i < locations.length; i++) {  "&vbCrLf)
Response.Write("      marker = new google.maps.Marker({"&vbCrLf)
Response.Write("        position: new google.maps.LatLng(locations[i][1], locations[i][2]),"&vbCrLf)
Response.Write("        title: locations[i][0],"&vbCrLf)
Response.Write("        zIndex: locations[i][3],"&vbCrLf)
Response.Write("        map: map"&vbCrLf)
Response.Write("      });"&vbCrLf)
Response.Write("      "&vbCrLf)
Response.Write("      google.maps.event.addListener(marker, 'click', (function(marker, i) {"&vbCrLf)
Response.Write("        return function() {"&vbCrLf)
Response.Write("          infowindow.setContent(locations[i][0]);"&vbCrLf)
Response.Write("          infowindow.open(map, marker);"&vbCrLf)
Response.Write("        }"&vbCrLf)
Response.Write("      })(marker, i));"&vbCrLf)
Response.Write("    }"&vbCrLf)
Response.Write("}"&vbCrLf)
Response.Write("</script>"&vbCrLf)

dim Index
Index = 0
do
    Index = Index + 1
    
    Set RsDetail = Server.CreateObject("ADODB.RecordSet")
    RsDetail.open SqlDetail, conn, 1, 1

    Response.Write "<h4>" & trim("" & myRS("Title")) & "</h4>" & vbCrLf
    if not RsDetail.eof then
        Response.Write "<div class='map'>"
        Response.Write("<h4>【"& server.htmlencode(trim("" & myRS("Subject"))) &"】</h4>"&vbCrLf)
        Response.Write "<div id='map_canvas_"& Index &"' style='width: 600px; height: 400px;'></div>"
        Response.Write "</div>"&vbCrLf
        
        Response.Write("<script type=""text/javascript"">"&vbCrLf)
        Response.Write("var locations_"& Index &" = ["&vbCrLf)
        
        dim detailIndex
        detailIndex = 0
        do
            detailIndex = detailIndex + 1
            
            dim DetailLongitude
            dim DetailLatitude
            dim DetailName
            DetailName = trim("" & rsDetail("DetailName"))
            DetailLongitude = trim("" & rsDetail("DetailLongitude"))
            DetailLatitude = trim("" & rsDetail("DetailLatitude"))
            
            Response.Write "['"& DetailName &"', "& DetailLatitude &", "& DetailLongitude &", "& detailIndex &"]"

            RsDetail.movenext
            if RsDetail.eof then
                exit do
            end if
            Response.Write ","
        loop
        Response.Write("];"&vbCrLf)
        
        '// delay 1 second to show each google map.
        Response.Write("setTimeout(""initialize_map('map_canvas_"& Index &"', locations_"& Index &");"", "& (clng(Index) * 1000)+3000 &");"&vbCrLf)
        Response.Write("</script>"&vbCrLf)
        RsDetail.movefirst
    end if
    myRS.movenext
    if myRS.eof then
        exit do
    end if
loop




2012年8月9日 星期四

[Asp].中文網域 doamain name (Punycode)

客戶反應一個問題, 要提供一個 URL在網頁裡, 由於包含中文字, 所以中文字被被編碼了.

要連到 URL:

https://law.全國法規.tw/

編修存檔後,透過 IE 瀏覽器, URL 點下去後變成(無法顯示網頁), 神奇的 google  chrome 可以連!

全國法規, UTF-8 編碼做 URLEncode 如下, IE 不能連.
https://law.%E5%85%A8%E5%9C%8B%E6%B3%95%E8%A6%8F.tw/

全國法規, Big5 編碼做 URLEncode 如下, IE 不能連.
https://law.%A5%FE%B0%EA%AAk%B3W.tw/

https://law.%A5%FE%B0%EA%AA%6B%B3%57.tw/


查了一下, 原來中文網域名要轉成 punycode 才行,
隨手下載了一個 ASP 版的轉換程式.
Punycode / IDN conversion code for classic ASP
http://www.simpledns.com/outbox/idn-convert-asp.zip

剛好遇到 "規" 這個字在使用 ASCW( ) 函數是會變成負數. 會出問題, trace 一下程式碼, 改改修修, 弄不出來, 正確解答應該轉換成:
https://law.xn--15q40leqvqi0a.tw/

可是我跑出來的結果有出入, 15q40l 變成 15q50l 實是在很無言, 又看不懂程式在做什麼. 於是改用 .net 來解決. 隨手寫了一個 .net 的 COM+. 最後當然是 Encode 轉換成功, 測試用的程式碼如下:



如果您的 IE 想讓 URL 從 Punycode 的符號變成中文字的話, 需要這樣子設定.





相關文章:
如何利用 IE 或 Firefox 查詢中文域名轉碼(Punycode)
http://blog.miniasp.com/post/2011/07/07/How-to-use-IE-and-Firefox-query-Punycode.aspx

Punycode
http://anferneehardaway.pixnet.net/blog/post/4946513-punycode

[Asp] Server.URLEncode 和 encodeURIComponent 的差別
http://maxtellyou.blogspot.tw/2012/07/asp-serverurlencode-encodeuricomponent.html


使用ASP呼叫C#寫的COM元件
http://maxtellyou.blogspot.tw/2012/03/aspccom.html

如何用 C# 開發的 DLL 讓 VB6 可以使用
http://smilelight-tw.blogspot.tw/2011/06/c-dll-vb6.html

2012年8月6日 星期一

[Asp].透過javascript暫時騙過 codeSecure 白箱檢測的方法

直接從 request 取得值, 再做簡單的單引號處理, 再串成一組的SQL Command 是不能透過白箱檢測. 目前他還沒檢測的出來 asp 的 vbscript + javascript 同時存在的code, 例如:
<%
Function ezPkAssign(byref op, byval val)
    call maxInputSwitch(val)
    '// ... vbscript code here
%><script language="javascript" type="text/javascript" runat="server">
  function maxInputSwitch(assignValue){
    try{
      //... javascripe code where
    } catch(e){
    }
  }
</script><%
End Function
%>


我猜, javascript 的部份應該暫時被誤判為 client side 所以沒檢查出來, 也許下一個版本就不能這樣子用.

針對"舊的程式" 暫時...就這樣子先擋一下吧, 之後"新開發的程式" 應該還是要用 parameter 的方式來組合SQL cmd 即可輕鬆過白箱檢測.

2012年7月6日 星期五

傳統ASP字串效能改善(A Fast String Builder Class For Classic ASP)

我寫了一支資料匯出的程式, 資料量才 988 筆, 可是居然花了60秒才跑的完, trace 程式後發現是大量字串相加造成的問題.


程式修改之前, 資料 979到987 就需要花掉1秒中,
即1秒只能處理 9筆資料.


程式修改之前, 第1秒可以處理 93筆資料, 
由於字串變大而且重覆相加造成效能變差.


在不修改架構的情況下, 只修改字串相加的地方改用字串物件,
第1秒可以處理到 141筆資料, 而且對於後續資料處理的速度滿固定的,
 每秒都可以處理 140筆左右.


修改架構, 不透過 function 傳回字串, 而是所有的程式存取同一個字串物件,
處理速度可以到1秒 208筆資料, 原本處理60秒才能跑完的 988筆資料,
修改後5秒內可以跑完.




使用 Asp String Builder Object 來處理ASP的程式範例:

    dim oString
    Set oString = New ASPStringBuilder
    oString.Append "要相加的字串內容"
    myString = oString.ToString()
    set oString = nothing


在 asp 改用 .net 的StringBuilder 來處理, 速度上差不多,  也是5秒內跑完:
心得:
字串處理, 的問題應該解決了, 要克服其他的(程式流程或寫法), 才能讓反應速度再上去...



使用.net 的StringBuilder 來處理, ASP的程式範例:

    dim oString
    Set oString = Server.CreateObject("Max.StringBuilderObject")
    oString.Append "要相加的字串內容"
    myString = oString.ToString()
    set oString = nothing





相關文章:
A Fast String Builder Class For Classic ASP

字串物件 source code 下載:


google keyword:
classic asp stringbuilder


附註: 
應該有其他更好的解決辦法....

2012年5月21日 星期一

Asp 中強迫輸出 BOM 字元的方式

做了一個匯出資料的功能, 但產生出來的 Utf-8 編碼 csv 檔, 用 Excel 檔開啟都會是亂碼, 他應該是誤以為是 ANSI 格式, 這次的目的是要讓 Excel  正常開啟 Utf-8 編碼 csv 檔.


如果是 asp.net 的話可以透過:

protected void Page_PreRender(object sender, EventArgs e) 
{ 
    Response.BinaryWrite(new byte[] { 0xEF, 0xBB, 0xBF }); 
}


來產生, utf-8 識別用的(BOM)字元.


如果是  php 的話, 就是:
fwrite($fp, "\xEF\xBB\xBF".$output)


如果是 asp 的話, 可以透過下列的函數來丟出 BOM 字元,

sub SendHex(valHex)
    for cHex = 1 to Len(valHex) step 2
        Response.BinaryWrite ChrB(CByte("&H" & Mid(valHex,cHex,2)))
    next
end sub

我比較建議使用下面這個方法:

    Response.CodePage = 65001
    Response.BinaryWrite(chrb(239))
    Response.BinaryWrite(chrb(187))
    Response.BinaryWrite(chrb(191))



另一個解法是透過 Server.CreateObject("ADODB.Stream") 物件來增加(或移除) BOM 字元, 參考看看: http://www.andmm.cn/post/48.html


我是透過ADODB.Stream 來處理, 最後用 Excel 開啟 csv 檔就正常了.

2012年3月30日 星期五

使用ASP呼叫C#寫的COM元件

相關文章,請 google keyword: c# 建立 com dll asp create object

目前專案有一個需求, 要把舊系統搬到ipv6 的環境下運作, 其中一個遇到的問題就是ftp 元件, 這次的目標是要寫一個跟舊系統一模一樣的 COM.


●【 Step 1 】
┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
┃ 先上 http://www.codeproject.com 取得一個別人寫好的 ftpclient class.
請 google keyword: codeproject ftpclient


●【 Step 2 】
┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
┃ 參考看看這篇教學文章:
┃ → http://smilelight-tw.blogspot.com/2011/06/c-dll-vb6.html
◆ 依照教學文章裡的步驟, 在 Assembly Information 裡的 COM-Visible checkbox 打勾.
◆ 設置(Build)項目屬性裡, 把註冊 COM Interop checkbox 打勾.


●【 Step 3 】
┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
┃ 上面文章沒有使用 簽名(Signing) 的功能, 所以在其他電腦註冊 com 元件時會出現下面的提示訊息:



◆ 解法很簡單, 請執行 sn.exe 指令, 產生簽名檔:
"C:\PROGRA~1\Microsoft SDKs\Windows\v7.0A\bin\sn.exe" -k c:\yourAppName.snk

◆ 在專案設定裡的 簽名(Signing) 分頁中選取剛才產生的 yourAppName.snk 文件.


●【 Step 4 】
┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
┃ 最後一個步驟了, 註冊C#寫的DLL是不能用regsvr32的,要用regasm,格式為:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe /codebase  c:\yourProject\yourAppName.dll

如果程式寫錯, 要移除可以下指令:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm.exe /unregister c:\yourProject\yourAppName.dll






━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ 結論 ◆
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 幾乎大部份的 .net 寫的 class 都可以轉換成 com 供 asp 呼叫.
2. 請避免使用「多形」(Polymorphism)的 method, 舊系統對多形的支援度似乎不太好.
3. 請避免在「建構方法(建構式)」(Constructor)裡使用參數, 舊系統對建構式的支援度似乎不太好.
4. 可以使用在 ipv6 上的 ftp com 寫好了.

2012年3月28日 星期三

出現在 HTTP_USER_AGENT 的 Blind SQL Injection 漏洞

出現在 HTTP_USER_AGENT 的 Blind SQL Injection 漏洞, 理論上這個情況是不會發現, 如果把 user agent 當作是一個 input, 而且執行的 web app "直接" 把 user-agent 的值 串成 sql 來執行, 就有可能發生 sql injection.

檢測報告的畫面:



修改方式, 透過下面的副程式把程式停掉, 或修改執行的流程即可.

sub checkHTTP_USER_AGENT()
dim isMatchPattern
isMatchPattern = not True

dim HTTP_USER_AGENT
HTTP_USER_AGENT = trim("" & Request.ServerVariables("HTTP_USER_AGENT"))

if not isMatchPattern then
if instr(HTTP_USER_AGENT,"'") > 0 then
isMatchPattern = true
end if
if instr(HTTP_USER_AGENT,"""") > 0 then
isMatchPattern = true
end if
end if

if isMatchPattern then
call EndAsp()
end if
end sub

2012年3月14日 星期三

char 0 在檔案上傳時對附檔名的影響

●【資料來源】
 ┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 ┃ → http://wenku.baidu.com/view/362e2fd028ea81c758f578b5.html
 ┃ → http://www.hack50.com/stu/sort091/sort0103/63093.html


 ●【WinSock Expert 工具下載】
 ┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 ┃ → http://www.dxqsoft.com/we/index.htm


 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ◆ 簡介 ◆
 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
一般的程式語言裡, 對字串的定義是遇到 “\0”(00)時, 就代表字串結束, 在 \0 (00) 之後的符號都會被乎略(刪除), 由於上傳檔案時可以修改 client 端的檔案名稱, 加入 \0, 讓上傳的程式誤判要上傳的檔案的附檔名.


 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ◆ 上傳元件測試 ◆
 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ●【TABS.Upload】
 ┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 ◎ <source code>
 ┃
response.write "<br/>(upload object) filname: " & Form.FileName & vbcrlf
response.write "<br/>(upload object) filname len: " & len(Form.FileName) & vbcrlf
response.write "<br/>(upload object) filname binary len: " & lenb(Form.FileName) & vbcrlf
response.write "<br/>(upload object) filname right 4 char: " & right(Form.FileName,4) & vbcrlf
fullfilename = Form.FileName
response.write "<br/>(asp variable) filname right 4 char: " & right(cstr(fullfilename),4) & vbcrlf
 ┃
 ◎ <執行結果>
 ┃

說明: chr(0) 攻擊無效, 程式判斷 user 上傳的檔案是 .asp


 ●【UpDownExpress.FileUpload】
 ┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 ◎ <source code>
 ┃
response.write "<br/>current upload object: UpDownExpress.FileUpload" & vbcrlf
response.write "<br/>(upload object) filname: " & xup.Attachments(1).FileName & vbcrlf
response.write "<br/>(upload object) filname len: " & len(xup.Attachments(1).FileName) & vbcrlf
response.write "<br/>(upload object) filname binary len: " & lenb(xup.Attachments(1).FileName) & vbcrlf
response.write "<br/>(upload object) filname right 4 char: " & right(xup.Attachments(1).FileName,4) & vbcrlf
fullfilename = xup.Attachments(1).FileName
response.write "<br/>(asp variable) filname right 4 char: " & right(cstr(fullfilename),4) & vbcrlf
 ┃
 ◎ <執行結果>
 ┃

說明: chr(0) 攻擊無效, 程式判斷 user 上傳的檔案是 .asp


 ●【ADODB.Stream】
 ┃  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
 ◎ <source code>
 ┃
response.write "<br/>(upload object) filname: " & xup.Form(uItem).FileName & vbcrlf
response.write "<br/>(upload object) filname right 4 char: " & right(xup.Form(uItem).FileName,4) & vbcrlf
response.write "<br/>(upload object) filname: " & xup.Form(uItem).FileName & vbcrlf
response.write "<br/>(upload object) filname len: " & len(xup.Form(uItem).FileName) & vbcrlf
response.write "<br/>(upload object) filname binary len: " & lenb(xup.Form(uItem).FileName) & vbcrlf
response.write "<br/>(upload object) filname right 4 char: " & right(xup.Form(uItem).FileName,4) & vbcrlf
fullfilename = xup.Form(uItem).FileName
response.write "<br/>(asp variable) filname right 4 char: " & right(cstr(fullfilename),4) & vbcrlf
 ┃
 ◎ <執行結果>
 ┃

說明: chr(0) 攻擊有效, 程式無法判斷 user 上傳的檔案是 .asp


 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ◆ 結論 ◆
 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1.透過 ADODB.Stream 自行開發的 upload component 需要針對 filename 多增加 chr(0) 的處理.
2.目前是透過 IIS 6 做測試, 比較舊的 IIS 5沒試過, 不確定測試結果是否一樣.
3.其他的程式語言, 沒試過, 不確定測試結果是否一樣.

2012年1月9日 星期一

[Asp].處理 json 中的中文字編碼

前言: 愈來愈多 AP, 除了可以使用 xml 做介接, 也開始支援 json 格式, 例如: jQuery, facebook api, php 等.

jSon 也支援大部份的程式語言,
參考看看 URL: http://www.json.org/

如果收到的 json 字串是: \u5922\u5e7b\u68d2\u58d8\u793e
要怎麼知道對應到的中文字是什麼?


程式碼:

* 說明: 透過 getElement() 即可取得欄位裡的內容.


執行結果:




=============================================
* 備註: 範例中的 json 和 base.asp 是取自於 ASP Xtreme Evolution:
URL: http://zend.lojcomm.com.br
Written by Fabio Zendhi Nagao

[Asp].自動上傳影片到 youtube的範例.

step 1: 利用 google帳號及取得developerKey
URL: http://code.google.com/apis/youtube/dashboard/


step 2: 把取得的 developerKey 寫入我們的設定檔.


說明: 由於這個年代沒有人在寫 Asp 了, 反正程式的邏輯是一樣的, 你可以下載你看的懂的 sample code 下來看:
https://developers.google.com/youtube/code_samples



step 3: 透過 max牌 youtube object, 測試看看把本機的檔案傳上 youtube.

* 參數說明: videoPath(本機路徑) + videoTitle(標題) + descript(說明)


step 4: 測試可以用之後, 把 youtube object 放到開發中的專案裡, 上傳前 youtube 影片數=0.




step 5: 上傳影片, 上傳後 youtube 影片數=1.



  • * 附註1: 由於 youtube 處理縮圖需要時間, 30秒~90秒不一定, 而且還要視上傳的影片內容的編碼方式和大小而定. 
  • * 附註2: 關於處理縮圖的時間點, 比較好的解法是, 透過程式去檢查 Video API, 取得 video status, 等youtube 處理完成後, 再去取縮圖, 這個我還沒去實作, (偷懶的解法) 取縮圖的部份修改為: 等 user 完成整個文檔的編輯, 並按下 submit 後, 再從 youtube 來取縮圖.


[Asp].http to disk 把URL 裡的文件存到硬碟裡

假設有一個 Http 的 URL 裡的文件(例如: .doc , .pdf , .jpg 或是 .asp) 要把URL 裡的檔案存下來本機的硬碟裡, 可以透過這一個副程式: httpUrlToDisk(URL, filePath)


這個副程式的原理, 就是透過副程式 saveReponseToFile 把 binary array 存到 Disk 裡.

2011年12月19日 星期一

[Asp].queryString 阻擋單引號比較好的解法

假設您有2道關卡, 來阻擋 hacker 的 sql injection,
第1道關卡: 寫在所有的 include 檔的第1行裡, 固定會執行.
第2道關卡: 寫在,每一個 request 指令之後, 用來判斷接收到的變數內容的正確及合法性.

* 附註: 聽說攻擊的方式, 不只有用單引號, 所以其實第1道關卡不是必須的, 但一定要在每一程式寫好第2道關卡進行檢查.


由於在 querystring 裡, %27 與 ' (單引號) 是不同的, 但直接擋掉 %27 感覺又怪怪的, 不知道會不會擋到不知名的中文字. 所以之前寫:
if instr(request.querystring(),"%27") > 0 then
...
end if


置換成:
if isQueryStringContainQuota() then
...
end if



'// purpose: check is querystring Contains quota.
'// ex: ret = isQueryStringContantQuota()
'// return:
' True: Found!
' False: not Found.
function isQueryStringContainQuota()
dim returnValue
returnValue = not True '// default: not found.

dim strSingleQuota
strSingleQuota = "'"

dim qItem
for each qItem in request.querystring
if instr(trim("" & request.querystring(qItem)),strSingleQuota) > 0 then
returnValue = True
exit for
end if
next

isQueryStringContainQuota = returnValue
end function

2011年12月9日 星期五

單引號' 在 querystring() 裡與 %27 是不同的

有用防火牆不一定安全, 因為有些攻擊的方法, 一樣是走 80 port 進來您的系統後, 再做滲透.
使用外部的 filter 來擋 SQL Injection 的單引號, 也不一定安全, 因為 filter 可能沒寫好, 如果寫好, 可能會安全一點點.

最近在看 IIS Log, 有一支 demo-1.asp 程式裡的 id 欄位, 忘了增加前置檢查, 直接去 access databae, 結果..., 現在發現的2個小問題:
1. Site 1 傳回給 user 的 status, 居然是 302, 而不是 200.
2. Site 1 的 filter 功能沒擋成功, 把單引號丟給實際 Access Database 的程式.




架構說明:
-----------------
Site 1 的 demo-1.asp 是在與 User 的 browser 的互動, 裡面沒有寫程式, 單純的做單引號的過濾, 就去呼叫 Site 2 的 demo-2.asp



Site 1 (與User browser 互連的程式) 的 IIS log
---------------------------------
201X-XX-XX 17:03:14 GET /demo-1.asp id=-1%27%20or%20%273%27%3d%273 80 - xxx.xxx.xxx.xxx Mozilla/4.0+(compatible;+MSIE+8.0;+Windows+NT+6.0) 302 0 0


Site 2 (實際程式) 的 IIS log
---------------------------------
201X-XX-XX 17:03:14 GET /demo-2.asp id=-1%27%20or%20%273%27%3d%273&|316|80040e07|將_nvarchar_值_'-1'_or_'3'='3'_轉換成資料類型_int_時,轉換失敗。 80 - 127.0.0.1 Mozilla/4.0+(compatible;+MSIE+5.00;+Windows+98) 500 0 0



手動地開啟 chrome 在 Site 1 上做測試,
輸入單引號, 有被程式判斷到, 並導回首頁. status=200
---------------------------------
2011-12-09 02:59:41 GET /demo-1.asp id=-1'%20or%20'3'='3 80 - 10.10.x.x Chrome/17.0.963.0 200 0 0


輸入單引號 (%27), 有被程式判斷到, 並導回首頁. status=200
---------------------------------
2011-12-09 03:10:24 GET /demo-1.asp id=1%27%20or%20%273%27 80 - 10.10.x.x Chrome/17.0.963.0 200 0 0


* 附註: 有問題的地方在, 為什麼之前的 IIS 的 log , status=302 而不是 200.


接下來, 我發現 request.querystring() 裡 %27 並不等於 ' (單引號),
測試的程式如下:
if instr(request.querystring(),"'") > 0 then
response.write "querystring found ' sign!"
else
response.write "pass ' sign query string check."
end if

if instr(request.querystring(),"%27") > 0 then
response.write "querystring found %27 sign!"
else
response.write "pass %27 query string check."
end if

URL 裡輸入 id=', 可以被第1個 if 判斷到, 但無法通過第2個 if 判斷.
URL 裡輸入 id=%27, 可以被第2個 if 判斷到, 但無法通過第1個 if 判斷.

* 附註: 如果您是使用 request("id") 的話, 會取得的是 ' 而不是 %27. (合乎常理).


結論1: 關於 querystring 的部份, 建議增加一個 %27 的判斷.

結論2: 如果要在第1關的 filter 裡擋單引號的話, 用 for each 來一個個的把 form 的資料 request 出來後, 再做判斷, 可能會擋的比較確實.

結論3: 每一支程式, 的每一個 request 應該都要詳細檢查要放進去 database 處理的變數是否為乾淨的(沒有被Hacker修改).

Facebook 留言板