Files
harbour-core/harbour/examples/uhttpd2/umain.prg
Mindaugas Kavaliauskas 508e2ec93f 2010-06-10 19:12 UTC+0300 Mindaugas Kavaliauskas (dbtopas/at/dbtopas.lt)
* harbour/examples/httpsrv/uhttpd.prg
  * harbour/examples/uhttpd2/umain.prg
    * optimized hb_socketRecv() calls
    * optimized (removed) hb_socketSelect() calls, current API
      has timeout parameter and make things simple

  * harbour/examples/commouse/commouse.prg
    ! fixed error reporting (missing hb_comGetError()) parameter

  * harbour/include/hbsocket.ch
    * adjusted comment text

  * harbour/contrib/hbtpathy/telepath.prg
    ! fixed missing nTimeout parameter in tp_send()

  * harbour/source/rtl/hbcomhb.c
    ! fixed typo in hb_comSend()
    ! fixed hb_storni() paramter order bugs
2010-06-10 16:12:39 +00:00

1042 lines
32 KiB
Plaintext

/*
* $Id$
*/
#include "hbclass.ch"
#include "common.ch"
#include "fileio.ch"
#include "error.ch"
#include "hbsocket.ch"
#pragma -kM+
/*
Docs:
RFC 1945 - Hypertext Transfer Protocol -- HTTP/1.0
RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1
HTTP Made Really Easy (http://www.jmarshall.com/easy/http/)
*/
#define THREAD_COUNT_PREALLOC 0
#define THREAD_COUNT_MAX 50
#define SESSION_TIMEOUT 600
#define CR_LF (CHR(13)+CHR(10))
THREAD STATIC s_cResult, s_nStatusCode, s_aHeader, s_lSessionDestroy
MEMVAR server, get, post, cookie, session
CLASS UHttpd
// Settings
DATA nPort INIT 80
DATA cBindAddress INIT "0.0.0.0"
DATA cAccessLog INIT "uhttpd_access.log"
DATA cErrorLog INIT "uhttpd_error.log"
DATA bIdle INIT {|| NIL}
DATA aMount INIT {=>}
// Results
DATA cError INIT ""
// Private
DATA hAccessLog
DATA hErrorLog
DATA hmtxQueue
DATA hmtxLog
DATA hmtxSession
DATA hListen
DATA aSession
DATA lStop
METHOD Run()
METHOD Stop()
// Private
METHOD LogAccess()
METHOD LogError()
ENDCLASS
FUNC UHttpdNew()
RETURN UHttpd()
METHOD Run() CLASS UHttpd
LOCAL hSocket, aRemote, nI, aThreads, aI
LOCAL nWaiters
IF ! HB_MTVM()
Self:cError := "Multithread support required"
RETURN .F.
ENDIF
IF Self:nPort < 1 .OR. Self:nPort > 65535
Self:cError := "Invalid port number"
RETURN .F.
ENDIF
IF (Self:hAccessLog := FOPEN(Self:cAccessLog, FO_CREAT + FO_WRITE)) == -1
Self:cError := "Access log file open error " + LTRIM(STR(FERROR()))
RETURN .F.
ENDIF
FSEEK(Self:hAccessLog, 0, FS_END)
IF (Self:hErrorLog := FOPEN(Self:cErrorLog, FO_CREAT + FO_WRITE)) == -1
Self:cError := "Error log file open error " + LTRIM(STR(FERROR()))
FCLOSE(Self:hAccessLog)
RETURN .F.
ENDIF
FSEEK(Self:hErrorLog, 0, FS_END)
Self:hmtxQueue := hb_mutexCreate()
Self:hmtxLog := hb_mutexCreate()
Self:hmtxSession := hb_mutexCreate()
IF Empty(Self:hListen := hb_socketOpen())
Self:cError := "Socket create error " + LTRIM(STR(hb_socketGetError()))
FCLOSE(Self:hErrorLog)
FCLOSE(Self:hAccessLog)
RETURN .F.
ENDIF
IF !hb_socketBind(Self:hListen, {HB_SOCKET_AF_INET, Self:cBindAddress, Self:nPort})
Self:cError := "Bind error " + LTRIM(STR(hb_socketGetError()))
hb_socketClose(Self:hListen)
FCLOSE(Self:hErrorLog)
FCLOSE(Self:hAccessLog)
RETURN .F.
ENDIF
IF !hb_socketListen(Self:hListen)
Self:cError := "Listen error " + LTRIM(STR(hb_socketGetError()))
hb_socketClose(Self:hListen)
FCLOSE(Self:hErrorLog)
FCLOSE(Self:hAccessLog)
RETURN .F.
ENDIF
aThreads := {}
FOR nI := 1 TO THREAD_COUNT_PREALLOC
AADD(aThreads, hb_threadStart(@ProcessConnection(), Self))
NEXT
Self:lStop := .F.
Self:aSession := {=>}
DO WHILE .T.
IF EMPTY( hSocket := hb_socketAccept(Self:hListen,, 1000) )
IF hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT
EVAL(Self:bIdle, Self)
IF Self:lStop; EXIT
ENDIF
ELSE
Self:LogError("[error] Accept error " + LTRIM(STR(hb_socketGetError())))
ENDIF
ELSE
hb_mutexQueueInfo( Self:hmtxQueue, @nWaiters )
? "New connection", hSocket
? "Waiters:", nWaiters
IF nWaiters < 2 .AND. LEN(aThreads) < THREAD_COUNT_MAX
/*
We need two threads in worst case. If first thread becomes a sessioned
thread, the second one will continue to serve sessionless requests for
the same connection. We create two threads here to avoid free thread count
check (and aThreads variable sync) in ProcessRequest().
*/
AADD(aThreads, hb_threadStart(@ProcessConnection(), Self))
AADD(aThreads, hb_threadStart(@ProcessConnection(), Self))
ENDIF
hb_mutexNotify(Self:hmtxQueue, {hSocket, ""})
ENDIF
ENDDO
hb_socketClose(Self:hListen)
// End child threads
hb_mutexLock(Self:hmtxSession)
HB_HEVAL(Self:aSession, {|k,v| hb_mutexNotify(v[2], NIL), HB_SYMBOL_UNUSED(k)})
hb_mutexUnlock(Self:hmtxSession)
AEVAL(aThreads, {|| hb_mutexNotify(Self:hmtxQueue, NIL)})
AEVAL(aThreads, {|h| hb_threadJoin(h)})
FCLOSE(Self:hErrorLog)
FCLOSE(Self:hAccessLog)
RETURN .T.
METHOD Stop() CLASS UHttpd
Self:lStop := .T.
RETURN NIL
METHOD LogError(cError) CLASS UHttpd
hb_mutexLock(Self:hmtxLog)
FWRITE(Self:hErrorLog, DTOS(DATE()) + " " + TIME() + " " + cError + " " + HB_OSNewLine())
hb_mutexUnlock(Self:hmtxLog)
RETURN NIL
METHOD LogAccess() CLASS UHttpd
LOCAL cDate := DTOS(DATE()), cTime := TIME()
hb_mutexLock(Self:hmtxLog)
FWRITE(Self:hAccessLog, ;
server["REMOTE_ADDR"] + " - - [" + RIGHT(cDate, 2) + "/" + ;
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}[VAL(SUBSTR(cDate, 5, 2))] + ;
"/" + LEFT(cDate, 4) + ":" + cTime + ' +0000] "' + server["REQUEST_ALL"] + '" ' + ;
LTRIM(STR(s_nStatusCode)) + " " + LTRIM(STR(LEN(s_cResult))) + ;
' "' + server["HTTP_REFERER"] + '" "' + server["HTTP_USER_AGENT"] + ;
'"' + HB_OSNewLine())
hb_mutexUnlock(Self:hmtxLog)
RETURN NIL
STATIC FUNC ProcessConnection(oServer)
LOCAL hSocket, cRequest, cSend, aI, nLen, nI, nReqLen, cBuf
PRIVATE server, get, post, cookie
DO WHILE .T.
hb_mutexSubscribe(oServer:hmtxQueue,, @aI)
IF aI == NIL
EXIT
ENDIF
hSocket := aI[1]
cRequest := aI[2]
BEGIN SEQUENCE
/* receive query header */
cRequest := ""
nLen := 1
DO WHILE AT(CR_LF + CR_LF, cRequest) == 0 .AND. nLen > 0
cBuf := Space( 4096 )
IF (nLen := hb_socketRecv(hSocket, @cBuf,,, 10000)) > 0 /* Timeout */
cRequest += LEFT(cBuf, nLen)
ELSE
IF nLen == -1 .AND. hb_socketGetError() == HB_SOCKET_ERR_TIMEOUT
nLen := 0
? "recv() timeout", hSocket
ENDIF
ENDIF
ENDDO
IF nLen == -1
? "recv() error:", hb_socketGetError()
ELSEIF nLen == 0 /* connection closed */
ELSE
// PRIVATE
server := {=>}
get := {=>}
post := {=>}
cookie := {=>}
s_cResult := ""
s_aHeader := {}
s_nStatusCode := 200
IF !Empty( aI := hb_socketGetPeerName(hSocket))
server["REMOTE_ADDR"] := aI[HB_SOCKET_ADINFO_ADDRESS]
server["REMOTE_HOST"] := server["REMOTE_ADDR"] // no reverse DNS
server["REMOTE_PORT"] := aI[HB_SOCKET_ADINFO_PORT]
ENDIF
IF !Empty( aI := hb_socketGetSockName(hSocket))
server["SERVER_ADDR"] := aI[HB_SOCKET_ADINFO_ADDRESS]
server["SERVER_PORT"] := aI[HB_SOCKET_ADINFO_PORT]
ENDIF
? LEFT(cRequest, AT(CR_LF + CR_LF, cRequest) + 1)
nReqLen := ParseRequestHeader(@cRequest)
IF nReqLen == NIL
USetStatusCode(400)
ELSE
/* receive query body */
DO WHILE LEN(cRequest) < nReqLen .AND. nLen > 0
cBuf := Space( 4096 )
IF (nLen := hb_socketRecv(hSocket, @cBuf,,, 500)) > 0
cRequest += LEFT( cBuf, nLen )
ENDIF
ENDDO
IF nLen == -1
? "recv() error:", hb_socketGetError()
ELSEIF nLen == 0 /* connection closed */
ELSE
? cRequest
ParseRequestBody(LEFT(cRequest, nReqLen))
cRequest := SUBSTR(cRequest, nReqLen + 1)
/* Deal with supported protocols and methods */
IF server["SERVER_PROTOCOL"] $ "HTTP/1.0 HTTP/1.1"
IF !(server["REQUEST_METHOD"] $ "GET POST")
USetStatusCode(501)
ELSE
IF server["SERVER_PROTOCOL"] == "HTTP/1.1"
IF LOWER(server["HTTP_CONNECTION"]) == "close"
UAddHeader("Connection", "close")
ELSE
UAddHeader("Connection", "keep-alive")
ENDIF
ENDIF
IF ! ProcessRequest(oServer, hSocket); BREAK
ENDIF
ENDIF
ELSE /* We do not support another protocols */
USetStatusCode(400)
ENDIF
ENDIF
ENDIF
SendResponse(oServer, hSocket)
IF LOWER(UGetHeader("Connection")) == "close" .OR. server["SERVER_PROTOCOL"] == "HTTP/1.0"
ELSE
hb_mutexNotify(oServer:hmtxQueue, {hSocket, cRequest})
BREAK
ENDIF
ENDIF
? "Close connection1", hSocket
hb_socketShutdown(hSocket)
hb_socketClose(hSocket)
END SEQUENCE
ENDDO
DBCLOSEALL()
RETURN 0
STATIC FUNC ProcessRequest(oServer, hSocket, cBuffer)
LOCAL nI, cMount, cPath, cSID, hmtx, aData, bEval
PRIVATE session
// Search mounting table
cMount := server["SCRIPT_NAME"]
IF HB_HHasKey(oServer:aMount, cMount)
cPath := ""
ELSE
nI := LEN(cMount)
DO WHILE (nI := HB_RAT("/", cMount,, nI)) > 0
IF HB_HHasKey(oServer:aMount, LEFT(cMount, nI) + "*")
cMount := LEFT(cMount, nI) + "*"
cPath := SUBSTR(server["SCRIPT_NAME"], nI + 1)
EXIT
ENDIF
nI--
ENDDO
ENDIF
IF cMount != NIL
bEval := oServer:aMount[cMount, 1]
IF oServer:aMount[cMount, 2]
/* sessioned */
IF HB_HHasKey(cookie, "SESSID"); cSID := cookie["SESSID"]
ENDIF
hb_mutexLock(oServer:hmtxSession)
IF cSID == NIL .OR. ! HB_HHasKey(oServer:aSession, cSID)
/* create new session */
cSID := HB_MD5(DTOS(DATE()) + TIME() + STR(HB_RANDOM(), 15, 12))
hmtx := hb_mutexCreate()
oServer:aSession[cSID] := {hb_threadSelf(), hmtx, {=>}}
// PRIVATE
session := oServer:aSession[cSID, 3]
hb_mutexUnlock(oServer:hmtxSession)
DO WHILE .T.
s_cResult := ""
s_aHeader := {}
s_nStatusCode := 200
s_lSessionDestroy := .F.
BEGIN SEQUENCE WITH {|oErr| UErrorHandler(oErr, oServer)}
EVAL(bEval, cPath)
RECOVER
USetStatusCode(500)
END SEQUENCE
IF s_lSessionDestroy
UAddHeader("Set-Cookie", "SESSID=" + cSID + "; path=/; Max-Age=0")
ELSE
UAddHeader("Set-Cookie", "SESSID=" + cSID + "; path=/")
ENDIF
IF server["SERVER_PROTOCOL"] == "HTTP/1.1"
IF LOWER(server["HTTP_CONNECTION"]) == "close"
UAddHeader("Connection", "close")
ELSE
UAddHeader("Connection", "keep-alive")
ENDIF
ENDIF
SendResponse(oServer, hSocket)
IF s_lSessionDestroy
/* Destroy session before closing socket, since graceful close requires some time */
hb_mutexLock(oServer:hmtxSession)
HB_HDel(oServer:aSession, cSID)
hb_mutexUnlock(oServer:hmtxSession)
ENDIF
IF LOWER(UGetHeader("Connection")) == "close" .OR. server["SERVER_PROTOCOL"] == "HTTP/1.0"
? "Close connection2", hSocket
hb_socketShutdown(hSocket)
hb_socketClose(hSocket)
ELSE
/* pass connection to common queue */
hb_mutexNotify(oServer:hmtxQueue, {hSocket, cBuffer})
ENDIF
IF s_lSessionDestroy
EXIT
ENDIF
IF ! hb_mutexSubscribe(hmtx, SESSION_TIMEOUT, @aData) .OR. aData == NIL
? "Session exit"
hb_mutexLock(oServer:hmtxSession)
HB_HDel(oServer:aSession, cSID)
hb_mutexUnlock(oServer:hmtxSession)
EXIT
ENDIF
hSocket := aData[1]
cBuffer := aData[2]
bEval := aData[3]
cPath := aData[4]
server := aData[5]
get := aData[6]
post := aData[7]
cookie := aData[8]
session := aData[9]
aData := NIL
ENDDO
/* close databases and release variables */
DBCLOSEALL()
server := NIL
get := NIL
post := NIL
cookie := NIL
session := NIL
ELSE
/* session already exists */
? "session pries", server["SCRIPT_NAME"]
hb_mutexNotify(oServer:aSession[cSID, 2], {hSocket, cBuffer, oServer:aMount[cMount, 1], cPath, server, get, post, cookie, oServer:aSession[cSID, 3]})
hb_mutexUnlock(oServer:hmtxSession)
ENDIF
RETURN .F.
ELSE
/* not sessioned */
BEGIN SEQUENCE WITH {|oErr| UErrorHandler(oErr, oServer)}
EVAL(bEval, cPath)
RECOVER
USetStatusCode(500)
END SEQUENCE
ENDIF
ELSE
USetStatusCode(404)
ENDIF
RETURN .T.
STATIC FUNC ParseRequestHeader(cRequest)
LOCAL aRequest, aLine, nI, nJ, cI, nK, nContentLength := 0
aRequest := split(CR_LF, cRequest)
aLine := split(" ", aRequest[1])
server["REQUEST_ALL"] := aRequest[1]
IF LEN(aLine) == 3 .AND. LEFT(aLine[3], 5) == "HTTP/"
server["REQUEST_METHOD"] := aLine[1]
server["REQUEST_URI"] := aLine[2]
server["SERVER_PROTOCOL"] := aLine[3]
ELSE
server["REQUEST_METHOD"] := aLine[1]
server["REQUEST_URI"] := IIF(LEN(aLine) >= 2, aLine[2], "")
server["SERVER_PROTOCOL"] := IIF(LEN(aLine) >= 3, aLine[3], "")
RETURN 0
ENDIF
// Fix invalid queries: bind to root
IF ! (LEFT(server["REQUEST_URI"], 1) == "/")
server["REQUEST_URI"] := "/" + server["REQUEST_URI"]
ENDIF
IF (nI := AT("?", server["REQUEST_URI"])) > 0
server["SCRIPT_NAME"] := LEFT(server["REQUEST_URI"], nI - 1)
server["QUERY_STRING"] := SUBSTR(server["REQUEST_URI"], nI + 1)
ELSE
server["SCRIPT_NAME"] := server["REQUEST_URI"]
server["QUERY_STRING"] := ""
ENDIF
server["HTTP_ACCEPT"] := ""
server["HTTP_ACCEPT_CHARSET"] := ""
server["HTTP_ACCEPT_ENCODING"] := ""
server["HTTP_ACCEPT_LANGUAGE"] := ""
server["HTTP_CONNECTION"] := ""
server["HTTP_HOST"] := ""
server["HTTP_KEEP_ALIVE"] := ""
server["HTTP_REFERER"] := ""
server["HTTP_USER_AGENT"] := ""
server["HTTP_CONTENT_TYPE"] := ""
FOR nI := 2 TO LEN(aRequest)
IF aRequest[nI] == ""; EXIT
ELSEIF (nJ := AT(":", aRequest[nI])) > 0
cI := ALLTRIM(SUBSTR(aRequest[nI], nJ + 1))
SWITCH UPPER(LEFT(aRequest[nI], nJ - 1))
CASE "COOKIE"
IF (nK := AT(";", cI)) == 0
nK := LEN(TRIM(cI))
ENDIF
cI := LEFT(cI, nK)
IF (nK := AT("=", cI)) > 0
/* cookie names are case insensitive, uppercase it */
cookie[UPPER(LEFT(cI, nK - 1))] := SUBSTR(cI, nK + 1)
ENDIF
EXIT
CASE "CONTENT-LENGTH"
nContentLength := VAL(cI)
EXIT
OTHERWISE
server["HTTP_" + STRTRAN(UPPER(LEFT(aRequest[nI], nJ - 1)), "-", "_")] := cI
EXIT
ENDSWITCH
ENDIF
NEXT
IF !(server["QUERY_STRING"] == "")
FOR EACH cI IN split("&", server["QUERY_STRING"])
IF (nI := AT("=", cI)) > 0
get[UUrlDecode(LEFT(cI, nI - 1))] := UUrlDecode(SUBSTR(cI, nI + 1))
ELSE
get[UUrlDecode(cI)] := NIL
ENDIF
NEXT
ENDIF
cRequest := SUBSTR(cRequest, AT(CR_LF + CR_LF, cRequest) + 4)
RETURN nContentLength
STATIC FUNC ParseRequestBody(cRequest)
LOCAL nI, cPart
IF server["HTTP_CONTENT_TYPE"] == "application/x-www-form-urlencoded"
FOR EACH cPart IN split("&", cRequest)
IF (nI := AT("=", cPart)) > 0
post[UUrlDecode(LEFT(cPart, nI - 1))] := UUrlDecode(SUBSTR(cPart, nI + 1))
ELSE
post[UUrlDecode(cPart)] := NIL
ENDIF
NEXT
ENDIF
RETURN NIL
STATIC FUNC MakeResponse()
LOCAL cRet
IF UGetHeader("Content-Type") == NIL
UAddHeader("Content-Type", "text/html")
ENDIF
UAddHeader("Date", HttpDateFormat(HB_DATETIME()))
cRet := IIF(server["SERVER_PROTOCOL"] == "HTTP/1.0", "HTTP/1.0 ", "HTTP/1.1 ")
SWITCH s_nStatusCode
CASE 200
cRet += "200 OK"
EXIT
CASE 301
cRet += "301 Moved Permanently"
s_cResult := "<html><body><h1>301 Moved Permanently</h1></body></html>"
EXIT
CASE 302
cRet += "302 Found"
s_cResult := "<html><body><h1>302 Found</h1></body></html>"
EXIT
CASE 303
cRet += "303 See Other"
s_cResult := "<html><body><h1>303 See Other</h1></body></html>"
EXIT
CASE 304
cRet += "304 Not Modified"
s_cResult := "<html><body><h1>304 Not Modified</h1></body></html>"
EXIT
CASE 400
cRet += "400 Bad Request"
s_cResult := "<html><body><h1>400 Bad Request</h1></body></html>"
UAddHeader("Connection", "close")
EXIT
CASE 401
cRet += "401 Unauthorized"
s_cResult := "<html><body><h1>401 Unauthorized</h1></body></html>"
EXIT
CASE 402
cRet += "402 Payment Required"
s_cResult := "<html><body><h1>402 Payment Required</h1></body></html>"
EXIT
CASE 403
cRet += "403 Forbidden"
s_cResult := "<html><body><h1>403 Forbidden</h1></body></html>"
EXIT
CASE 404
cRet += "404 Not Found"
s_cResult := "<html><body><h1>404 Not Found</h1></body></html>"
EXIT
CASE 412
cRet += "412 Precondition Failed"
s_cResult := "<html><body><h1>412 Precondition Failed</h1></body></html>"
EXIT
CASE 500
cRet += "500 Internal Server Error"
s_cResult := "<html><body><h1>500 Internal Server Error</h1></body></html>"
EXIT
CASE 501
cRet += "501 Not Implemented"
s_cResult := "<html><body><h1>501 Not Implemented</h1></body></html>"
UAddHeader("Connection", "close")
EXIT
OTHERWISE
cRet += "500 Internal Server Error"
s_cResult := "<html><body><h1>500 Internal Server Error</h1></body></html>"
UAddHeader("Connection", "close")
ENDSWITCH
cRet += CR_LF
UAddHeader("Content-Length", LTRIM(STR(LEN(s_cResult))))
AEVAL(s_aHeader, {|x| cRet += x[1] + ": " + x[2] + CR_LF})
cRet += CR_LF
? cRet
cRet += s_cResult
RETURN cRet
STATIC PROC SendResponse(oServer, hSocket)
LOCAL cSend, nLen, cBuf
cSend := MakeResponse()
// ? cSend
DO WHILE LEN(cSend) > 0
IF (nLen := hb_socketSend(hSocket, cSend)) == -1
? "send() error:", hb_socketGetError(), hSocket
EXIT
ELSEIF nLen > 0
cSend := SUBSTR(cSend, nLen + 1)
ENDIF
ENDDO
oServer:LogAccess()
RETURN
STATIC FUNC HttpDateFormat(tDate)
RETURN {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}[DOW(tDate)] + ", " + ;
PADL(DAY(tDate), 2, "0") + " " + ;
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}[MONTH(tDate)] + ;
" " + PADL(YEAR(tDate), 4, "0") + HB_TTOC(tDate, "", "HH:MM:SS") + " GMT" // TOFIX: time zone
STATIC FUNC HttpDateUnformat(cDate, tDate)
LOCAL nMonth
// TODO: support outdated compatibility format RFC2616
IF LEN(cDate) == 29 .AND. RIGHT(cDate, 4) == " GMT" .AND. SUBSTR(cDate, 4, 2) == ", "
nMonth := ASCAN({"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", ;
"Oct", "Nov", "Dec"}, SUBSTR(cDate, 9, 3))
IF nMonth > 0
tDate := HB_STOT(SUBSTR(cDate, 13, 4) + PADL(nMonth, 2, "0") + SUBSTR(cDate, 6, 2) + ;
STRTRAN(SUBSTR(cDate, 18, 8), ":", ""))
RETURN ! EMPTY(tDate)
ENDIF
ENDIF
RETURN .F.
STATIC FUNC UErrorHandler(oErr, oServer)
IF oErr:genCode == EG_ZERODIV; RETURN 0
ELSEIF oErr:genCode == EG_LOCK; RETURN .T.
ELSEIF (oErr:genCode == EG_OPEN .AND. oErr:osCode == 32 .OR. ;
oErr:genCode == EG_APPENDLOCK) .AND. oErr:canDefault
NETERR(.T.)
RETURN .F.
ENDIF
oServer:LogError(GetErrorDesc(oErr))
BREAK(oErr)
RETURN NIL
STATIC FUNC GetErrorDesc(oErr)
LOCAL cRet, nI
cRet := "ERRORLOG ============================================================" + HB_OSNewLine() + ;
"Error: " + oErr:subsystem + "/" + ErrDescCode(oErr:genCode) + "(" + LTRIM(STR(oErr:genCode)) + ") " + ;
LTRIM(STR(oErr:subcode)) + HB_OSNewLine()
IF !EMPTY(oErr:filename); cRet += "File: " + oErr:filename + HB_OSNewLine()
ENDIF
IF !EMPTY(oErr:description); cRet += "Description: " + oErr:description + HB_OSNewLine()
ENDIF
IF !EMPTY(oErr:operation); cRet += "Operacija: " + oErr:operation + HB_OSNewLine()
ENDIF
IF !EMPTY(oErr:osCode); cRet += "OS error: " + LTRIM(STR(oErr:osCode)) + HB_OSNewLine()
ENDIF
IF VALTYPE(oErr:args) == "A"
cRet += "Arguments:" + HB_OSNewLine()
AEVAL(oErr:args, {|X, Y| cRet += STR(Y, 5) + ": " + HB_CStr(X) + HB_OSNewLine()})
ENDIF
cRet += HB_OSNewLine()
cRet += "Stack:" + HB_OSNewLine()
nI := 2
DO WHILE ! EMPTY(PROCNAME(++nI))
cRet += " " + PROCNAME(nI) + "(" + LTRIM(STR(PROCLINE(nI))) + ")" + HB_OSNewLine()
ENDDO
cRet += HB_OSNewLine()
cRet += "Executable: " + HB_PROGNAME() + HB_OSNewLine()
cRet += "Versions:" + HB_OSNewLine()
cRet += " OS: " + OS() + HB_OSNewLine()
cRet += " Harbour: " + VERSION() + ", " + HB_BUILDDATE() + HB_OSNewLine()
cRet += HB_OSNewLine()
IF oErr:genCode != EG_MEM
cRet += "Database areas:" + HB_OSNewLine()
cRet += " Current: " + LTRIM(STR(SELECT())) + " " + ALIAS() + HB_OSNewLine()
BEGIN SEQUENCE WITH {|o| BREAK(o)}
IF !EMPTY(ALIAS())
cRet += " Filter: " + DBFILTER() + HB_OSNewLine()
cRet += " Relation: " + DBRELATION() + HB_OSNewLine()
cRet += " Index expression: " + ORDKEY(ORDSETFOCUS()) + HB_OSNewLine()
cRet += HB_OSNewLine()
BEGIN SEQUENCE WITH {|o| BREAK(o)}
FOR nI := 1 to FCOUNT()
cRet += STR(nI, 6) + " " + PADR(FIELDNAME(nI), 14) + ": " + HB_VALTOEXP(FIELDGET(nI)) + HB_OSNewLine()
NEXT
RECOVER
cRet += "!!! Error reading database fields !!!" + HB_OSNewLine()
END SEQUENCE
cRet += HB_OSNewLine()
ENDIF
RECOVER
cRet += "!!! Error accessing current workarea !!!" + HB_OSNewLine()
END SEQUENCE
FOR nI := 1 to 250
BEGIN SEQUENCE WITH {|o| BREAK(o)}
IF ! EMPTY(ALIAS(nI))
DBSELECTAREA(nI)
cRet += STR(nI, 6) + " " + RDDNAME() + " " + PADR(ALIAS(), 15) + ;
STR(RECNO()) + "/" + STR(LASTREC()) + ;
IIF(EMPTY(ORDSETFOCUS()), "", " Index " + ORDSETFOCUS() + "(" + LTRIM(STR(ORDNUMBER())) + ")") + HB_OSNewLine()
DBCLOSEAREA()
ENDIF
RECOVER
cRet += "!!! Error accessing workarea number: " + STR(nI, 4) + "!!!" + HB_OSNewLine()
END SEQUENCE
NEXT
cRet += HB_OSNewLine()
ENDIF
RETURN cRet
STATIC FUNC ErrDescCode(nCode)
LOCAL cI := NIL
IF nCode > 0 .AND. nCode <= 41
cI := {"ARG" , "BOUND" , "STROVERFLOW", "NUMOVERFLOW", "ZERODIV" , "NUMERR" , "SYNTAX" , "COMPLEXITY" , ; // 1, 2, 3, 4, 5, 6, 7, 8
NIL , NIL , "MEM" , "NOFUNC" , "NOMETHOD", "NOVAR" , "NOALIAS" , "NOVARMETHOD", ; // 9, 10, 11, 12, 13, 14, 15, 16
"BADALIAS", "DUPALIAS" , NIL , "CREATE" , "OPEN" , "CLOSE" , "READ" , "WRITE" , ; // 17, 18, 19, 20, 21, 22, 23, 24
"PRINT" , NIL , NIL , NIL , NIL , "UNSUPPORTED", "LIMIT" , "CORRUPTION" , ; // 25, 26 - 29, 30, 31, 32
"DATATYPE", "DATAWIDTH", "NOTABLE" , "NOORDER" , "SHARED" , "UNLOCKED" , "READONLY", "APPENDLOCK" , ; // 33, 34, 35, 36, 37, 38, 39, 40
"LOCK" }[nCode] // 41
ENDIF
RETURN IF(cI == NIL, "", "EG_" + cI)
/********************************************************************
Public functions
********************************************************************/
PROC USetStatusCode(nStatusCode)
s_nStatusCode := nStatusCode
RETURN
FUNC UGetHeader(cType)
LOCAL nI
IF (nI := ASCAN(s_aHeader, {|x| UPPER(x[1]) == UPPER(cType)})) > 0
RETURN s_aHeader[nI, 2]
ENDIF
RETURN NIL
PROC UAddHeader(cType, cValue)
LOCAL nI
IF (nI := ASCAN(s_aHeader, {|x| UPPER(x[1]) == UPPER(cType)})) > 0
s_aHeader[nI, 2] := cValue
ELSE
AADD(s_aHeader, {cType, cValue})
ENDIF
RETURN
PROC URedirect(cURL, nCode)
IF nCode == NIL; nCode := 303
ENDIF
USetStatusCode(nCode)
UAddHeader("Location", cURL)
RETURN
PROC USessionDestroy()
s_lSessionDestroy := .T.
RETURN
PROC UWrite(cString)
s_cResult += cString
RETURN
FUNC UOsFileName(cFileName)
IF HB_OSPathSeparator() != "/"
RETURN STRTRAN(cFileName, "/", HB_OSPathSeparator())
ENDIF
RETURN cFileName
FUNC UHtmlEncode(cString)
LOCAL nI, cI, cRet := ""
FOR nI := 1 TO LEN(cString)
cI := SUBSTR(cString, nI, 1)
IF cI == "<"
cRet += "&lt;"
ELSEIF cI == ">"
cRet += "&gt;"
ELSEIF cI == "&"
cRet += "&amp;"
ELSEIF cI == '"'
cRet += "&quot;"
ELSE
cRet += cI
ENDIF
NEXT
RETURN cRet
FUNC UUrlEncode(cString)
LOCAL nI, cI, cRet := ""
FOR nI := 1 TO LEN(cString)
cI := SUBSTR(cString, nI, 1)
IF cI == " "
cRet += "+"
ELSEIF ASC(cI) >= 127 .OR. ASC(cI) <= 31 .OR. cI $ '=&%+'
cRet += "%" + HB_StrToHex(cI)
ELSE
cRet += cI
ENDIF
NEXT
RETURN cRet
FUNC UUrlDecode(cString)
LOCAL nI
cString := STRTRAN(cString, "+", " ")
nI := 1
DO WHILE nI <= LEN(cString)
nI := HB_AT("%", cString, nI)
IF nI == 0; EXIT
ENDIF
IF UPPER(SUBSTR(cString, nI + 1, 1)) $ "0123456789ABCDEF" .AND. ;
UPPER(SUBSTR(cString, nI + 2, 1)) $ "0123456789ABCDEF"
cString := STUFF(cString, nI, 3, HB_HexToStr(SUBSTR(cString, nI + 1, 2)))
ENDIF
nI++
ENDDO
RETURN cString
FUNC ULink(cText, cURL)
RETURN '<a href="' + cURL + '">' + UHtmlEncode(cText) + '</a>'
PROC UProcFiles(cFileName, lIndex)
LOCAL aDir, aF, nI, cI, tDate, tHDate
IF lIndex == NIL; lIndex := .F.
ENDIF
cFileName := STRTRAN(cFileName, "//", "/")
// Security
IF "/../" $ cFileName
USetStatusCode(403)
RETURN
ENDIF
IF HB_FileExists(uOSFileName(cFileName))
IF HB_HHasKey(server, "HTTP_IF_MODIFIED_SINCE") .AND. ;
HttpDateUnformat(server["HTTP_IF_MODIFIED_SINCE"], @tHDate) .AND. ;
HB_FGETDATETIME(UOsFileName(cFileName), @tDate) .AND. ;
( tDate <= tHDate )
USetStatusCode(304)
ELSEIF HB_HHasKey(server, "HTTP_IF_UNMODIFIED_SINCE") .AND. ;
HttpDateUnformat(server["HTTP_IF_UNMODIFIED_SINCE"], @tHDate) .AND. ;
HB_FGETDATETIME(UOsFileName(cFileName), @tDate) .AND. ;
( tDate > tHDate )
USetStatusCode(412)
ELSE
IF (nI := RAT(".", cFileName)) > 0
SWITCH LOWER(SUBSTR(cFileName, nI + 1))
CASE "css"; cI := "text/css"; EXIT
CASE "htm"; CASE "html"; cI := "text/html"; EXIT
CASE "txt"; CASE "text"; CASE "asc"
CASE "c"; CASE "h"; CASE "cpp"
CASE "hpp"; CASE "log"; cI := "text/plain"; EXIT
CASE "rtf"; cI := "text/rtf"; EXIT
CASE "xml"; cI := "text/xml"; EXIT
CASE "bmp"; cI := "image/bmp"; EXIT
CASE "gif"; cI := "image/gif"; EXIT
CASE "jpg"; CASE "jpe"; CASE "jpeg"; cI := "image/jpeg"; EXIT
CASE "png"; cI := "image/png"; EXIT
CASE "tif"; CASE "tiff"; cI := "image/tiff"; EXIT
CASE "djv"; CASE "djvu"; cI := "image/vnd.djvu"; EXIT
CASE "ico"; cI := "image/x-icon"; EXIT
CASE "xls"; cI := "application/excel"; EXIT
CASE "doc"; cI := "application/msword"; EXIT
CASE "pdf"; cI := "application/pdf"; EXIT
CASE "ps"; CASE "eps"; cI := "application/postscript"; EXIT
CASE "ppt"; cI := "application/powerpoint"; EXIT
CASE "bz2"; cI := "application/x-bzip2"; EXIT
CASE "gz"; cI := "application/x-gzip"; EXIT
CASE "tgz"; cI := "application/x-gtar"; EXIT
CASE "js"; cI := "application/x-javascript"; EXIT
CASE "tar"; cI := "application/x-tar"; EXIT
CASE "tex"; cI := "application/x-tex"; EXIT
CASE "zip"; cI := "application/zip"; EXIT
CASE "midi"; cI := "audio/midi"; EXIT
CASE "mp3"; cI := "audio/mpeg"; EXIT
CASE "wav"; cI := "audio/x-wav"; EXIT
CASE "qt"; CASE "mov"; cI := "video/quicktime"; EXIT
CASE "avi"; cI := "video/x-msvideo"; EXIT
OTHERWISE
cI := "application/octet-stream"
ENDSWITCH
ELSE
cI := "application/octet-stream"
ENDIF
UAddHeader("Content-Type", cI)
IF HB_FGETDATETIME(UOsFileName(cFileName), @tDate)
UAddHeader("Last-Modified", HttpDateFormat(tDate))
ENDIF
UWrite(HB_MEMOREAD(UOsFileName(cFileName)))
ENDIF
ELSEIF HB_DirExists(UOsFileName(cFileName))
IF RIGHT(cFileName, 1) != "/"
URedirect("http://" + server["HTTP_HOST"] + server["SCRIPT_NAME"] + "/")
RETURN
ENDIF
IF (nI := ASCAN({"index.html", "index.htm"}, ;
{|x| IIF(HB_FileExists(UOSFileName(cFileName + X)), (cFileName += X, .T.), .F.)})) > 0
UAddHeader("Content-Type", "text/html")
UWrite(HB_MEMOREAD(UOsFileName(cFileName)))
RETURN
ENDIF
IF ! lIndex
USetStatusCode(403)
RETURN
ENDIF
UAddHeader("Content-Type", "text/html")
aDir := DIRECTORY(UOsFileName(cFileName), "D")
IF HB_HHasKey(get, "s")
IF get["s"] == "s"
ASORT(aDir,,, {|X,Y| IF(X[5] == "D", IF(Y[5] == "D", X[1] < Y[1], .T.), ;
IF(Y[5] == "D", .F., X[2] < Y[2]))})
ELSEIF get["s"] == "m"
ASORT(aDir,,, {|X,Y| IF(X[5] == "D", IF(Y[5] == "D", X[1] < Y[1], .T.), ;
IF(Y[5] == "D", .F., DTOS(X[3]) + X[4] < DTOS(Y[3]) + Y[4]))})
ELSE
ASORT(aDir,,, {|X,Y| IF(X[5] == "D", IF(Y[5] == "D", X[1] < Y[1], .T.), ;
IF(Y[5] == "D", .F., X[1] < Y[1]))})
ENDIF
ELSE
ASORT(aDir,,, {|X,Y| IF(X[5] == "D", IF(Y[5] == "D", X[1] < Y[1], .T.), ;
IF(Y[5] == "D", .F., X[1] < Y[1]))})
ENDIF
UWrite('<html><body><h1>Index of ' + server["SCRIPT_NAME"] + '</h1><pre> ')
UWrite('<a href="?s=n">Name</a> ')
UWrite('<a href="?s=m">Modified</a> ')
UWrite('<a href="?s=s">Size</a>' + CR_LF + '<hr>')
FOR EACH aF IN aDir
IF LEFT(aF[1], 1) == "."
ELSEIF "D" $ aF[5]
UWrite('[DIR] <a href="' + aF[1] + '/">'+ aF[1] + '</a>' + SPACE(50 - LEN(aF[1])) + ;
DTOC(aF[3]) + ' ' + aF[4] + CR_LF)
ELSE
UWrite(' <a href="' + aF[1] + '">'+ aF[1] + '</a>' + SPACE(50 - LEN(aF[1])) + ;
DTOC(aF[3]) + ' ' + aF[4] + STR(aF[2], 12) + CR_LF)
ENDIF
NEXT
UWrite("<hr></pre></body></html>")
ELSE
USetStatusCode(404)
ENDIF
RETURN
PROC UProcInfo()
UWrite('<h1>Info</h1>')
UWrite('<h2>Platform</h2>')
UWrite('<table border=1 cellspacing=0>')
UWrite('<tr><td>OS</td><td>' + UHtmlEncode(OS()) + '</td></tr>')
UWrite('<tr><td>Harbour</td><td>' + UHtmlEncode(VERSION()) + '</td></tr>')
UWrite('<tr><td>Build date</td><td>' + UHtmlEncode(HB_BUILDDATE()) + '</td></tr>')
UWrite('<tr><td>Compiler</td><td>' + UHtmlEncode(HB_COMPILER()) + '</td></tr>')
UWrite('</table>')
UWrite('<h2>Capabilities</h2>')
UWrite('<table border=1 cellspacing=0>')
UWrite('<tr><td>RDD</td><td>' + UHtmlEncode(join(", ", RDDLIST())) + '</td></tr>')
UWrite('</table>')
UWrite('<h2>Variables</h2>')
UWrite('<h3>server</h3>')
UWrite('<table border=1 cellspacing=0>')
HB_HEval(server, {|k,v| UWrite('<tr><td>' + k + '</td><td>' + UHtmlEncode(HB_CStr(v)) + '</td></tr>')})
UWrite('</table>')
IF !EMPTY(get)
UWrite('<h3>get</h3>')
UWrite('<table border=1 cellspacing=0>')
HB_HEval(get, {|k,v| UWrite('<tr><td>' + k + '</td><td>' + UHtmlEncode(HB_CStr(v)) + '</td></tr>')})
UWrite('</table>')
ENDIF
IF !EMPTY(post)
UWrite('<h3>post</h3>')
UWrite('<table border=1 cellspacing=0>')
HB_HEval(post, {|k,v| UWrite('<tr><td>' + k + '</td><td>' + UHtmlEncode(HB_CStr(v)) + '</td></tr>')})
UWrite('</table>')
ENDIF
RETURN