VFPCGI Day8

出自VFP Wiki

(修訂版本間差異)
跳轉到: 導航, 搜尋
 
第1行: 第1行:
[[Category:VFPCGI]]
[[Category:VFPCGI]]
=====VFPCGI的第八天=====
=====VFPCGI的第八天=====
-
上次還沒實做關於 POST 的部份,所以這次延續上次的例子,並且做了一些擴充,這樣可以讓你很清楚怎麼取 GET 與 POST 來的資料。
+
當你知道規則之後,很快就能寫出一個類似 Request 的物件了~
-
 
+
所以我們的 cgilib.prg 又多了一個 Request 類別:
-
這裡我添加了一個 form,這是用來放表單資料的,再按下 submit 之後,你會在表單的下方看到讀到的結果。這邊也利用了 text...endtext 來作為一個 template 產生器,你可以看到 text...endtext 可以很方便的同時寫 html,並且在裡面嵌入變數(用<<與>>把變數括起來)。
+
-
 
+
<pre>
<pre>
-
*
+
DEFINE CLASS REQUEST as Custom
-
* cgi05
+
TotalBytes = 0
-
*
+
RequestMethod = ""
-
 
+
ADD OBJECT QueryString AS collection
-
SET PROCEDURE TO cgilib
+
ADD OBJECT FormField AS collection
-
DECLARE INTEGER GetStdHandle in Win32API integer nHandleType
+
ADD OBJECT Cookies as collection
-
declare integer ReadFile in Win32API integer hFile, string @ cBuffer,;
+
-
integer nBytes, integer @ nBytes2, integer @ nBytes3
+
PROCEDURE INIT
-
 
+
DECLARE INTEGER GetStdHandle in Win32API integer nHandleType
-
LOCAL oResponse
+
declare integer ReadFile in Win32API integer hFile, string @ cBuffer,;
-
oResponse = CREATEOBJECT( "RESPONSE" )
+
integer nBytes, integer @ nBytes2, integer @ nBytes3
-
 
+
-
LOCAL cRequestMethod, cInput
+
LOCAL lcMethod
-
cRequestMethod = UPPER( GETENV( "REQUEST_METHOD" ) )
+
LOCAL lcQueryString
-
 
+
LOCAL lcContentLength, lnContentLength
-
cInput = "&lt;p&gt;REQUEST_METHOD=" + cRequestMethod + "&lt;/p&gt;"
+
-
 
+
-
DO CASE
+
-
CASE cRequestMethod == "GET"
+
-
cInput = cInput + GETENV( "QUERY_STRING" )
+
-
CASE cRequestMethod == "POST"
+
-
&&lenght of input string in STDIN is in this environment variable
+
-
LOCAL lcContentLength, lnContentLength  
+
-
LOCAL lnHandle
+
LOCAL lcInput
LOCAL lcInput
LOCAL lnOverlappedIO
LOCAL lnOverlappedIO
 +
LOCAL lnInHandle
-
lcContentLength = GETENV("CONTENT_LENGTH")
+
lcMethod = UPPER( GETENV( "REQUEST_METHOD" ) )
-
IF LEN( lcContentLength ) &gt; 0 THEN
+
this.RequestMethod = lcMethod
-
lnContentLength=VAL( lcContentLength )
+
lcQueryString = ""
-
ELSE
+
-
lnContentLength = 0
+
-
ENDIF
+
-
 
+
-
IF lnContentLength &gt; 0 THEN
+
-
&&get the input from STDIN
+
-
lnInHandle=GetStdHandle(-10)
+
-
lcInPut=REPLICATE(' ', lnContentLength )
+
-
lnOverlappedIO=0
+
-
ReadFile(lnInHandle, @lcInPut, lnContentLength, @lnContentLength, @lnOverlappedIO)
+
-
ELSE
+
-
lcInput=''
+
-
ENDIF
+
-
cInput = cInput + lcInput
+
-
OTHERWISE
+
-
cInput = ""
+
-
ENDCASE
+
-
 
+
-
lcHtml = ""
+
-
TEXT TO lcHtml NOSHOW ADDITIVE TEXTMERGE
+
-
&lt;script language="javascript"&gt;
+
-
function method_change( form )
+
-
{
+
-
switch ( form.cboMethod.value ) {
+
-
case "GET": form.method = "GET"; break;
+
-
case "POST": form.method = "POST"; break;
+
-
}
+
-
}
+
-
&lt;/script&gt;
+
-
&lt;form method="get" action="vfpcgi.exe"&gt;
+
-
RequestMethod: &lt;select name="cboMethod" onChange="return method_change(this.form);"&gt;
+
-
&lt;option value="GET" &lt;&lt;IIF(cRequestMethod=="GET", "selected", "")&gt;&gt; &gt;GET&lt;/option&gt;
+
-
&lt;option value="POST" &lt;&lt;IIF(cRequestMethod=="POST", "selected", "")&gt;&gt; &gt;POST&lt;/option&gt;
+
-
&lt;/select&gt;&lt;br/&gt;
+
-
&lt;input type="text" name="txt" value=""/&gt;
+
-
&lt;select name="cbo"&gt;
+
-
&lt;option value="0" selected&gt;0&lt;/option&gt;
+
-
&lt;option value="1"&gt;1&lt;/option&gt;
+
-
&lt;option value="2"&gt;2&lt;/option&gt;
+
-
&lt;/select&gt;&lt;br/&gt;
+
-
&lt;input type="submit" value="Submit"/&gt;
+
-
&lt;input type="reset" value="Reset"/&gt;
+
-
&lt;/form&gt;
+
-
&lt;pre&gt;
+
-
&lt;&lt;cInput&gt;&gt;
+
-
&lt;/pre&gt;
+
-
ENDTEXT
+
-
 
+
-
oResponse.Write( lcHtml )
+
-
</pre>
+
-
 
+
-
同時,也對上次的 Response 類別作一點修正,在上次 Response 類別裡面,用來作為 cache 的 cursor,其資料欄位是 varchar(254),換言之,每次 Write 時,只能塞入 254 個字元,如果超過的話,就完蛋了。這次我把她修正為 M,也就是 Memo 型態。
+
-
 
+
-
<pre>
+
-
DEFINE CLASS RESPONSE as Custom
+
-
bDirty = .F.
+
-
bHeaderOut = .F.
+
-
ADD OBJECT Headers AS collection
+
-
+
-
PROCEDURE Init
+
-
CREATE CURSOR outputCache ( outLine M )
+
-
ENDPROC
+
-
+
-
PROCEDURE destroy
+
-
IF this.bDirty == .T.
+
-
this.flush()
+
-
ENDIF
+
-
USE IN outputCache
+
-
ENDPROC
+
-
+
-
PROCEDURE Write
+
-
LPARAMETERS theHtml
+
-
LOCAL lcOutput
+
-
INSERT INTO outputCache values( theHtml )
+
DO CASE
-
this.bDirty = .t.
+
CASE INLIST( lcMethod , "GET" )
-
RETURN
+
* get environment variable: QUERY_STRING
-
ENDPROC
+
lcQueryString = GETENV( "QUERY_STRING" )
-
+
this.ParseData( lcQueryString, this.QueryString )
-
HIDDEN PROCEDURE InternalWrite
+
CASE INLIST( lcMethod, "POST" )
-
LPARAMETER lcOutput
+
&&lenght of input string in STDIN is in this environment variable
 +
lcContentLength = GETENV("CONTENT_LENGTH")
 +
IF LEN( lcContentLength ) > 0 THEN
 +
lnContentLength=VAL( lcContentLength )
 +
ELSE
 +
lnContentLength = 0
 +
ENDIF
-
DECLARE INTEGER GetStdHandle in Win32API integer nHandleType
+
IF lnContentLength > 0 THEN
-
declare integer WriteFile    in Win32API integer hFile, string @ cBuffer,;
+
&&get the input from STDIN
-
integer nBytes, integer @ nBytes2, integer @ nBytes3
+
lnInHandle=GetStdHandle(-10)
 +
lcInPut=REPLICATE(' ', lnContentLength )
 +
lnOverlappedIO=0
 +
ReadFile(lnInHandle, @lcInPut, lnContentLength, @lnContentLength, @lnOverlappedIO)
 +
ELSE
 +
lcInput=''
 +
ENDIF
-
LOCAL lnOutHandle
+
IF LEN( lcInput ) > 0 THEN
-
LOCAL lnBytesWritten
+
this.ParseData( lcInput, this.FormField )
-
LOCAL lnOverLappedIO
+
ENDIF
-
+
OTHERWISE
-
lnOutHandle=GetStdHandle(-11)  
+
ENDCASE
-
lnBytesWritten=0
+
-
lnOverLappedIO=0
+
-
WriteFile(lnOutHandle, @lcOutput, len(lcOutput), @lnBytesWritten, @lnOverLappedIO)
+
ENDPROC
ENDPROC
-
PROCEDURE flushHeader
+
HIDDEN PROCEDURE ParseData
-
LOCAL lcDefaultOutput
+
LPARAMETERS cInput as String , oCollection as Collection
-
LOCAL lcOutput
+
LOCAL i, nStart, nPos, nEqualPos
-
LOCAL lcKey
+
LOCAL lcStr, lcKey, lcValue
-
local i
+
-
lcDefaultOutput=""
+
* parse it.
-
lcOutput = ""
+
i = 1
-
 
+
nStart = 1
-
FOR i = 1 TO this.Headers.Count
+
nPos=AT( "&", cInput, i )
-
lcKey = this.Headers.GetKey( i )
+
IF nPos == 0 THEN
-
IF !EMPTY( lcKey ) THEN
+
nPos = LEN( cInput )
-
lcOutput = lcOutput + lcKey + ": " + this.Headers.Item(i) + CHR(13) + CHR(10)  
+
ENDIF
 +
DO WHILE nPos!=0
 +
lcStr = SUBSTR( cInput, nStart, nPos-nStart )
 +
nEqualPos = AT( "=", lcStr )
 +
IF nEqualPos!=0 THEN
 +
lcKey = SUBSTR( lcStr, 1, nEqualPos-1 )
 +
lcValue = SUBSTR( lcStr, nEqualPos+1 )
 +
oCollection.Add( lcValue, lcKey )
 +
ENDIF
 +
i=i+1
 +
nStart = nPos + 1
 +
nPos=AT( "&", cInput, i )
 +
ENDDO
 +
IF nStart < LEN(cInput) THEN
 +
nPos = LEN( cInput )
 +
lcStr = SUBSTR( cInput, nStart )
 +
nEqualPos = AT( "=", lcStr )
 +
IF nEqualPos!=0 THEN
 +
lcKey = SUBSTR( lcStr, 1, nEqualPos-1 )
 +
lcValue = SUBSTR( lcStr, nEqualPos+1 )
 +
oCollection.Add( lcValue, lcKey )
ENDIF  
ENDIF  
-
NEXT
 
-
 
-
IF this.Headers.getKey( "Content-type" ) == 0 THEN
 
-
lcOutput = lcOutput + "Content-type: text/html"+chr(13)+chr(10)
 
-
ENDIF
 
-
 
-
lcOutput = lcOutput + chr(13)+chr(10)
 
-
this.InternalWrite( lcDefaultOutput + lcOutput )
 
-
this.bHeaderOut = .T.
 
-
ENDPROC
 
-
 
-
PROCEDURE flush
 
-
IF this.bDirty == .f. then
 
-
RETURN
 
ENDIF  
ENDIF  
-
 
+
ENDPROC  
-
LOCAL lcAlias
+
-
LOCAL lcOutput
+
-
+
-
IF this.bHeaderOut == .F. THEN
+
-
this.flushHeader()
+
-
ENDIF
+
-
+
-
lcAlias = ALIAS()
+
-
SELECT outputCache
+
-
GO top
+
-
SCAN
+
-
lcOutput = outputCache.outLine
+
-
this.InternalWrite( lcOutput )
+
-
ENDSCAN
+
-
SELECT( lcAlias )
+
-
ENDPROC
+
ENDDEFINE
ENDDEFINE
</pre>
</pre>
-
同樣的,新增 cgi05 以後,將她設置為主程式,重新編譯之後,再丟到對應路徑即可。
+
Request 這部份是最容易有問題的地方了~因為安全的漏洞往往來自於此。
 +
也正式因為如此,才會有人說,永遠不要相信使用者輸入的資料,無論如何都要作詳細的檢查。
 +
早期多半用 C 語言來寫 CGI 的時候,這邊常會犯的錯誤是,沒有預留足夠的空間來放這些輸入的字串,這也導致了 hacker 可以用緩衝區溢位的方式來進行攻擊。
 +
 
 +
我們這邊做的比較簡單,都自己寫程式去 parse 字串,然後放到 collection 裡面去。
 +
依照上個範例的輸出結果,我想你應該知道可以利用 aline() 之類的函數來解,這會更方便些~
 +
 
 +
這邊我們還沒處理 utf-8 與 % 的一些問題....同時也還沒補上 Request 的使用範例...

在2006年11月22日 (三) 14:58的最新修訂版本

VFPCGI的第八天

當你知道規則之後,很快就能寫出一個類似 Request 的物件了~ 所以我們的 cgilib.prg 又多了一個 Request 類別:

DEFINE CLASS REQUEST as Custom 
	TotalBytes = 0
	RequestMethod = ""
	ADD OBJECT QueryString AS collection
	ADD OBJECT FormField AS collection
	ADD OBJECT Cookies as collection 
	
	PROCEDURE INIT
		DECLARE INTEGER GetStdHandle in Win32API integer nHandleType
		declare integer ReadFile in Win32API integer hFile, string @ cBuffer,;
			integer nBytes, integer @ nBytes2, integer @ nBytes3
		
		LOCAL lcMethod
		LOCAL lcQueryString
		LOCAL lcContentLength, lnContentLength
		LOCAL lcInput
		LOCAL lnOverlappedIO
		LOCAL lnInHandle
		
		lcMethod = UPPER( GETENV( "REQUEST_METHOD" ) )
		this.RequestMethod = lcMethod
		lcQueryString = ""
		
		DO CASE 
			CASE INLIST( lcMethod , "GET" )
				* get environment variable: QUERY_STRING
				lcQueryString = GETENV( "QUERY_STRING" )
				this.ParseData( lcQueryString, this.QueryString )
			CASE INLIST( lcMethod, "POST" )
				&&lenght of input string in STDIN is in this environment variable
				lcContentLength = GETENV("CONTENT_LENGTH")
				IF LEN( lcContentLength ) > 0 THEN 
					lnContentLength=VAL( lcContentLength )
				ELSE
					lnContentLength = 0
				ENDIF 

				IF lnContentLength > 0 THEN 
					&&get the input from STDIN
					lnInHandle=GetStdHandle(-10) 
					lcInPut=REPLICATE(' ', lnContentLength )
					lnOverlappedIO=0
					ReadFile(lnInHandle, @lcInPut, lnContentLength, @lnContentLength, @lnOverlappedIO)
				ELSE
					lcInput=''
				ENDIF 

				IF LEN( lcInput ) > 0 THEN
					this.ParseData( lcInput, this.FormField )
				ENDIF 
			OTHERWISE 
		ENDCASE 
	ENDPROC
	
	HIDDEN PROCEDURE ParseData
		LPARAMETERS cInput as String , oCollection as Collection 
		LOCAL i, nStart, nPos, nEqualPos
		LOCAL lcStr, lcKey, lcValue 
		
		* parse it.
		i = 1
		nStart = 1
		nPos=AT( "&", cInput, i )
		IF nPos == 0 THEN 
			nPos = LEN( cInput )
		ENDIF 
		DO WHILE nPos!=0
			lcStr = SUBSTR( cInput, nStart, nPos-nStart )
			nEqualPos = AT( "=", lcStr )
			IF nEqualPos!=0 THEN 
				lcKey = SUBSTR( lcStr, 1, nEqualPos-1 )
				lcValue = SUBSTR( lcStr, nEqualPos+1 )
				oCollection.Add( lcValue, lcKey )
			ENDIF 
			i=i+1
			nStart = nPos + 1
			nPos=AT( "&", cInput, i )
		ENDDO 
		IF nStart < LEN(cInput) THEN 
			nPos = LEN( cInput )
			lcStr = SUBSTR( cInput, nStart )
			nEqualPos = AT( "=", lcStr )
			IF nEqualPos!=0 THEN 
				lcKey = SUBSTR( lcStr, 1, nEqualPos-1 )
				lcValue = SUBSTR( lcStr, nEqualPos+1 )
				oCollection.Add( lcValue, lcKey )
			ENDIF 
		ENDIF 
	ENDPROC 
ENDDEFINE

Request 這部份是最容易有問題的地方了~因為安全的漏洞往往來自於此。 也正式因為如此,才會有人說,永遠不要相信使用者輸入的資料,無論如何都要作詳細的檢查。 早期多半用 C 語言來寫 CGI 的時候,這邊常會犯的錯誤是,沒有預留足夠的空間來放這些輸入的字串,這也導致了 hacker 可以用緩衝區溢位的方式來進行攻擊。

我們這邊做的比較簡單,都自己寫程式去 parse 字串,然後放到 collection 裡面去。 依照上個範例的輸出結果,我想你應該知道可以利用 aline() 之類的函數來解,這會更方便些~

這邊我們還沒處理 utf-8 與 % 的一些問題....同時也還沒補上 Request 的使用範例...