diff --git a/harbour/ChangeLog b/harbour/ChangeLog index de8cc8a9a0..712b49b173 100644 --- a/harbour/ChangeLog +++ b/harbour/ChangeLog @@ -8,6 +8,42 @@ 2008-12-31 13:59 UTC+0100 Foo Bar (foo.bar foobar.org) */ +2009-01-19 23:39 UTC+0100 Francesco Saverio Giudice (info/at/fsgiudice.com) + + harbour/contrib/examples/uhttpd + + harbour/contrib/examples/socket.c + + harbour/contrib/examples/uhttpd.prg + + harbour/contrib/examples/hbmk_b32.bat + + harbour/contrib/examples/readme.txt + + harbour/contrib/examples/home + + harbour/contrib/examples/home/cgi-bin + + harbour/contrib/examples/home/counter.html + + harbour/contrib/examples/home/css + + harbour/contrib/examples/home/css/base.css + + harbour/contrib/examples/home/favicon.ico + + harbour/contrib/examples/home/images + + harbour/contrib/examples/home/images/ajax-loader.gif + + harbour/contrib/examples/home/index.html + + harbour/contrib/examples/home/js + + harbour/contrib/examples/home/js/ajax.js + + harbour/contrib/examples/home/testajax.html + + harbour/contrib/examples/home/testxmldb.html + + harbour/contrib/examples/home/xsl + + harbour/contrib/examples/home/xsl/based.xsl + + harbour/contrib/examples/home/xsl/basep.xsl + + harbour/contrib/examples/logs + + harbour/contrib/examples/modules + + harbour/contrib/examples/modules/bldhrb.bat + + harbour/contrib/examples/modules/showcounter.prg + + harbour/contrib/examples/modules/tableservletdb.prg + + harbour/contrib/examples/modules/testajax.prg + + Uploaded first version of uHTTPD server. + ; NOTE: + This is first version of uHTTPD (micro HTTPD server) based + on a sample shared from Mindaugas (thanks!). + Actually is only for windows and BCC32. + To build use hbmk_b32.bat + Please read readme.txt before start to use. + 2009-01-19 22:17 UTC+0100 Przemyslaw Czerpak (druzus/at/priv.onet.pl) * harbour/source/vm/hvm.c * clone arrays and hash tables instead of coping when thread static diff --git a/harbour/contrib/examples/uhttpd/hbmk_b32.bat b/harbour/contrib/examples/uhttpd/hbmk_b32.bat new file mode 100644 index 0000000000..aba8367f30 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/hbmk_b32.bat @@ -0,0 +1,19 @@ +@echo off +rem +rem $Id: hbmk_b32.bat 9884 2008-11-09 19:37:16Z vszakats $ +rem + +rem NOTE: This sample program needs hbgd.lib from contrib/hbgd + +..\..\..\bin\harbour uhttpd /n /i..\..\..\include +bcc32 -O2 -tW -d -a8 -I..\..\..\include -L..\..\..\lib uhttpd.c socket.c hbdebug.lib hbvmmt.lib hbrtl.lib gtwvt.lib gtwin.lib gtgui.lib hblang.lib hbrdd.lib hbmacro.lib hbpp.lib rddntx.lib rddcdx.lib rddfpt.lib hbcpage.lib hbsix.lib hbcommon.lib hbpcre.lib hbhsx.lib hbzlib.lib hbgd.lib bgd.lib xhb.lib hbct.lib cw32mt.lib + +:CLEAN +del *.obj +del *.tds +del uhttpd.c + +if not exist uhttpd.exe goto :EXIT +if not exist bgd.dll echo.ATTENTION! This program needs bgd.dll +echo.Build complete. +:EXIT diff --git a/harbour/contrib/examples/uhttpd/home/counter.html b/harbour/contrib/examples/uhttpd/home/counter.html new file mode 100644 index 0000000000..2c2e1b346b --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/counter.html @@ -0,0 +1,89 @@ + + + + + Show Graphic Counter + + + + + + + This is a simple ajax test. Please type a number (at least 4 digits). +
+

word: +

+
+
+ Return to Main Page + + + diff --git a/harbour/contrib/examples/uhttpd/home/css/base.css b/harbour/contrib/examples/uhttpd/home/css/base.css new file mode 100644 index 0000000000..543824704d --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/css/base.css @@ -0,0 +1,66 @@ + +body {font-family:Tahoma,Helvetica, Arial;font-size:10pt;color:black;} + +a.pageSection:link {color: #ff0000} +a.pageSection:visited {color: #0000ff} +a.pageSection:hover {background: #66ff66} + +/* +a:link {color: #ff0000} +a:visited {color: #0000ff} +a:hover {background: #66ff66} +*/ + +A { + color: #0000FF; + text-decoration: none; +} + +A:hover { color: #6699cc; text-decoration: underline; } +A.urls { color: #0A68B6; text-decoration: none; } +A.urls:hover { color: #6699cc; text-decoration: underline; } +A.tags { color: #008080; text-decoration: none; } +A.tags:hover { color: #6699cc; text-decoration: underline; } + + + +table.pagetable td +{ + padding: 3px; +} + +table.datatable +{ + width: 100%; + font-family: Verdana; + font-size: 12px; +} + +table.datatable tr th +{ + border-bottom:1px solid black; + padding: 2px; + text-align: left; +} + +table.datatable tr td +{ + vertical-align: top; + padding: 2px; + border-bottom: 1px solid white; +} + +table.datatable tr.odd +{ + background-color: #7FFFD4; +} + +table.datatable tr.even +{ + background-color: #F0F0F0; +} + +table.datatable tr.blank +{ + background-color: #FFFFFF; +} \ No newline at end of file diff --git a/harbour/contrib/examples/uhttpd/home/favicon.ico b/harbour/contrib/examples/uhttpd/home/favicon.ico new file mode 100644 index 0000000000..f92a768e65 Binary files /dev/null and b/harbour/contrib/examples/uhttpd/home/favicon.ico differ diff --git a/harbour/contrib/examples/uhttpd/home/images/ajax-loader.gif b/harbour/contrib/examples/uhttpd/home/images/ajax-loader.gif new file mode 100644 index 0000000000..49b6d85326 Binary files /dev/null and b/harbour/contrib/examples/uhttpd/home/images/ajax-loader.gif differ diff --git a/harbour/contrib/examples/uhttpd/home/index.html b/harbour/contrib/examples/uhttpd/home/index.html new file mode 100644 index 0000000000..a8b4ba3989 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/index.html @@ -0,0 +1,20 @@ + + + + +Harbour uHTTPD Server example + + + +Simple uHTTPD server demo. +
+
+Examples: +
+Test Ajax +
+Test Ajax XML Database +
+Test Ajax Counter + + diff --git a/harbour/contrib/examples/uhttpd/home/js/ajax.js b/harbour/contrib/examples/uhttpd/home/js/ajax.js new file mode 100644 index 0000000000..c405ca3714 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/js/ajax.js @@ -0,0 +1,257 @@ +/* + Global data var declaration +*/ + +/** + * Open a connection to the specified URL, which is + * intended to provide an XML message. The specified data + * is sent to the server as parameters. This is the same as + * calling xmlOpen("POST", url, toSend, responseHandler). + * + * @param string url The URL to connect to. + * @param string toSend The data to send to the server; must be URL encoded. + * @param function responseHandler The Javascript function handling server response. + */ +function xmlPost(url, toSend, responseHandler) +{ + StartProgress(); + xmlOpen("POST", url, toSend, responseHandler); +} + +/** + * Open a connection to the specified URL, which is + * intended to provide an XML message. No other data is + * sent to the server. This is the same as calling + * xmlOpen("GET", url, null, responseHandler). + * + * @param string url The URL to connect to. + * @param function responseHandler The Javascript function handling server response. + */ +function xmlGet(url, responseHandler) +{ + StartProgress(); + xmlOpen("GET", url, null, responseHandler); +} + +/** + * Open a connection to the specified URL, which is + * intended to respond with an XML message. + * + * @param string method The connection method; either "GET" or "POST". + * @param string url The URL to connect to. + * @param string toSend The data to send to the server; must be URL encoded. + * @param function responseHandler The Javascript function handling server response. + */ +function xmlOpen(method, url, toSend, responseHandler) +{ + req = null; + if (window.XMLHttpRequest) + { + // browser has native support for XMLHttpRequest object + req = new XMLHttpRequest(); + } + else if (window.ActiveXObject) + { + // try XMLHTTP ActiveX (Internet Explorer) version + req = new ActiveXObject("Microsoft.XMLHTTP"); + } + + if(req) + { + req.onreadystatechange = responseHandler; + req.open(method, url, true); + req.setRequestHeader("content-type","application/x-www-form-urlencoded"); + req.send(toSend); + } + else + { + alert('Your browser does not seem to support XMLHttpRequest.'); + } +} + +/** + * Gets the first child node of parent with the + * specified tag name. + * + * @param parent the parent XML DOM node to search + * @param tagName the tag name of the child node to search for + */ +function getNode(parent, tagName) +{ + var i; + var max = parent.childNodes.length; + + // Check each child node + for(i = 0; i < max; i++) + { + if(parent.childNodes[i].tagName) + { + if(parent.childNodes[i].tagName.toUpperCase() == tagName.toUpperCase()) + { + // We found a matching child node; return it. + return parent.childNodes[i]; + } + } + } + // One was not found; return null + return null; +} + +/** + * Gets the first child node of parent with the + * specified tag name and whose value of the 'key' attribute + * is key. + * + * @param parent the parent XML DOM node to search + * @param tagName the tag name of the child nodes to search in + * @param key the value of the 'key' attribute to search on + */ +function getNodesWithKey(parent, tagName, key) +{ + var i; + var cellNodes = parent.getElementsByTagName(tagName); + var max = cellNodes.length; + + // Check each cell node for the specified value for + // the 'key' attribute + for(i = 0; i < max; i++) + { + if(cellNodes[i].getAttribute('key') == key) + { + // We found a matching cell node; return it. + return cellNodes[i]; + } + } + // One was not found; return null + return null; +} + +// ----- xslT functions -------------------------------------------------------------------- + +// Immediately try to load the xsl file asynchronously +var xsldocloaded = false; +var xsldoc; + +function xslGet( xslfile ) +{ + + if (window.XSLTProcessor) + { + // support Mozilla/Gecko based browsers + xsldoc = document.implementation.createDocument("", "", null); + xsldoc.addEventListener("load", onXslLoad, false); + xsldoc.load( xslfile ); + } + else if(window.ActiveXObject) + { + // support Windows / ActiveX + xsldoc = new ActiveXObject("Microsoft.XMLDOM"); + xsldoc.ondataavailable = onXslLoad; + xsldoc.load( xslfile ); + } + +} + +function onXslLoad() +{ + // flag that the xsl is loaded + xsldocloaded = true; + //alert( "xsl loaded: " + xsldocloaded ) +} + +// ----- xslT to HTML functions ----------- + +function combine_XLM_XSLT_HTML( xlm, xsl, html, html_id ) +{ + + var swappableSection = html.getElementById( html_id ); + + if (window.XSLTProcessor) + { + // support Mozilla/Gecko based browsers + var xsltProcessor = new XSLTProcessor(); + xsltProcessor.importStylesheet( xsl ); + var outputXHTML = xsltProcessor.transformToFragment( xlm.responseXML, html ); + //alert( outputXHTML ); + swappableSection.innerHTML = ""; + swappableSection.appendChild( outputXHTML ); + } + else if(window.ActiveXObject) + { + // support Windows/ActiveX enabled browsers + var outputXHTML = xlm.responseXML.transformNode( xsl ); + //alert( outputXHTML ); + swappableSection.innerHTML = outputXHTML; + } + +} + + +// ----- show or hide a progress indicator ----- + +var progress = false; +var progressTimer = null; + +// show a progress indicator if it takes longer... +function StartProgress() +{ + //alert( "progress = " + progress ); + progress = true; + if (progressTimer != null) + window.clearTimeout(progressTimer); + progressTimer = window.setTimeout(ShowProgress, 220); +} // StartProgress + + +// hide any progress indicator soon. +function EndProgress() +{ + progress = false; + if (progressTimer != null) + window.clearTimeout(progressTimer); + progressTimer = window.setTimeout(ShowProgress, 20); +} // EndProgress + + +// this function is called by a timer to show or hide a progress indicator +function ShowProgress() +{ + //alert( "Showprogress = " + progress ); + progressTimer = null; + var a = document.getElementById("AjaxProgressIndicator"); + + if (progress && (a != null)) { + // just display the existing object + a.style.top = document.documentElement.scrollTop + 2 + "px"; + a.style.display = ""; + + } else if (progress) { + + // find a relative link to the ajaxcore folder containing ajax.js + var path = "/images/" + //for (var n in document.scripts) { + // s = document.scripts[n].src; + // if ((s != null) && (s.length >= 7) && (s.substr(s.length -7).toLowerCase() == "ajax.js")) + // path = s.substr(0,s.length -7); + //} // for + + // create new standard progress object + a = document.createElement("div"); + a.id = "AjaxProgressIndicator"; + a.style.position = "absolute"; + a.style.right = "2px"; + a.style.top = document.documentElement.scrollTop + 2 + "px"; + a.style.width = "130px"; + a.style.height = "16px" + a.style.padding = "2px"; + a.style.verticalAlign = "bottom"; + a.style.backgroundColor="#9FCDFF"; + + a.innerHTML = " please wait..."; + document.body.appendChild(a); + + } else if (a) { + a.style.display="none"; + } // if +} // ShowProgress + diff --git a/harbour/contrib/examples/uhttpd/home/testajax.html b/harbour/contrib/examples/uhttpd/home/testajax.html new file mode 100644 index 0000000000..fb2cfcd189 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/testajax.html @@ -0,0 +1,56 @@ + + +Simple Ajax Example + + + +This is a simple ajax test. Please type a string in input field and press GO button. +
+

word: +

+
+
+Return to Main Page + + \ No newline at end of file diff --git a/harbour/contrib/examples/uhttpd/home/testxmldb.html b/harbour/contrib/examples/uhttpd/home/testxmldb.html new file mode 100644 index 0000000000..5e097849cd --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/testxmldb.html @@ -0,0 +1,85 @@ + + + + Part 4 Example + + + + + + + Sample XML servlet. Tested with IE6+ and Firefox 2+ +
Return to Main Page +
Page  +
 
+
+ +
 
+ + diff --git a/harbour/contrib/examples/uhttpd/home/xsl/based.xsl b/harbour/contrib/examples/uhttpd/home/xsl/based.xsl new file mode 100644 index 0000000000..9b5bbb3b83 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/xsl/based.xsl @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + even + odd + + + + + + + + + + + + + + +
No Records Found
+ + +
+ + + + + + +   + + + + + + + + + +   + + + + + + + + +
\ No newline at end of file diff --git a/harbour/contrib/examples/uhttpd/home/xsl/basep.xsl b/harbour/contrib/examples/uhttpd/home/xsl/basep.xsl new file mode 100644 index 0000000000..088b6d3380 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/home/xsl/basep.xsl @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + +
+ + + +
+ +
+
diff --git a/harbour/contrib/examples/uhttpd/modules/bldhrb.bat b/harbour/contrib/examples/uhttpd/modules/bldhrb.bat new file mode 100644 index 0000000000..d87f85c0b7 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/modules/bldhrb.bat @@ -0,0 +1,63 @@ +@echo off +rem Saving current HB_MT state +set OLDENVMT=%HB_MT% +set OLDENVGT=%HB_GT_LIB% +set OLDENVC=%CFLAGS% +set OLDENVHB=%HARBOURFLAGS% +set OLD_HB_ARCHITECTURE=%HB_ARCHITECTURE% +set OLD_HB_COMPILER=%HB_COMPILER% +set OLD_HB_USER_LIBS=%HB_USER_LIBS% + +set HB_INSTALL=..\..\..\.. +if %HB_ARCHITECTURE%.==. set HB_ARCHITECTURE=w32 +if %HB_COMPILER%.==. set HB_COMPILER=bcc32 +SET HB_BIN_INSTALL=%HB_INSTALL%\bin +set HB_INC_INSTALL=include;%HB_INSTALL%\include +set HB_LIB_INSTALL=%HB_INSTALL%\lib + +%HB_BIN_INSTALL%\harbour %1.prg -n -q0 -w -es2 -gh -i%HB_INC_INSTALL% %2 %3 %HARBOURFLAGS% > bldtest.log + +IF ERRORLEVEL 1 GOTO SHOWERROR + +GOTO COMPILEOK + +:SHOWERROR +echo. +echo.Error on compiling ... +echo. +echo.Running notepad, please close to end this batch file ... +echo. +notepad bldtest.log +echo. +echo.Notepad closed, exiting ... +echo. +GOTO ENDSET + +:COMPILEOK +echo. +echo.Compiled successfully +echo. +if exist bldtest.log del bldtest.log +if exist %1.hrb copy %1.hrb ..\home\cgi-bin /y +if exist %1.hrb del %1.hrb +GOTO ENDSET + +:ENDSET +rem Restore Old Settings +set HB_MT=%OLDENVMT% +set HB_GT_LIB=%OLDENVGT% +set CFLAGS=%OLDENVC% +set HARBOURFLAGS=%OLDENVHB% +set HB_ARCHITECTURE=%OLD_HB_ARCHITECTURE% +set HB_COMPILER=%OLD_HB_COMPILER% +set HB_USER_LIBS=%OLD_HB_USER_LIBS% + +set OLDENVHB= +set OLDENVGT= +set OLDENVC= +set OLDENVMT= +set BLDDEFAULT= +set OLD_HB_ARCHITECTURE= +set OLD_HB_COMPILER= +set OLD_HB_USER_LIBS= + diff --git a/harbour/contrib/examples/uhttpd/modules/showcounter.prg b/harbour/contrib/examples/uhttpd/modules/showcounter.prg new file mode 100644 index 0000000000..4b57d6a2df --- /dev/null +++ b/harbour/contrib/examples/uhttpd/modules/showcounter.prg @@ -0,0 +1,219 @@ + +/* + * $Id: rlcdx.prg 9754 2008-10-27 22:40:04Z vszakats $ + */ + +/* + * Harbour Project source code: + * simple image counter + * + * Copyright 2009 Francesco Saverio Giudice + * www - http://www.harbour-project.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/). + * + * As a special exception, the Harbour Project gives permission for + * additional uses of the text contained in its release of Harbour. + * + * The exception is that, if you link the Harbour libraries with other + * files to produce an executable, this does not by itself cause the + * resulting executable to be covered by the GNU General Public License. + * Your use of that executable is in no way restricted on account of + * linking the Harbour library code into it. + * + * This exception does not however invalidate any other reasons why + * the executable file might be covered by the GNU General Public License. + * + * This exception applies only to the code released by the Harbour + * Project under the name Harbour. If you copy code from other + * Harbour Project or Free Software Foundation releases into a copy of + * Harbour, as the General Public License permits, the exception does + * not apply to the code that you add in this way. To avoid misleading + * anyone as to the status of such modified files, you must delete + * this exception notice from them. + * + * If you write modifications of your own for Harbour, it is your choice + * whether to permit this exception to apply to your modifications. + * If you do not wish that, delete this exception notice. + * + */ + + +MEMVAR _SERVER // defined in uHTTPD +MEMVAR _REQUEST // defined in uHTTPD + +#include "common.ch" +//#include "xhb.ch" +#include "gd.ch" + +#define IMAGES_IN "..\..\hbgd\tests\digits\" +#define IMAGES_OUT ( _SERVER[ "DOCUMENT_ROOT" ] + "\counter\" ) + +#define DISPLAY_NUM 10 + +FUNCTION HRBMAIN() + LOCAL cHtml + LOCAL cBaseImage + + IF HB_HHasKey( _REQUEST, "w" ) + + cHtml := CreateCounter( AllTrim( Str( Val( _REQUEST[ "w" ] ) ) ) ) + //hb_ToOutDebug( hb_sprintf( "CreateCounter = %s", cHtml ) ) + IF !Empty( cHtml ) + uAddHeader( "Content-Type", "image/gif" ) + uAddHeader( "Pragma", "no-cache" ) + uAddHeader( "Content-Disposition", "inline; filename=counter" + LTrim( Str( hb_randomint( 100 ) ) ) + ".gif" ) + uWrite( cHtml ) + ELSE + uAddHeader( "Content-Type", "text/html" ) + uWrite( "

Error: No image created

" ) + ENDIF + + + ELSE + + uAddHeader( "Content-Type", "text/html" ) + uWrite( "

Error: no parameters passed

" ) + + ENDIF + +RETURN TRUE + +STATIC FUNCTION CreateCounter( cValue, cBaseImage ) + + LOCAL oI, oIDigits, nWidth, nHeight, nDigits, nNumWidth, oTemp + //LOCAL black, white, blue, red, green, cyan, gray + LOCAL white + LOCAL aNumberImages := {} + LOCAL n, nValue + LOCAL cFile + + // A value if not passed + DEFAULT cValue TO Str( hb_RandomInt( 1, 10^DISPLAY_NUM ), DISPLAY_NUM ) + DEFAULT cBaseImage TO "57chevy.gif" + + IF !File( IMAGES_IN + cBaseImage ) + //hb_ToOutDebug( "ERROR: Base Image File '" + IMAGES_IN + cBaseImage + "' not found" ) + //THROW( "ERROR: Base Image File '" + IMAGES_IN + cBaseImage + "' not found" ) + RETURN NIL + ENDIF + + nValue := Val( cValue ) + + // Fix num lenght + IF nValue > 10^DISPLAY_NUM + nValue := 10^DISPLAY_NUM + ENDIF + + cValue := StrZero( nValue, DISPLAY_NUM ) + + //? "Value = ", cValue + + // To set fonts run this command: + // for windows: SET GDFONTPATH=c:\windows\fonts + // per linux : export GDFONTPATH=/usr/share/fonts/default/TrueType + + // SET GDFONTPATH=c:\windows\fonts + //IF GetEnv( "GDFONTPATH" ) == "" + // ? "Please set GDFONTPATH" + // ? "On Windows: SET GDFONTPATH=c:\windows\fonts" + // ? "On Linux : export GDFONTPATH=/usr/share/fonts/default/TrueType" + // ? + //ENDIF + + // Check output directory + /* + IF !ISDirectory( IMAGES_OUT ) + DirMake( IMAGES_OUT ) + ENDIF + */ + + /* Load a digits image in memory from file */ + oIDigits := GDImage():LoadFromGif( IMAGES_IN + cBaseImage ) + + /* Get single number images */ + + // Get dimensions + nWidth := oIDigits:Width() + nHeight := oIDigits:Height() + + // Check base digits image + DO CASE + CASE nWidth % 10 == 0 // 0..9 digits + nDigits := 10 + CASE nWidth % 11 == 0 // 0..9 : + nDigits := 11 + CASE nWidth % 13 == 0 // 0..9 : am pm + nDigits := 13 + OTHERWISE + uWrite( "Error on digits image" ) + ENDCASE + nNumWidth := nWidth / nDigits + + //? "nNumWidth, nWidth, nHeight, nDigits = ", nNumWidth, nWidth, nHeight, nDigits + + /* extracts single digits */ + FOR n := 1 TO nDigits + oTemp := oIDigits:Copy( (n - 1) * nNumWidth, 0, nNumWidth, nHeight ) + //oTemp:SaveGif( IMAGES_OUT + StrZero( n-1, 2 ) + ".gif" ) + // Here I have to clone the image, otherwise on var destruction I loose + // the image in memory + aAdd( aNumberImages, oTemp:Clone() ) + NEXT + + /* Create counter image in memory */ + oI := GDImage():New( nNumWidth * DISPLAY_NUM, nHeight ) // the counter + //? "Image dimensions: ", oI:Width(), oI:Height() + + /* Allocate background */ + white := oI:SetColor( 255, 255, 255 ) + + /* Allocate drawing color */ + //black := oI:SetColor( 0, 0, 0 ) + //blue := oI:SetColor( 0, 0, 255 ) + //red := oI:SetColor( 255, 0, 0 ) + //green := oI:SetColor( 0, 255, 0 ) + //cyan := oI:SetColor( 0, 255, 255 ) + + /* Draw rectangle */ + //oI:Rectangle( 0, 0, 200, 30, , blue ) + + /* Draw Digits */ + FOR n := 1 TO Len( cValue ) + // Retrieve the number from array in memory + oTemp := aNumberImages[ Val( SubStr( cValue, n, 1 ) ) + 1 ]:Clone() + // Save it to show the number for a position + //oTemp:SaveGif( IMAGES_OUT + "Pos_" + StrZero( n, 2 ) + ".gif" ) + // Set the digit as tile that I have to use to fill position in counter + oI:SetTile( oTemp ) + // Fill the position with the image digit + oI:Rectangle( (n - 1) * nNumWidth, 0, (n - 1) * nNumWidth + nNumWidth, nHeight, TRUE, gdTiled ) + NEXT + + /* Write Final Counter Image */ + cFile := "counter" + StrZero( hb_RandomInt( 1, 99 ), 2 ) + ".gif" + //oI:SaveGif( IMAGES_OUT + cFile ) + + /* Destroy images in memory */ + // Class does it automatically + + //? + //? "Look at " + IMAGES_OUT + " folder for output images" + //? + +//RETURN cFile +RETURN oI:ToStringGif() + diff --git a/harbour/contrib/examples/uhttpd/modules/tableservletdb.prg b/harbour/contrib/examples/uhttpd/modules/tableservletdb.prg new file mode 100644 index 0000000000..e68f04bafc --- /dev/null +++ b/harbour/contrib/examples/uhttpd/modules/tableservletdb.prg @@ -0,0 +1,400 @@ +/* + * $Id: rlcdx.prg 9754 2008-10-27 22:40:04Z vszakats $ + */ + +/* + * Harbour Project source code: + * xml table servlet + * + * Copyright 2009 Francesco Saverio Giudice + * www - http://www.harbour-project.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/). + * + * As a special exception, the Harbour Project gives permission for + * additional uses of the text contained in its release of Harbour. + * + * The exception is that, if you link the Harbour libraries with other + * files to produce an executable, this does not by itself cause the + * resulting executable to be covered by the GNU General Public License. + * Your use of that executable is in no way restricted on account of + * linking the Harbour library code into it. + * + * This exception does not however invalidate any other reasons why + * the executable file might be covered by the GNU General Public License. + * + * This exception applies only to the code released by the Harbour + * Project under the name Harbour. If you copy code from other + * Harbour Project or Free Software Foundation releases into a copy of + * Harbour, as the General Public License permits, the exception does + * not apply to the code that you add in this way. To avoid misleading + * anyone as to the status of such modified files, you must delete + * this exception notice from them. + * + * If you write modifications of your own for Harbour, it is your choice + * whether to permit this exception to apply to your modifications. + * If you do not wish that, delete this exception notice. + * + */ + +#include "common.ch" +#include "hbclass.ch" + +#define CRLF ( chr(13)+chr(10) ) +#define TABLE_NAME_PATH "..\..\..\tests\test.dbf" +#define SIMULATE_SLOW_REPLY + +MEMVAR _REQUEST // defined in uHTTPD + +FUNCTION HRBMAIN() + + LOCAL cXml, cPage, cCount, nCount + LOCAL oTM + LOCAL hGets + + hGets := _REQUEST + + DEFAULT hGets TO hb_Hash() + + IF HB_HHasKey( hGets, "page" ) + + cPage := hGets[ "page" ] + + oTM := TableManager():New() + + IF ( oTM:Open() ) + + oTM:Read() + cXml := oTM:getXmlData( Val( cPage ) ) + + oTM:Close() + + ENDIF + + ELSEIF HB_HHasKey( hGets, "count" ) + + cCount := hGets[ "count" ] + + IF cCount == "true" + + oTM := TableManager():New() + + IF ( oTM:Open() ) + + nCount := oTM:getLastRec() + cXml := oTM:getXmlCount( nCount ) + + oTM:Close() + + ENDIF + + ENDIF + ENDIF + + + IF !Empty( cXml ) + + uAddHeader("Content-Type", "text/xml") + // cache control + uAddHeader( "Cache-Control", "no-cache, must-revalidate" ) + uAddHeader( "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" ) + + uWrite( cXml ) + + ELSE + + uAddHeader("Content-Type", "text/xml") + uWrite( '' ) + uWrite( 'No Data' ) + + ENDIF + +RETURN TRUE // I Handle HTML Output + +/* + TableManager +*/ + +CLASS TableManager + + CLASSVAR ROWS_PER_PAGE INIT 23 + + VAR aData INIT {} + + VAR cTable INIT TABLE_NAME_PATH + VAR lOpened INIT FALSE + + METHOD New() + METHOD Open() + METHOD Close() INLINE IIF( ::lOpened, ( table->( dbCloseArea() ), ::lOpened := FALSE ), ) + METHOD Read() + METHOD getLastRec() INLINE table->( LastRec() ) + METHOD getXmlData() + METHOD getXmlCount() + METHOD xmlEncode( input ) +ENDCLASS + +METHOD New() CLASS TableManager +RETURN Self + +METHOD Open() CLASS TableManager + LOCAL cDBF := ::cTable + + IF !::lOpened + + CLOSE ALL + USE ( cDBF ) ALIAS table SHARED NEW + ::lOpened := USED() + + ENDIF + +RETURN ::lOpened + +METHOD Read() CLASS TableManager + LOCAL hMap, lOk := FALSE + +#ifdef SIMULATE_SLOW_REPLY + // force slow connection to simulate long reply + HB_IDLESLEEP(0.5) +#endif + + IF ::lOpened + + table->( dbGoTop() ) + //n := 0 + DO WHILE table->( !Eof() ) //.AND. ++n < 50 + + hMap := hb_Hash() + hMap[ "recno" ] := StrZero( table->( RecNo() ), 4 ) + hMap[ "name" ] := RTrim( table->first ) + " " + RTrim( table->last ) + hMap[ "address" ] := RTrim( table->street ) + hMap[ "city" ] := RTrim( table->city ) + hMap[ "state" ] := table->state + hMap[ "zip" ] := table->zip + aAdd( ::aData, hMap ) + table->( dbSkip() ) + ENDDO + + lOk := TRUE + + ENDIF + +RETURN lOK + +/** + * Builds a String of XML representing the aData for the + * request table. + * + * For simplicity, we are using a hard-coded data set. In a production + * system, you may wish to use DAOs to query a database for specific table + * data. This may require additional parameters (e.g., the name of the + * table, which could be used to look up instructions on retrieving the + * necessary data). + * + * The returned XML will be formatted as follows: + * <table>
+ * <header>
+ * <cell key="address">Address</cell>
+ * </header>
+ * <row>
+ * <cell key="name">Hank</cell>
+ * <cell key="address">1B Something Street</cell>
+ * <cell key="city">Marietta</cell>
+ * <cell key="state">GA</cell>
+ * <cell key="zip">30339</cell>
+ * </row>
+ * ...
+ * </table> + * + * @param page + * the page number to retrieve data for + * @return a String of XML representing data for the + * requested table + * @throws IllegalArgumentException + */ + +METHOD getXmlData( page ) CLASS TableManager + LOCAL startIndex, stopIndex + LOCAL xml, i, map, key, cString + + /* + * For simplicity, we are creating XML as a String. In a production + * system, you should create an XML document (org.w3c.dom.Document) to + * ensure compliance with the DOM Level 2 Core Specification. + */ + + // Calculate the start and end indexes of the table data. + startIndex := (page - 1) * ::ROWS_PER_PAGE + stopIndex := startIndex + ::ROWS_PER_PAGE + stopIndex := Min( Len( ::aData ), stopIndex ) + + // Check the validity of the page index. + IF ( startIndex < 0 .OR. startIndex >= stopIndex ) + //throw new IllegalArgumentException("Page index is out of bounds."); + ENDIF + + xml := BasicXML():New() + + xml:append( '' ) + + // Add the opening tag + xml:append( "
" ) + + // Add nodes describing the table columns + xml:append( "
" ) + xml:append( 'RecNo') + xml:append( 'Name') + xml:append( 'Address' ) + xml:append( 'City' ) + xml:append( 'State' ) + xml:append( 'Zip' ) + xml:append( "
" ) + + // Add nodes for each row. + FOR i := startIndex + 1 TO stopIndex + map := ::aData[ i ] + + // Add the opening tag + xml:append( "" ) + + // For each entry in the HashMap, add a node + // e.g.,
123 four street
+ FOR EACH key IN map:Keys + + cString := '' + cString += ::xmlEncode( hb_cStr( map[ key ] ) ) + cString += "" + + xml:append( cString ) + + NEXT + + // Add the closing
tag + xml:append( "
" ) + + NEXT + + // Add the closing
tag + xml:append( "" ) + +RETURN xml:toString() + +METHOD getXmlCount( nCount ) CLASS TableManager + LOCAL xml, n + LOCAL nPages := nCount / ::ROWS_PER_PAGE + + IF Int( nPages ) < nPages + nPages++ + ENDIF + + xml := BasicXML():New() + + xml:append( '' ) + + xml:append( "" ) + FOR n := 1 TO nPages + xml:append( "" + LTrim( Str( n ) ) + "" ) + NEXT + xml:append( "" ) + +RETURN xml:toString() + + /** + * Replaces characters commonly used in XML with symbolic representations + * such that they are interpretted correctly by XML parsers. + * + * @param input + * the string to encode. + * @return the encoded version of the specified string + */ +METHOD xmlEncode( input ) CLASS TableManager + + LOCAL out, i, c + + IF input == NIL + RETURN input + ENDIF + + // Go through the input string and replace the following + // characters: + // & & + // ' ' + // " " + // < < + // > > + // [any non-ascii character] &#[character code]; + + out := "" + + FOR i := 1 TO Len( input ) + c := SubStr( input, i, 1 ) + switch ( c ) + case '&' + out += "&" + exit + case "'" + out += "'" + exit + case '"' + out += """ + exit + case '<' + out += "<" + exit + case '>' + out += ">" + exit + //case ' ' + // out += " " + // exit + case Chr( 9 ) //E'\t' + case Chr( 13 ) //E'\r' + case Chr( 10 ) //E'\n' + out += c + exit + OTHERWISE + // All non-ascii + if ( Asc( c ) <= 0x1F .OR. Asc( c ) >= 0x80 ) + out += "&#x" + hb_NumToHex( Asc( c ) ) + ";" + else + out += c + endif + exit + end + NEXT + +RETURN out + +CLASS BasicXML + VAR aData INIT {} + + METHOD New() CONSTRUCTOR + METHOD Append( cString ) INLINE aAdd( ::aData, cString ) + METHOD ToString() + +ENDCLASS + +METHOD New() CLASS BasicXML + +RETURN Self + +METHOD ToString() CLASS BasicXML + LOCAL s := "" + + aEval( ::aData, {|c| s += c + IIF( Right( c, 1 ) == ">", CRLF, "" ) } ) + +RETURN s + diff --git a/harbour/contrib/examples/uhttpd/modules/testajax.prg b/harbour/contrib/examples/uhttpd/modules/testajax.prg new file mode 100644 index 0000000000..344ad5c32c --- /dev/null +++ b/harbour/contrib/examples/uhttpd/modules/testajax.prg @@ -0,0 +1,69 @@ +/* + * $Id: rlcdx.prg 9754 2008-10-27 22:40:04Z vszakats $ + */ + +/* + * Harbour Project source code: + * simple ajax responder + * + * Copyright 2009 Francesco Saverio Giudice + * www - http://www.harbour-project.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/). + * + * As a special exception, the Harbour Project gives permission for + * additional uses of the text contained in its release of Harbour. + * + * The exception is that, if you link the Harbour libraries with other + * files to produce an executable, this does not by itself cause the + * resulting executable to be covered by the GNU General Public License. + * Your use of that executable is in no way restricted on account of + * linking the Harbour library code into it. + * + * This exception does not however invalidate any other reasons why + * the executable file might be covered by the GNU General Public License. + * + * This exception applies only to the code released by the Harbour + * Project under the name Harbour. If you copy code from other + * Harbour Project or Free Software Foundation releases into a copy of + * Harbour, as the General Public License permits, the exception does + * not apply to the code that you add in this way. To avoid misleading + * anyone as to the status of such modified files, you must delete + * this exception notice from them. + * + * If you write modifications of your own for Harbour, it is your choice + * whether to permit this exception to apply to your modifications. + * If you do not wish that, delete this exception notice. + * + */ + +#include "common.ch" + +MEMVAR _REQUEST + +FUNCTION HRBMAIN() + LOCAL cW + LOCAL cHtml := "" + + IF HB_HHasKey( _REQUEST, "w" ) + IF !Empty( cW := _REQUEST[ "w" ] ) + cHtml += "This is a reply from testajax : " + cW + ENDIF + ENDIF + +RETURN cHtml + + diff --git a/harbour/contrib/examples/uhttpd/readme.txt b/harbour/contrib/examples/uhttpd/readme.txt new file mode 100644 index 0000000000..d1178241aa --- /dev/null +++ b/harbour/contrib/examples/uhttpd/readme.txt @@ -0,0 +1,12 @@ + +uHTTPD server + +Build it using hbmk*.bat +For parameters run: +uhttpd -? + +Before starting please build modules in modules folder using bldhrb.bat + +Francesco + + diff --git a/harbour/contrib/examples/uhttpd/socket.c b/harbour/contrib/examples/uhttpd/socket.c new file mode 100644 index 0000000000..dd26fc9ac9 --- /dev/null +++ b/harbour/contrib/examples/uhttpd/socket.c @@ -0,0 +1,416 @@ +#include +#include "hbapi.h" +#include "hbapiitm.h" + +/* + + Function naming: + The intention of this library is to be as close as possible to the original + socket implementation. This supposed to be valid for function names also, + but some of the names are very platform dependent, ex., WSA*() functions. + select() function name is reserved for standard Harbour's function, so, + socket_*() prefix was used: + socket_init() - WSAStartup() + socket_exit() - WSACleanup() + socket_error() - WSALastError() + socket_select() - select() + Finally I renamed all functions to have socket_*() prefix to be more "prefix + compatible" and not to occupy a general function names like send(), bind(), + accept(), listen(), etc.: + socket_create() - socket() + socket_close() - closesocket() + socket_shutdown() - shutdown() + socket_bind() - bind() + socket_listen() - listen() + socket_accept() - accept() + socket_send() - send() + socket_recv() - recv() + socket_recv() - recv() + socket_getsockname() - getsockname() + socket_getpeername() - getpeername() + + + Types mapping: + SOCKET + UINT_PTR in Windows, let's map it to pointer type, and INVALID_SOCKET value to NIL + + struct sockaddr + It is not only IP addresses, also can be IPX, etc. All network-host byte order + conversion should be hidden from Harbour API. So, let's map to: + { adress_familly, ... } + AF_INET: { AF_INET, cAddr, nPort } + other: { AF_?, cAddressDump } +*/ + +#ifdef hb_parnidef +#undef hb_parnidef +#endif + + +static int hb_parnidef( int iParam, int iValue ) +{ + return ISNUM( iParam ) ? hb_parni( iParam ) : iValue; +} + + +static SOCKET hb_parsocket( int iParam ) +{ + return ISPOINTER( iParam ) ? ( SOCKET ) hb_parptr( 1 ) : INVALID_SOCKET; +} + + +static void hb_retsocket( SOCKET hSocket ) +{ + if( hSocket == INVALID_SOCKET ) + hb_ret(); + else + hb_retptr( ( void* ) hSocket ); +} + + +static SOCKET hb_itemGetSocket( PHB_ITEM pItem ) +{ + return HB_IS_POINTER( pItem ) ? ( SOCKET ) hb_itemGetPtr( pItem ) : INVALID_SOCKET; +} + + +static PHB_ITEM hb_itemPutSocket( PHB_ITEM pItem, SOCKET hSocket ) +{ + if( ! pItem ) + pItem = hb_itemNew( NULL ); + + if( hSocket == INVALID_SOCKET ) + hb_itemClear( pItem ); + else + hb_itemPutPtr( pItem, ( void* ) hSocket ); + + return pItem; +} + + +static void hb_itemGetSockaddr( PHB_ITEM pItem, struct sockaddr* sa ) +{ + memset( sa, 0, sizeof( struct sockaddr ) ); + + if( HB_IS_ARRAY( pItem ) ) + { + sa->sa_family = hb_arrayGetNI( pItem, 1 ); + + if( sa->sa_family == AF_INET ) + { + ( ( struct sockaddr_in* ) sa)->sin_addr.S_un.S_addr = inet_addr( hb_arrayGetCPtr( pItem, 2 ) ); + ( ( struct sockaddr_in* ) sa)->sin_port = htons( hb_arrayGetNI( pItem, 3 ) ); + } + else + { + ULONG ulLen = hb_arrayGetCLen( pItem, 2 ); + + if( ulLen > sizeof( sa->sa_data ) ) + ulLen = sizeof( sa->sa_data ); + memcpy( sa->sa_data, hb_arrayGetCPtr( pItem, 2 ), ulLen ); + } + } +} + + +static PHB_ITEM hb_itemPutSockaddr( PHB_ITEM pItem, const struct sockaddr* saddr ) +{ + pItem = hb_itemNew( pItem ); + + if( saddr->sa_family == AF_INET ) + { + hb_arrayNew( pItem, 3 ); + hb_arraySetNI( pItem, 1, saddr->sa_family ); + hb_arraySetC( pItem, 2, inet_ntoa( ( ( struct sockaddr_in* ) saddr )->sin_addr ) ); + hb_arraySetNI( pItem, 3, ntohs( ( ( struct sockaddr_in* ) saddr )->sin_port ) ); + } + else + { + hb_arrayNew( pItem, 2 ); + hb_arraySetNI( pItem, 1, saddr->sa_family ); + hb_arraySetCL( pItem, 2, saddr->sa_data, sizeof( saddr->sa_data ) ); + } + return pItem; +} + + +HB_FUNC ( SOCKET_INIT ) +{ + WSADATA wsad; + + hb_retni( WSAStartup( hb_parnidef( 1, 257 ), &wsad ) ); + hb_storclen( (char*) &wsad, sizeof( WSADATA ), 2 ); +} + + +HB_FUNC ( SOCKET_EXIT ) +{ + hb_retni( WSACleanup() ); +} + + +HB_FUNC ( SOCKET_ERROR ) +{ + hb_retni( WSAGetLastError() ); +} + + +HB_FUNC ( SOCKET_CREATE ) +{ + hb_retsocket( socket( hb_parnidef( 1, PF_INET ), + hb_parnidef( 2, SOCK_STREAM ), + hb_parnidef( 3, IPPROTO_TCP ) ) ); +} + + +HB_FUNC ( SOCKET_CLOSE ) +{ + hb_retni( closesocket( hb_parsocket( 1 ) ) ); +} + + +HB_FUNC ( SOCKET_BIND ) +{ + struct sockaddr sa; + + hb_itemGetSockaddr( hb_param( 2, HB_IT_ANY ), &sa ); + hb_retni( bind( hb_parsocket( 1 ), &sa, sizeof( struct sockaddr ) ) ); +} + + +HB_FUNC ( SOCKET_LISTEN ) +{ + hb_retni( listen( hb_parsocket( 1 ), hb_parnidef( 2, 10 ) ) ); +} + + +HB_FUNC ( SOCKET_ACCEPT ) +{ + struct sockaddr saddr; + int iSize = sizeof( struct sockaddr ); + + hb_retsocket( accept( hb_parsocket( 1 ), &saddr, &iSize ) ); + + if( ISBYREF( 2 ) ) + { + hb_itemParamStoreForward( 2, hb_itemPutSockaddr( NULL, &saddr ) ); + } +} + + +HB_FUNC ( SOCKET_SHUTDOWN ) +{ + hb_retni( shutdown( hb_parsocket( 1 ), hb_parnidef( 2, SD_BOTH ) ) ); +} + + +HB_FUNC ( SOCKET_RECV ) +{ + int iLen, iRet; + char* pBuf; + + iLen = hb_parni( 3 ); + + if( iLen > 65536 || iLen <= 0 ) + iLen = 4096; + + pBuf = ( char* ) hb_xgrab( ( ULONG ) iLen ); + iRet = recv( hb_parsocket( 1 ), pBuf, iLen, hb_parnidef( 4, 0 ) ); + hb_retni( iRet ); + hb_storclen( pBuf, iRet > 0 ? iRet : 0, 2 ); + hb_xfree( pBuf ); +} + + +HB_FUNC ( SOCKET_SEND ) +{ + hb_retni( send( hb_parsocket( 1 ), hb_parc( 2 ), hb_parclen( 2 ), hb_parni( 3, 0 ) ) ); +} + + +HB_FUNC ( SOCKET_SELECT ) +{ + fd_set setread, setwrite, seterror; + BOOL bRead = 0, bWrite = 0, bError = 0; + struct timeval tv; + SOCKET socket, maxsocket; + PHB_ITEM pArray, pItem; + ULONG ulLen, ulIndex, ulCount; + LONG lTimeout; + int iRet; + + + FD_ZERO( &setread ); + FD_ZERO( &setwrite ); + FD_ZERO( &seterror ); + + maxsocket = (SOCKET) 0; + + pArray = hb_param( 1, HB_IT_ARRAY ); + if( pArray ) + { + ulLen = hb_arrayLen( pArray ); + for( ulIndex = 1; ulIndex <= ulLen; ulIndex++ ) + { + socket = hb_itemGetSocket( hb_arrayGetItemPtr( pArray, ulIndex ) ); + if( socket != INVALID_SOCKET ) + { + bRead = 1; + FD_SET( socket, &setread ); + if( socket > maxsocket ) + maxsocket = socket; + } + } + } + + pArray = hb_param( 2, HB_IT_ARRAY ); + if( pArray ) + { + ulLen = hb_arrayLen( pArray ); + for( ulIndex = 1; ulIndex <= ulLen; ulIndex++ ) + { + socket = hb_itemGetSocket( hb_arrayGetItemPtr( pArray, ulIndex ) ); + if( socket != INVALID_SOCKET ) + { + bWrite = 1; + FD_SET( socket, &setwrite ); + if( socket > maxsocket ) + maxsocket = socket; + } + } + } + + pArray = hb_param( 3, HB_IT_ARRAY ); + if( pArray ) + { + ulLen = hb_arrayLen( pArray ); + for( ulIndex = 1; ulIndex <= ulLen; ulIndex++ ) + { + socket = hb_itemGetSocket( hb_arrayGetItemPtr( pArray, ulIndex ) ); + if( socket != INVALID_SOCKET ) + { + bError = 1; + FD_SET( socket, &seterror ); + if( socket > maxsocket ) + maxsocket = socket; + } + } + } + + /* Default forever */ + lTimeout = ISNUM( 4 ) ? hb_parnl( 4 ) : -1; + + if( lTimeout == -1 ) + { + iRet = select( maxsocket + 1, bRead ? &setread : NULL, bWrite ? &setwrite: NULL, + bError ? &seterror : NULL, NULL ); + } + else + { + tv.tv_sec = lTimeout / 1000; + tv.tv_usec = ( lTimeout % 1000 ) * 1000; + iRet = select( maxsocket + 1, bRead ? &setread : NULL, bWrite ? &setwrite: NULL, + bError ? &seterror : NULL, &tv ); + } + + pArray = hb_param( 1, HB_IT_ARRAY ); + if( pArray && ISBYREF( 1 ) ) + { + ulLen = hb_arrayLen( pArray ); + pItem = hb_itemNew( NULL ); + hb_arrayNew( pItem, ulLen ); + ulCount = 0; + for( ulIndex = 1; ulIndex <= ulLen; ulIndex++ ) + { + socket = hb_itemGetSocket( hb_arrayGetItemPtr( pArray, ulIndex ) ); + if( socket != INVALID_SOCKET ) + { + if( FD_ISSET( socket, &setread ) ) + { + hb_arraySetForward( pItem, ++ulCount, hb_itemPutSocket( NULL, socket ) ); + } + } + } + hb_itemParamStoreForward( 1, pItem ); + } + + pArray = hb_param( 2, HB_IT_ARRAY ); + if( pArray && ISBYREF( 2 ) ) + { + ulLen = hb_arrayLen( pArray ); + pItem = hb_itemNew( NULL ); + hb_arrayNew( pItem, ulLen ); + ulCount = 0; + for( ulIndex = 1; ulIndex <= ulLen; ulIndex++ ) + { + socket = hb_itemGetSocket( hb_arrayGetItemPtr( pArray, ulIndex ) ); + if( socket != INVALID_SOCKET ) + { + if( FD_ISSET( socket, &setwrite ) ) + { + hb_arraySetForward( pItem, ++ulCount, hb_itemPutSocket( NULL, socket ) ); + } + } + } + hb_itemParamStoreForward( 2, pItem ); + } + + pArray = hb_param( 3, HB_IT_ARRAY ); + if( pArray && ISBYREF( 3 ) ) + { + ulLen = hb_arrayLen( pArray ); + pItem = hb_itemNew( NULL ); + hb_arrayNew( pItem, ulLen ); + ulCount = 0; + for( ulIndex = 1; ulIndex <= ulLen; ulIndex++ ) + { + socket = hb_itemGetSocket( hb_arrayGetItemPtr( pArray, ulIndex ) ); + if( socket != INVALID_SOCKET ) + { + if( FD_ISSET( socket, &seterror ) ) + { + hb_arraySetForward( pItem, ++ulCount, hb_itemPutSocket( NULL, socket ) ); + } + } + } + hb_itemParamStoreForward( 3, pItem ); + } + + hb_retni( iRet ); +} + + +HB_FUNC ( SOCKET_GETSOCKNAME ) +{ + struct sockaddr saddr; + int iSize = sizeof( struct sockaddr ); + + hb_retni( getsockname( hb_parsocket( 1 ), &saddr, &iSize ) ); + if( ISBYREF( 2 ) ) + { + hb_itemParamStoreForward( 2, hb_itemPutSockaddr( NULL, &saddr ) ); + } +} + + +HB_FUNC ( SOCKET_GETPEERNAME ) +{ + struct sockaddr saddr; + int iSize = sizeof( struct sockaddr ); + + hb_retni( getpeername( hb_parsocket( 1 ), &saddr, &iSize ) ); + if( ISBYREF( 2 ) ) + { + hb_itemParamStoreForward( 2, hb_itemPutSockaddr( NULL, &saddr ) ); + } +} + + +HB_FUNC ( CONNECT ) +{ + struct sockaddr sa; + + hb_itemGetSockaddr( hb_param( 2, HB_IT_ANY ), &sa ); + hb_retni( connect( hb_parsocket( 1 ), &sa, sizeof( struct sockaddr ) ) ); +} + diff --git a/harbour/contrib/examples/uhttpd/uhttpd.prg b/harbour/contrib/examples/uhttpd/uhttpd.prg new file mode 100644 index 0000000000..a73681475b --- /dev/null +++ b/harbour/contrib/examples/uhttpd/uhttpd.prg @@ -0,0 +1,1650 @@ +/* + * $Id: rlcdx.prg 9754 2008-10-27 22:40:04Z vszakats $ + */ + +/* + * Harbour Project source code: + * uHTTPD (Micro HTTP server) + * + * Copyright 2009 Francesco Saverio Giudice + * Copyright 2008 Mindaugas Kavaliauskas (dbtopas at dbtopas.lt) + * www - http://www.harbour-project.org + * + * Credits: + * Based on first version posted from Mindaugas Kavaliauskas on + * developers NG on December 15th, 2008 whom give my thanks to have + * shared initial work. + * Francesco. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/). + * + * As a special exception, the Harbour Project gives permission for + * additional uses of the text contained in its release of Harbour. + * + * The exception is that, if you link the Harbour libraries with other + * files to produce an executable, this does not by itself cause the + * resulting executable to be covered by the GNU General Public License. + * Your use of that executable is in no way restricted on account of + * linking the Harbour library code into it. + * + * This exception does not however invalidate any other reasons why + * the executable file might be covered by the GNU General Public License. + * + * This exception applies only to the code released by the Harbour + * Project under the name Harbour. If you copy code from other + * Harbour Project or Free Software Foundation releases into a copy of + * Harbour, as the General Public License permits, the exception does + * not apply to the code that you add in this way. To avoid misleading + * anyone as to the status of such modified files, you must delete + * this exception notice from them. + * + * If you write modifications of your own for Harbour, it is your choice + * whether to permit this exception to apply to your modifications. + * If you do not wish that, delete this exception notice. + * + */ + +/* + * A simple HTTP server. + * + * More description to come. + * + * + */ + +/* + TODO: + - Add ini file for switches (port it from another project) + - Add aliases from ini file + +*/ + + +#define APP_NAME "uhttpd" +#define APP_VERSION "0.1" + +#ifndef _XHARBOUR_ + #include "hbcompat.ch" +#endif +#include "fileio.ch" +#include "common.ch" +#include "inkey.ch" + +#include "hbextern.ch" // need this to use with HRB +// adding GD support +REQUEST GDIMAGE, gdImageChar, GDCHART + +#define AF_INET 2 + +// default values - they can changes using line command switch +#define START_RUNNING_THREADS 4 // Start threads to serve connections +#define MAX_RUNNING_THREADS 20 // Max running threads + +#define START_SERVICE_THREADS 1 // Initial number for service connections +#define MAX_SERVICE_THREADS 3 // Max running threads + +#define LISTEN_PORT 8082 // differs from standard 80 port for tests in case + // anyone has a apache/IIS installed +#define FILE_STOP ".uhttpd.stop" +#define FILE_ACCESS_LOG "logs\access.log" +#define FILE_ERROR_LOG "logs\error.log" + +#define PAGE_STATUS_REFRESH 1 +#define THREAD_MAX_WAIT ( 60 ) // HOW MUCH TIME THREAD HAS TO WAIT BEFORE FINISH - IN SECONDS + +#define CR_LF (CHR(13)+CHR(10)) +#define HB_IHASH() HB_HSETCASEMATCH( {=>}, FALSE ) + +#ifndef _XHARBOUR_ + + #ifdef __PLATFORM__WINDOWS + REQUEST HB_GT_WVT_DEFAULT + REQUEST HB_GT_WIN + REQUEST HB_GT_NUL + #ifdef HB_MT_VM + #define THREAD_GT hb_gtVersion() + #endif + #else + REQUEST HB_GT_STD_DEFAULT + REQUEST HB_GT_NUL + #define THREAD_GT "XWC" + #endif + +#else + + REQUEST HB_GT_WVT + REQUEST HB_GT_WIN + REQUEST HB_GT_NUL + +#endif + +// dynamic call for HRB support +DYNAMIC HRBMAIN + +STATIC hmtxQueue, hmtxServiceThreads, hmtxRunningThreads, hmtxLog, hmtxConsole, hmtxBusy +STATIC hmtxHRB +STATIC s_hfileLogAccess, s_hfileLogError, s_cDocumentRoot, s_lIndexes, s_lConsole, s_nPort +STATIC s_nThreads, s_nStartThreads, s_nMaxThreads +STATIC s_nServiceThreads, s_nStartServiceThreads, s_nMaxServiceThreads +STATIC s_nConnections, s_nMaxConnections, s_nTotConnections +STATIC s_nServiceConnections, s_nMaxServiceConnections, s_nTotServiceConnections +STATIC s_aRunningThreads := {} +STATIC s_aServiceThreads := {} + +// TODO: add aliases from ini file +STATIC s_hFileAliases := { "/info" => "/cgi-bin/info.hrb", "/wait" => "/cgi-bin/wait.hrb" } + +THREAD STATIC s_cResult, s_nStatusCode, s_aHeader, s_cErrorMsg + +MEMVAR _SERVER, _GET, _POST, _REQUEST, _HTTP_REQUEST, m_cPost + +FUNCTION MAIN( ... ) +LOCAL nPort, hListen, hSocket, aRemote, nI, cI +LOCAL hThread, aThreads, nStartThreads, nMaxThreads, nStartServiceThreads +LOCAL i, cPar, lStop +LOCAL cGT, cDocumentRoot, lIndexes, cConfig, cPort, nNewStartThreads, nNewMaxThreads +LOCAL lConsole +LOCAL nProgress := 0 + + IF !HB_MTVM() + ? "I need multhread support. Please, recompile me!" + WAIT + RETURN 2 + ENDIF + + // ----------------------- Initializations --------------------------------- + + SysSettings() + + // ----------------------- Parameters defaults ----------------------------- + + // defaults + nPort := LISTEN_PORT + lStop := FALSE + cDocumentRoot := EXE_Path() + "\home" + lIndexes := FALSE + cConfig := EXE_Path() + "\" + APP_NAME + ".ini" + lConsole := TRUE + nStartThreads := START_RUNNING_THREADS + nMaxThreads := MAX_RUNNING_THREADS + nStartServiceThreads := START_SERVICE_THREADS + + // Check GT version - if I have started app with //GT:NUL then I have to disable + // console + cGT := HB_GT_VERSION() + IF ( cGT == "NUL" ) + lConsole := FALSE + ENDIF + + // TOCHECK: per il momento non forzo + //HB_HSETCASEMATCH( s_hFileAliases, FALSE ) + + // ----------------- Line command parameters checking ---------------------- + + i := 1 + while ( i <= PCount() ) + + cPar := hb_PValue( i++ ) + + do case + case cPar == "--port" .OR. cPar == "-p" + cPort := hb_PValue( i++ ) + + case cPar == "--docroot" .OR. cPar == "-d" + cDocumentRoot := hb_PValue( i++ ) + + case cPar == "--indexes" .OR. cPar == "-i" + lIndexes := TRUE + + case cPar == "--stop" .OR. cPar == "-s" + lStop := TRUE + + case cPar == "--config" .OR. cPar == "-c" + cConfig := hb_PValue( i++ ) + + case cPar == "--start-threads" .OR. cPar == "-ts" + nNewStartThreads := hb_PValue( i++ ) + + case cPar == "--max-threads" .OR. cPar == "-tm" + nNewMaxThreads := hb_PValue( i++ ) + + case cPar == "--help" .OR. Lower( cPar ) == "-h" .OR. cPar == "-?" + help() + RETURN 0 + + otherwise + help() + RETURN 0 + endcase + enddo + + // -------------------- checking STOP request ------------------------------- + + IF lStop + HB_MEMOWRIT( FILE_STOP, "" ) + RETURN 0 + ELSE + FERASE( FILE_STOP ) + ENDIF + + // -------------------- checking starting values ---------------------------- + + IF cPort != NIL + nPort := VAL( cPort ) + IF nPort <= 0 .OR. nPort > 65535 + ? "Invalid port number:", nPort + WAIT + RETURN 1 + ENDIF + ENDIF + + + IF HB_ISSTRING( cDocumentRoot ) + //cI := STRTRAN( SUBSTR( cDocumentRoot, 2 ), "\", "/" ) + cI := cDocumentRoot + IF HB_DirExists( cI ) + IF RIGHT( cI, 1 ) == "/" .AND. LEN(cI) > 2 .AND. SUBSTR( cI, LEN( cI ) - 2, 1 ) != ":" + s_cDocumentRoot := LEFT( cI, LEN( cI ) - 1 ) + ELSE + s_cDocumentRoot := cI + ENDIF + ELSE + ? "Invalid document root:", cI + WAIT + RETURN 3 + ENDIF + ELSE + ? "Invalid document root" + WAIT + RETURN 3 + ENDIF + + IF HB_ISNUMERIC( nNewMaxThreads ) .AND. ; + nNewMaxThreads > 0 + nMaxThreads := nNewMaxThreads + ENDIF + + IF HB_ISNUMERIC( nNewStartThreads ) .AND. ; + nNewStartThreads > 0 + IF nNewStartThreads <= nMaxThreads + nStartThreads := nNewStartThreads + ELSE + nStartThreads := nMaxThreads + ENDIF + ENDIF + + // -------------------- assign STATIC values -------------------------------- + + s_lIndexes := lIndexes + s_lConsole := lConsole + s_nPort := nPort + s_nThreads := 0 + s_nStartThreads := nStartThreads + s_nMaxThreads := nMaxThreads + s_nServiceThreads := 0 + s_nStartServiceThreads := nStartServiceThreads + s_nMaxServiceThreads := MAX_SERVICE_THREADS + s_nConnections := 0 + s_nMaxConnections := 0 + s_nTotConnections := 0 + s_nServiceConnections := 0 + s_nMaxServiceConnections := 0 + s_nTotServiceConnections := 0 + + // --------------------- Open log files ------------------------------------- + + IF ( s_hfileLogAccess := FOPEN( FILE_ACCESS_LOG, FO_CREAT + FO_WRITE ) ) == -1 + ? "Can't open access log file" + WAIT + RETURN 1 + ENDIF + FSEEK( s_hfileLogAccess, 0, FS_END ) + + IF ( s_hfileLogError := FOPEN( FILE_ERROR_LOG, FO_CREAT + FO_WRITE ) ) == -1 + ? "Can't open error log file" + WAIT + RETURN 1 + ENDIF + FSEEK( s_hfileLogError, 0, FS_END ) + + // --------------------- MAIN PART ------------------------------------------ + + SET CURSOR OFF + + // --------------------- define mutexes ------------------------------------- + + hmtxQueue := hb_mutexCreate() + hmtxLog := hb_mutexCreate() + hmtxConsole := hb_mutexCreate() + hmtxBusy := hb_mutexCreate() + hmtxRunningThreads := hb_mutexCreate() + hmtxServiceThreads := hb_mutexCreate() + hmtxHRB := hb_mutexCreate() + + WriteToConsole( "--- Starting " + APP_NAME + " ---" ) + + // -------------------------------------------------------------------------- + // SOCKET CREATION + // -------------------------------------------------------------------------- + + hListen := socket_create() + IF socket_bind( hListen, { AF_INET, "0.0.0.0", nPort } ) == -1 + ? "bind() error", socket_error() + ELSEIF socket_listen( hListen ) == -1 + ? "listen() error", socket_error() + ELSE + + // --------------------------------------------------------------------------------- // + // Starting Accept connection thread + // --------------------------------------------------------------------------------- // + + WriteToConsole( "Starting AcceptConnection Thread" ) + aThreads := {} + //FOR nI := 1 TO 1 // s_nMaxThreads + AADD( aThreads, hb_threadStart( @AcceptConnections() ) ) + //NEXT + + // --------------------------------------------------------------------------------- // + // main loop + // --------------------------------------------------------------------------------- // + + WriteToConsole( "Starting main loop" ) + + IF s_lConsole + hb_DispOutAt( 1, 5, APP_NAME + " - web server - v. " + APP_VERSION ) + hb_DispOutAt( 4, 5, "Server listening (Port: " + LTrim( Str( nPort ) ) + ") : ..." ) + hb_DispOutAt( 10, 9, "Waiting." ) + ENDIF + + DO WHILE .T. + + // windows resource releasing - 1 millisecond wait + WIN_SYSREFRESH( 1 ) + + IF s_lConsole + + // Show application infos + IF hb_mutexLock( hmtxBusy ) + hb_DispOutAt( 5, 5, "Threads : " + Transform( s_nThreads, "9999999999" ) ) + hb_DispOutAt( 6, 5, "Connections : " + Transform( s_nConnections, "9999999999" ) ) + hb_DispOutAt( 7, 5, "Max Connections : " + Transform( s_nMaxConnections, "9999999999" ) ) + hb_DispOutAt( 8, 5, "Total Connections : " + Transform( s_nTotConnections, "9999999999" ) ) + + hb_DispOutAt( 5, 37, "ServiceThreads : " + Transform( s_nServiceThreads, "9999999999" ) ) + hb_DispOutAt( 6, 37, "Connections : " + Transform( s_nServiceConnections, "9999999999" ) ) + hb_DispOutAt( 7, 37, "Max Connections : " + Transform( s_nMaxServiceConnections, "9999999999" ) ) + hb_DispOutAt( 8, 37, "Total Connections : " + Transform( s_nTotServiceConnections, "9999999999" ) ) + hb_mutexUnlock( hmtxBusy ) + ENDIF + + // Show progress + Progress( @nProgress ) + ENDIF + + // Wait a connection + IF ( nI := socket_select( { hListen },,, 50 ) ) > 0 + + // reset remote values + aRemote := NIL + + // Accept a remote connection + hSocket := socket_accept( hListen, @aRemote ) + + IF hSocket == NIL + + WriteToConsole( hb_sprintf( "accept() error: %s", socket_error() ) ) + + ELSE + + // Send accepted connection to AcceptConnections() thread + hb_mutexNotify( hmtxQueue, hSocket ) + + ENDIF + + ELSE + + // Checking if I have to quit + IF HB_FileExists( FILE_STOP ) + FERASE( FILE_STOP ) + EXIT + ENDIF + + ENDIF + + ENDDO + + WriteToConsole( "Waiting threads" ) + // Send to thread that they have to stop + AEVAL( aThreads, {|| hb_mutexNotify( hmtxQueue, NIL ) } ) + // Wait threads to end + AEVAL( aThreads, {|h| hb_threadJoin( h ) } ) + + ENDIF + + WriteToConsole( "--- Quitting " + APP_NAME + " ---" ) + + // Close socket + socket_close( hListen ) + + // Close log files + FCLOSE( s_hfileLogAccess ) + FCLOSE( s_hfileLogError ) + + SET CURSOR ON + +RETURN 0 + +// --------------------------------------------------------------------------------- // +// THREAD FUNCTIONS +// --------------------------------------------------------------------------------- // + +STATIC FUNCTION AcceptConnections() + LOCAL hSocket + LOCAL nConnections, nThreads, nMaxThreads, n + LOCAL nServiceConnections, nServiceThreads, nMaxServiceThreads, nThreadID + LOCAL pThread + + WriteToConsole( "Starting AcceptConnections()" ) + + // Starting initial running threads + FOR n := 1 TO s_nStartThreads + pThread := hb_threadStart( @ProcessConnection(), @nThreadID ) + AADD( s_aRunningThreads, { pThread, nThreadID } ) + NEXT + + // Starting initial service threads + FOR n := 1 TO s_nStartServiceThreads + pThread := hb_threadStart( @ServiceConnection(), @nThreadID ) + AADD( s_aServiceThreads, { pThread, nThreadID } ) + NEXT + + // Main AcceptConnections loop + DO WHILE .T. + + // reset socket + hSocket := NIL + + // releasing resources + WIN_SYSREFRESH( 1 ) + + // Waiting a connection from main application loop + hb_mutexSubscribe( hmtxQueue,, @hSocket ) + + // I have a QUIT request + IF hSocket == NIL + + // Requesting to Running threads to quit (using -1 value) + AEVAL( s_aRunningThreads, {|| hb_mutexNotify( hmtxRunningThreads, -1 ) } ) + // waiting running threads to quit + AEVAL( s_aRunningThreads, {|h| hb_threadJoin( h[1] ) } ) + + // Requesting to Service threads to quit (using -1 value) + AEVAL( s_aServiceThreads, {|| hb_mutexNotify( hmtxServiceThreads, -1 ) } ) + // waiting service threads to quit + AEVAL( s_aServiceThreads, {|h| hb_threadJoin( h[1] ) } ) + + EXIT + ENDIF + + // Load current state + IF hb_mutexLock( hmtxBusy ) + nConnections := s_nConnections + nThreads := s_nThreads + nMaxThreads := s_nMaxThreads + nServiceConnections:= s_nServiceConnections + nServiceThreads := s_nServiceThreads + nMaxServiceThreads := s_nMaxServiceThreads + hb_mutexUnlock( hmtxBusy ) + ENDIF + + // If I have no more thread to use ... + IF nConnections > nMaxThreads + + // If I have no more of service threads to use ... (DOS attack ?) + IF nServiceConnections > nMaxServiceThreads + // DROP connection + socket_shutdown( hSocket ) + socket_close( hSocket ) + + // If I have no service threads in use ... + ELSEIF nServiceConnections >= nServiceThreads + // Add one more + pThread := hb_threadStart( @ServiceConnection(), @nThreadID ) + AADD( s_aServiceThreads, { pThread, nThreadID } ) + ENDIF + // Otherwise I send connection to service thread + hb_mutexNotify( hmtxServiceThreads, hSocket ) + + LOOP + + // If I have no running threads in use ... + ELSEIF nConnections >= nThreads + // Add one more + pThread := hb_threadStart( @ProcessConnection(), @nThreadID ) + AADD( s_aRunningThreads, { pThread, nThreadID } ) + ENDIF + // Otherwise I send connection to running thread + hb_mutexNotify( hmtxRunningThreads, hSocket ) + + ENDDO + + WriteToConsole( "Quitting AcceptConnections()" ) + +RETURN 0 + +// --------------------------------------------------------------------------------- // +// CONNECTIONS +// --------------------------------------------------------------------------------- // +STATIC FUNCTION ProcessConnection( nThreadIdRef ) +LOCAL hSocket, cBuf, nLen, cRequest, cSend, aI +LOCAL nMsecs, nParseTime, nPos +LOCAL nThreadId + + nThreadId := hb_threadID() + nThreadIdRef := nThreadId + + WriteToConsole( "Starting ProcessConnections() " + hb_CStr( nThreadId ) ) + + IF hb_mutexLock( hmtxBusy ) + s_nThreads++ + hb_mutexUnlock( hmtxBusy ) + ENDIF + + // ProcessConnection Loop + DO WHILE .T. + + // Reset socket + hSocket := NIL + + // releasing resources + WIN_SYSREFRESH( 1 ) + + // Waiting a connection from AcceptConnections() but up to defined time + hb_mutexSubscribe( hmtxRunningThreads, THREAD_MAX_WAIT, @hSocket ) + + // received a -1 value, I have to quit + IF HB_ISNUMERIC( hSocket ) + EXIT + // no socket received, thread can graceful quit only if over minimal number + ELSEIF hSocket == NIL + IF hb_mutexLock( hmtxBusy ) + IF s_nThreads <= s_nStartThreads + hb_mutexUnlock( hmtxBusy ) + LOOP + ENDIF + hb_mutexUnlock( hmtxBusy ) + ENDIF + EXIT + ENDIF + + // Connection accepted + IF hb_mutexLock( hmtxBusy ) + s_nConnections++ + s_nTotConnections++ + s_nMaxConnections := Max( s_nConnections, s_nMaxConnections ) + hb_mutexUnlock( hmtxBusy ) + ENDIF + + // Save initial time + nMsecs := hb_milliseconds() + + BEGIN SEQUENCE + + /* receive query */ + cRequest := "" + nLen := 1 + DO WHILE AT( CR_LF + CR_LF, cRequest ) == 0 .AND. nLen > 0 + nLen := socket_recv( hSocket, @cBuf ) + cRequest += cBuf + ENDDO + + IF nLen == -1 + ? "recv() error:", socket_error() + ELSEIF nLen == 0 /* connection closed */ + ELSE + + //hb_ToOutDebug( "cRequest -- INIZIO --\n\r%s\n\rcRequest -- FINE --\n\r", cRequest ) + + PRIVATE _SERVER := HB_IHASH(), _GET := HB_IHASH(), _POST := HB_IHASH(), _REQUEST := HB_IHASH(), _HTTP_REQUEST := HB_IHASH(), m_cPost + s_cResult := "" + s_aHeader := {} + s_nStatusCode := 200 + s_cErrorMsg := "" + + IF socket_getpeername( hSocket, @aI ) != -1 + _SERVER["REMOTE_ADDR"] := aI[2] + _SERVER["REMOTE_HOST"] := _SERVER["REMOTE_ADDR"] // no reverse DNS + _SERVER["REMOTE_PORT"] := aI[3] + ENDIF + + IF socket_getsockname( hSocket, @aI ) != -1 + _SERVER["SERVER_ADDR"] := aI[2] + _SERVER["SERVER_PORT"] := aI[3] + ENDIF + + IF ParseRequest( cRequest ) + //hb_ToOutDebug( "_SERVER = %s,\n\r _GET = %s,\n\r _POST = %s,\n\r _REQUEST = %s,\n\r _HTTP_REQUEST = %s\n\r", hb_ValToExp( _SERVER ), hb_ValToExp( _GET ), hb_ValToExp( _POST ), hb_ValToExp( _REQUEST ), hb_ValToExp( _HTTP_REQUEST ) ) + define_Env( _SERVER ) + uproc_default( s_cDocumentRoot, s_lIndexes ) + ELSE + uSetStatusCode( 400 ) + ENDIF + cSend := MakeResponse() + + //hb_ToOutDebug( "cSend = %s\n\r", cSend ) + + DO WHILE LEN( cSend ) > 0 + IF ( nLen := socket_send( hSocket, cSend ) ) == -1 + ? "send() error:", socket_error() + WriteToConsole( hb_sprintf( "ProcessConnection() - send() error: %s, cSend = %s, hSocket = %s", socket_error(), cSend, hSocket ) ) + EXIT + ELSEIF nLen > 0 + cSend := SUBSTR( cSend, nLen + 1 ) + ENDIF + ENDDO + + WriteToLog( cRequest ) + + ENDIF + socket_shutdown( hSocket ) + socket_close( hSocket ) + END SEQUENCE + + nParseTime := hb_milliseconds() - nMsecs + WriteToConsole( "Page served in : " + Str( nParseTime/1000, 10, 7 ) + " seconds" ) + + IF hb_mutexLock( hmtxBusy ) + s_nConnections-- + hb_mutexUnlock( hmtxBusy ) + ENDIF + + ENDDO + + WriteToConsole( "Quitting ProcessConnections() " + hb_CStr( nThreadId ) ) + + IF hb_mutexLock( hmtxBusy ) + s_nThreads-- + IF ( nPos := aScan( s_aRunningThreads, {|h| h[2] == nThreadId } ) > 0 ) + hb_aDel( s_aRunningThreads, nPos, TRUE ) + ENDIF + hb_mutexUnlock( hmtxBusy ) + ENDIF + +RETURN 0 + +STATIC FUNCTION ServiceConnection( nThreadIdRef ) +LOCAL hSocket, cBuf, nLen, cRequest, cSend, aI +LOCAL nMsecs, nParseTime, nPos +LOCAL nThreadId +LOCAL nError := 500013 + + nThreadId := hb_threadID() + nThreadIdRef := nThreadId + + WriteToConsole( "Starting ServiceConnections() " + hb_CStr( nThreadId ) ) + + IF hb_mutexLock( hmtxBusy ) + s_nServiceThreads++ + hb_mutexUnlock( hmtxBusy ) + ENDIF + + DO WHILE .T. + + // Reset socket + hSocket := NIL + + // releasing resources + WIN_SYSREFRESH( 1 ) + + // Waiting a connection from AcceptConnections() but up to defined time + hb_mutexSubscribe( hmtxServiceThreads, THREAD_MAX_WAIT, @hSocket ) + + // received a -1 value, I have to quit + IF HB_ISNUMERIC( hSocket ) + EXIT + // no socket received, thread can graceful quit only if over minimal number + ELSEIF hSocket == NIL + IF hb_mutexLock( hmtxBusy ) + IF s_nServiceThreads <= s_nStartServiceThreads + hb_mutexUnlock( hmtxBusy ) + LOOP + ENDIF + hb_mutexUnlock( hmtxBusy ) + ENDIF + EXIT + ENDIF + + // Connection accepted + IF hb_mutexLock( hmtxBusy ) + s_nServiceConnections++ + s_nTotServiceConnections++ + s_nMaxServiceConnections := Max( s_nServiceConnections, s_nMaxServiceConnections ) + hb_mutexUnlock( hmtxBusy ) + ENDIF + + // Save initial time + nMsecs := hb_milliseconds() + + BEGIN SEQUENCE + + /* receive query */ + cRequest := "" + nLen := 1 + DO WHILE AT( CR_LF + CR_LF, cRequest ) == 0 .AND. nLen > 0 + nLen := socket_recv( hSocket, @cBuf ) + cRequest += cBuf + ENDDO + + IF nLen == -1 + ? "recv() error:", socket_error() + ELSEIF nLen == 0 /* connection closed */ + ELSE + + //hb_ToOutDebug( "cRequest -- INIZIO --\n\r%s\n\rcRequest -- FINE --\n\r", cRequest ) + + PRIVATE _SERVER := HB_IHASH(), _GET := HB_IHASH(), _POST := HB_IHASH(), _REQUEST := HB_IHASH(), _HTTP_REQUEST := HB_IHASH(), m_cPost + s_cResult := "" + s_aHeader := {} + s_nStatusCode := 200 + s_cErrorMsg := "" + + IF socket_getpeername( hSocket, @aI ) != -1 + _SERVER["REMOTE_ADDR"] := aI[2] + _SERVER["REMOTE_HOST"] := _SERVER["REMOTE_ADDR"] // no reverse DNS + _SERVER["REMOTE_PORT"] := aI[3] + ENDIF + + IF socket_getsockname( hSocket, @aI ) != -1 + _SERVER["SERVER_ADDR"] := aI[2] + _SERVER["SERVER_PORT"] := aI[3] + ENDIF + + IF ParseRequest( cRequest ) + //hb_ToOutDebug( "_SERVER = %s,\n\r _GET = %s,\n\r _POST = %s,\n\r _REQUEST = %s,\n\r _HTTP_REQUEST = %s\n\r", hb_ValToExp( _SERVER ), hb_ValToExp( _GET ), hb_ValToExp( _POST ), hb_ValToExp( _REQUEST ), hb_ValToExp( _HTTP_REQUEST ) ) + define_Env( _SERVER ) + ENDIF + // Error page served + uSetStatusCode( nError ) + cSend := MakeResponse() + + DO WHILE LEN( cSend ) > 0 + IF ( nLen := socket_send( hSocket, cSend ) ) == -1 + ? "send() error:", socket_error() + WriteToConsole( hb_sprintf( "ServiceConnection() - send() error: %s, cSend = %s, hSocket = %s", socket_error(), cSend, hSocket ) ) + EXIT + ELSEIF nLen > 0 + cSend := SUBSTR( cSend, nLen + 1 ) + ENDIF + ENDDO + + WriteToLog( cRequest ) + + ENDIF + socket_shutdown( hSocket ) + socket_close( hSocket ) + END SEQUENCE + + nParseTime := hb_milliseconds() - nMsecs + WriteToConsole( "Page served in : " + Str( nParseTime/1000, 10, 7 ) + " seconds" ) + + IF hb_mutexLock( hmtxBusy ) + s_nServiceConnections-- + hb_mutexUnlock( hmtxBusy ) + ENDIF + + ENDDO + + WriteToConsole( "Quitting ServiceConnections() " + hb_CStr( nThreadId ) ) + + IF hb_mutexLock( hmtxBusy ) + s_nServiceThreads-- + IF ( nPos := aScan( s_aServiceThreads, {|h| h[2] == nThreadId } ) > 0 ) + hb_aDel( s_aServiceThreads, nPos, TRUE ) + ENDIF + hb_mutexUnlock( hmtxBusy ) + ENDIF + +RETURN 0 + +STATIC FUNCTION ParseRequest( cRequest ) +LOCAL aRequest, aLine, nI, nJ, cI +LOCAL cReq, aVal, cPost + + // RFC2616 + aRequest := split( CR_LF, cRequest ) + + //hb_ToOutDebug( "aRequest = %s\n\r", hb_ValToExp( aRequest ) ) + + WriteToConsole( aRequest[1] ) + aLine := split( " ", aRequest[1] ) + IF LEN( aLine ) != 3 .OR. ; + ( aLine[1] != "GET" .AND. aLine[1] != "POST" ) .OR. ; // Sorry, we support GET and POST only + LEFT( aLine[3], 5 ) != "HTTP/" + RETURN .F. + ENDIF + + // define _SERVER var + _SERVER["REQUEST_METHOD"] := aLine[1] + _SERVER["REQUEST_URI"] := aLine[2] + _SERVER["SERVER_PROTOCOL"] := aLine[3] + + 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_CACHE_CONTROL"] := "" + + FOR nI := 2 TO LEN( aRequest ) + IF aRequest[nI] == ""; EXIT + ELSEIF ( nJ := AT( ":", aRequest[nI] ) ) > 0 + cI := LTRIM( SUBSTR( aRequest[nI], nJ + 1)) + SWITCH UPPER( LEFT( aRequest[nI], nJ - 1)) + CASE "ACCEPT" + CASE "ACCEPT-CHARSET" + CASE "ACCEPT-ENCODING" + CASE "ACCEPT-LANGUAGE" + CASE "CACHE-CONTROL" + CASE "CONNECTION" + CASE "KEEP-ALIVE" + CASE "REFERER" + CASE "USER-AGENT" + _SERVER[ "HTTP_" + STRTRAN( UPPER( LEFT( aRequest[nI], nJ - 1 ) ), "-", "_" ) ] := cI + EXIT + CASE "HOST" + aVal := split( ":", aRequest[ nI ] ) + _SERVER[ "HTTP_" + STRTRAN( UPPER( aVal[ 1 ] ), "-", "_")] := AllTrim( aVal[ 2 ] ) + EXIT + CASE "CONTENT-TYPE" + CASE "CONTENT-LENGTH" + _SERVER[ STRTRAN( UPPER( LEFT( aRequest[ nI ], nJ - 1 ) ), "-", "_" ) ] := cI + EXIT + ENDSWITCH + ENDIF + NEXT + + // GET vars + FOR EACH cI IN split( "&", _SERVER["QUERY_STRING"] ) + IF ( nI := AT( "=", cI ) ) > 0 + _GET[ LEFT( cI, nI - 1 )] := SUBSTR( cI, nI + 1 ) + _REQUEST[ LEFT( cI, nI - 1 )] := SUBSTR( cI, nI + 1 ) + ELSE + _GET[ cI ] := "" + _REQUEST[ cI ] := "" + ENDIF + NEXT + + // Load _HTTP_REQUEST + FOR EACH cReq IN aRequest + IF cReq:__enumIndex() == 1 // GET request + hb_HSet( _HTTP_REQUEST, "HTTP Request", cReq ) + ELSEIF Empty( cReq ) + EXIT + ELSE + aVal := split( ":", cReq, 1 ) + hb_HSet( _HTTP_REQUEST, aVal[ 1 ], IIF( Len( aVal ) == 2, AllTrim( aVal[ 2 ] ), NIL ) ) + ENDIF + NEXT + + // POST vars + IF "POST" $ Upper( _SERVER[ 'REQUEST_METHOD' ] ) + //hb_ToOutDebug( "POST: %s\n\r", aTail( aRequest ) ) + //cPost := SubStr( aTail( aRequest ), 1, _SERVER[ 'CONTENT_LENGTH' ] ) + cPost := aTail( aRequest ) + FOR EACH cI IN split( "&", cPost ) + IF ( nI := AT( "=", cI ) ) > 0 + _POST[ LEFT( cI, nI - 1 )] := SUBSTR( cI, nI + 1 ) + _REQUEST[ LEFT( cI, nI - 1 )] := SUBSTR( cI, nI + 1 ) + ELSE + _POST[ cI ] := "" + _REQUEST[ cI ] := "" + ENDIF + NEXT + m_cPost := cPost + ENDIF + + // Complete _SERVER + _SERVER[ "SERVER_NAME" ] = split( ":", _HTTP_REQUEST[ "HOST" ], 1 )[ 1 ] + _SERVER[ "SERVER_SOFTWARE" ] = APP_NAME + " " + APP_VERSION + " (" + OS() + ")" + _SERVER[ "SERVER_SIGNATURE" ] = "
" + _SERVER[ "SERVER_SOFTWARE" ] + " Server at " + _SERVER[ "SERVER_NAME" ] + " Port " + LTrim( Str( _SERVER[ "SERVER_PORT" ] ) ) + "
" + _SERVER[ "DOCUMENT_ROOT" ] = s_cDocumentRoot + _SERVER[ "SERVER_ADMIN" ] = "root" + _SERVER[ "SCRIPT_FILENAME" ] = STRTRAN( STRTRAN( _SERVER[ "DOCUMENT_ROOT" ] + _SERVER[ "SCRIPT_NAME" ], "//", "/" ), "\", "/" ) + _SERVER[ "GATEWAY_INTERFACE" ] = "CGI/1.1" + _SERVER[ "SCRIPT_URL" ] := _SERVER["SCRIPT_NAME"] + + //hb_ToOutDebug( "_SERVER = %s\n\r", hb_ValToExp( _SERVER ) ) + //hb_ToOutDebug( "_GET = %s\n\r", hb_ValToExp( _GET ) ) + //hb_ToOutDebug( "_POST = %s\n\r", hb_ValToExp( _POST ) ) + //hb_ToOutDebug( "_HTTP_REQUEST = %s\n\r", hb_ValToExp( _HTTP_REQUEST ) ) + +RETURN .T. + + +STATIC FUNCTION MakeResponse() +LOCAL cRet, cReturnCode + + uAddHeader("Connection", "close") + + IF uGetHeader("Location") != NIL + s_nStatusCode := 301 + ENDIF + IF uGetHeader("Content-Type") == NIL + uAddHeader("Content-Type", "text/html") + ENDIF + + cRet := "HTTP/1.1 " + cReturnCode := DecodeStatusCode() + + SWITCH s_nStatusCode + CASE 200 + EXIT + + CASE 301 + CASE 400 + CASE 401 + CASE 402 + CASE 403 + CASE 404 + CASE 503 + s_cResult := "

" + cReturnCode + "

" + EXIT + + // extended error messages - from Microsoft IIS Server + CASE 500013 // error: 500-13 Server too busy + uAddHeader( "Retry-After", "60" ) // retry after 60 seconds + s_cResult := "

500 Server Too Busy

" + EXIT + + CASE 500100 // error: 500-100 Undeclared Variable + + OTHERWISE + cReturnCode := "403 Forbidden" + s_cResult := "

" + cReturnCode + "

" + ENDSWITCH + + WriteToConsole( cReturnCode ) + cRet += cReturnCode + CR_LF + AEVAL( s_aHeader, {|x| cRet += x[1] + ": " + x[2] + CR_LF } ) + cRet += CR_LF + cRet += s_cResult +RETURN cRet + +STATIC FUNCTION DecodeStatusCode() +LOCAL cReturnCode + + SWITCH s_nStatusCode + CASE 200 + cReturnCode := "200 OK" + EXIT + CASE 301 + cReturnCode := "301 Moved Permanently" + EXIT + CASE 400 + cReturnCode := "400 Bad Request" + EXIT + CASE 401 + cReturnCode := "401 Unauthorized" + EXIT + CASE 402 + cReturnCode := "402 Payment Required" + EXIT + CASE 403 + cReturnCode := "403 Forbidden" + EXIT + CASE 404 + cReturnCode := "404 Not Found" + EXIT + CASE 503 + cReturnCode := "503 Service Unavailable" + EXIT + + // extended error messages - from Microsoft IIS Server + CASE 500013 // error: 500-13 Server too busy + cReturnCode := "500-13 Server Too Busy" + EXIT + + CASE 500100 // error: 500-100 Undeclared Variable + + OTHERWISE + cReturnCode := "403 Forbidden" + ENDSWITCH + +RETURN cReturnCode + +STATIC PROCEDURE WriteToLog( cRequest ) + LOCAL cTime, cDate + LOCAL aDays := { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" } + LOCAL aMonths := {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} + LOCAL cAccess, cError, nDoW, dDate, nDay, nMonth, nYear, nSize, cBias + LOCAL cErrorMsg + LOCAL cReferer + + IF hb_mutexLock( hmtxLog ) + + //hb_ToOutDebug( "TIP_TimeStamp() = %s \n\r", TIP_TIMESTAMP() ) + + cTime := TIME() + dDate := Date() + cDate := DTOS( dDate ) + nSize := LEN( s_cResult ) + cReferer := _SERVER["HTTP_REFERER"] + cBias := WIN_TIMEZONEBIAS() + + cAccess := _SERVER["REMOTE_ADDR"] + " - - [" + RIGHT( cDate, 2 ) + "/" + ; + aMonths[ VAL( SUBSTR( cDate, 5, 2 ) ) ] + ; + "/" + LEFT( cDate, 4 ) + ":" + cTime + ' ' + cBias + '] "' + ; + LEFT( cRequest, AT( CR_LF, cRequest ) - 1 ) + '" ' + ; + LTRIM( STR( s_nStatusCode ) ) + " " + IIF( nSize == 0, "-", LTRIM( STR( nSize ) ) ) + ; + ' "' + IIF( Empty( cReferer ), "-", cReferer ) + '" "' + _SERVER["HTTP_USER_AGENT"] + ; + '"' + HB_OSNewLine() + + //hb_ToOutDebug( "AccessLog = %s \n\r", cAccess ) + + FWRITE( s_hfileLogAccess, cAccess ) + + IF !( s_nStatusCode == 200 ) // ok + + nDoW := Dow( dDate ) + nDay := Day( dDate ) + nMonth := Month( dDate ) + nYear := Year( dDate ) + cErrorMsg := s_cErrorMsg + + cError := "[" + Left( aDays[ nDoW ], 3 ) + " " + aMonths[ nMonth ] + " " + StrZero( nDay, 2 ) + " " + ; + PadL( LTrim( cTime ), 8, "0" ) + " " + StrZero( nYear, 4 ) + "] [error] [client " + _SERVER["REMOTE_ADDR"] + "] " + ; + cErrorMsg + HB_OSNewLine() + + //hb_ToOutDebug( "ErrorLog = %s \n\r", cError ) + + FWRITE( s_hfileLogError, cError ) + ENDIF + + hb_mutexUnlock( hmtxLog ) + ENDIF + +RETURN + +INIT PROCEDURE SocketInit() + IF socket_init() != 0 + ? "socket_init() error" + ENDIF +RETURN + + +EXIT PROCEDURE Socketxit() + socket_exit() +RETURN + + +/******************************************************************** + Public helper functions +********************************************************************/ +STATIC FUNCTION split( cSeparator, cString, nMax ) + LOCAL aRet := {}, nI + LOCAL nIter := 0 + + DEFAULT nMax TO 0 + + DO WHILE ( nI := AT( cSeparator, cString ) ) > 0 + AADD( aRet, LEFT( cString, nI - 1 ) ) + cString := SUBSTR( cString, nI + LEN( cSeparator ) ) + IF nMax > 0 .AND. ++nIter >= nMax + EXIT + ENDIF + ENDDO + AADD( aRet, cString ) +RETURN aRet + +STATIC FUNCTION join( cSeparator, aData ) +LOCAL cRet := "", nI + + FOR nI := 1 TO LEN( aData ) + IF nI > 1; cRet += cSeparator + ENDIF + IF VALTYPE(aData[nI]) $ "CM"; cRet += aData[nI] + ELSEIF VALTYPE(aData[nI]) == "N"; cRet += LTRIM(STR(aData[nI])) + ELSEIF VALTYPE(aData[nI]) == "D"; cRet += IF(!EMPTY(aData[nI]), DTOC(aData[nI]), "") + ELSE + ENDIF + NEXT +RETURN cRet + + +FUNCTION uOSFileName( cFileName ) + IF HB_OSPathSeparator() != "/" + RETURN STRTRAN( cFileName, "/", HB_OSPathSeparator() ) + ENDIF +RETURN cFileName + +PROCEDURE uSetStatusCode(nStatusCode) + s_nStatusCode := nStatusCode +RETURN + + +PROCEDURE 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 + + +FUNCTION uGetHeader( cType ) +LOCAL nI + + IF ( nI := ASCAN( s_aHeader, {|x| UPPER( x[ 1 ] ) == UPPER( cType ) } ) ) > 0 + RETURN s_aHeader[ nI, 2 ] + ENDIF +RETURN NIL + + +PROCEDURE uWrite( cString ) + s_cResult += cString +RETURN + +#define XP_SUCCESS 0 + +STATIC PROCEDURE uproc_default( cRoot, lIndex ) +LOCAL cFileName, nI, cI +LOCAL cExt, xResult, pHRB, oError + + //cFileName := STRTRAN(cRoot + _SERVER["SCRIPT_NAME"], "//", "/") + cFileName := _SERVER[ "SCRIPT_FILENAME" ] + + //hb_ToOutDebug( "cFileName = %s, uOSFileName( cFileName ) = %s,\n\r _SERVER = %s\n\r", cFileName, uOSFileName( cFileName ), hb_ValToExp( _SERVER ) ) + + // Security + IF ".." $ cFileName + uSetStatusCode( 403 ) + s_cErrorMsg := "Characters not allowed" + RETURN + ENDIF + + IF HB_HHasKey( s_hFileAliases, _SERVER[ "SCRIPT_NAME" ] ) + cFileName := _SERVER[ "DOCUMENT_ROOT" ] + hb_hGet( s_hFileAliases, _SERVER[ "SCRIPT_NAME" ] ) + ENDIF + + IF Upper( _SERVER[ "SCRIPT_NAME" ] ) == "/SERVERSTATUS" + ShowServerStatus() + ELSEIF HB_FileExists( uOSFileName( cFileName ) ) + IF ( nI := RAT( ".", cFileName ) ) > 0 + SWITCH ( cExt := LOWER( SUBSTR( cFileName, nI + 1 ) ) ) + CASE "hrb" ; cI := "text/html"; EXIT + 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 "xsl" ; cI := "text/xsl"; 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 + + IF cExt == "hrb" + + // Starting HRB module + + TRY + IF hb_mutexLock( hmtxHRB ) + IF !EMPTY( pHRB := __HRBLOAD( uOSFileName(cFileName) ) ) + + xResult := HRBMAIN() + + __HRBUNLOAD( pHRB ) + + ENDIF + hb_mutexUnlock( hmtxHRB ) + ENDIF + + IF HB_ISSTRING( xResult ) + uAddHeader( "Content-Type", cI ) + uWrite( xResult ) + ELSE + // Application in HRB module is responsible to send HTML content + ENDIF + + CATCH oError + + WriteToConsole( "Error!" ) + + uAddHeader( "Content-Type", "text/html" ) + uWrite( "Error" ) + uWrite( "
Description: " + hb_cStr( oError:Description ) ) + uWrite( "
Filename: " + hb_cStr( oError:filename ) ) + uWrite( "
Operation: " + hb_cStr( oError:operation ) ) + uWrite( "
OsCode: " + hb_cStr( oError:osCode ) ) + uWrite( "
GenCode: " + hb_cStr( oError:genCode ) ) + uWrite( "
SubCode: " + hb_cStr( oError:subCode ) ) + uWrite( "
SubSystem: " + hb_cStr( oError:subSystem ) ) + uWrite( "
Args: " + hb_cStr( hb_ValToExp( oError:args ) ) ) + uWrite( "
ProcName: " + hb_cStr( procname( 0 ) ) ) + uWrite( "
ProcLine: " + hb_cStr( procline( 0 ) ) ) + END + + + ELSE + uAddHeader( "Content-Type", cI ) + uWrite( HB_MEMOREAD( uOSFileName( cFileName ) ) ) + ENDIF + + ELSE + cI := "application/octet-stream" + uAddHeader( "Content-Type", cI ) + uWrite( HB_MEMOREAD( uOSFileName( cFileName ) ) ) + ENDIF + + ELSEIF HB_DirExists( uOSFileName( cFileName ) ) + IF RIGHT( cFileName, 1 ) != "/" + uAddHeader( "Location", "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 I'm here it's means that I have no page, so, if it is defined, I will display content folder + IF !s_lIndexes + uSetStatusCode( 403 ) + s_cErrorMsg := "Display file list not allowed" + RETURN + ENDIF + + // ----------------------- display folder content ------------------------------------- + ShowFolder( cFileName ) + + ELSE + uSetStatusCode( 404 ) + s_cErrorMsg := "File does not exist: " + cFileName + ENDIF +RETURN + +// Define environment SET variables - TODO: Actually only for windows, make multiplatform +STATIC PROCEDURE Define_Env( hmServer ) + LOCAL v + + FOR EACH v IN hmServer + WIN_SETENV( v:__enumKey(), v:__enumValue() ) + NEXT + +RETURN + +// ------------------------------- DEFAULT PAGES ----------------------------------- + +STATIC PROCEDURE ShowServerStatus() + + uAddHeader( "Content-Type", "text/html" ) + uWrite( '' ) + uWrite( '' ) + uWrite( 'Server Status

Server Status

')
+   //uWrite( '')
+
+   uWrite( 'SERVER: ' + _SERVER[ "SERVER_SOFTWARE" ] + " Server at " + _SERVER[ "SERVER_NAME" ] + " Port " + LTrim( Str( _SERVER[ "SERVER_PORT" ] ) ) )
+   uWrite( '
' ) + IF hb_mutexLock( hmtxBusy ) + uWrite( '
Thread: ' + Str( s_nThreads ) ) + uWrite( '
Connections: ' + Str( s_nConnections ) ) + uWrite( '
Max Connections: ' + Str( s_nMaxConnections ) ) + uWrite( '
Total Connections: ' + Str( s_nTotConnections ) ) + uWrite( '
Running Thread: ' + hb_ValToExp( s_aRunningThreads ) ) + + uWrite( '
Service Thread: ' + Str( s_nServiceThreads ) ) + uWrite( '
Service Connections: ' + Str( s_nServiceConnections ) ) + uWrite( '
Max Service Connections: ' + Str( s_nMaxServiceConnections ) ) + uWrite( '
Total Service Connections: ' + Str( s_nTotServiceConnections ) ) + uWrite( '
Service Thread: ' + hb_ValToExp( s_aServiceThreads ) ) + hb_mutexUnlock( hmtxBusy ) + ENDIF + uWrite( '
Time: ' + Time() ) + + //uWrite( '
') + uWrite( "
" ) + +RETURN + +STATIC PROCEDURE ShowFolder( cDir ) + LOCAL aDir, aF + LOCAL cParentDir, nPos + + uAddHeader( "Content-Type", "text/html" ) + + aDir := DIRECTORY( uOSFileName( cDir ), "D" ) + IF HB_HHasKey( _GET, "s" ) + IF _GET[ "s" ] == "s" + ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T. ), ; + IIF( Y[ 5 ] == "D", .F., X[ 2 ] < Y[ 2 ] ) ) } ) + ELSEIF _GET[ "s" ] == "m" + ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T.), ; + IIF( Y[ 5 ] == "D", .F., DTOS( X[ 3 ] ) + X[ 4 ] < DTOS( Y[ 3 ] ) + Y[ 4 ] ) ) } ) + ELSE + ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T. ), ; + IIF( Y[ 5 ] == "D", .F., X[ 1 ] < Y[ 1 ] ) ) } ) + ENDIF + ELSE + ASORT( aDir,,, {|X,Y| IIF( X[ 5 ] == "D", IIF( Y[ 5 ] == "D", X[ 1 ] < Y[ 1 ], .T. ), ; + IIF( Y[ 5 ] == "D", .F., X[ 1 ] < Y[ 1 ] ) ) } ) + ENDIF + + uWrite( '

Index of ' + _SERVER[ "SCRIPT_NAME" ] + '

      ')
+   uWrite( 'Name                                                  ')
+   uWrite( 'Modified             ' )
+   uWrite( 'Size' + CR_LF + '
' ) + + // Adding Upper Directory + nPos := RAT( "/", SUBSTR( cDir, 1, Len( cDir ) - 1 ) ) + cParentDir := SUBSTR( cDir, 1, nPos ) + cParentDir := SUBSTR( cParentDir, Len( _SERVER[ "DOCUMENT_ROOT" ] ) + 1 ) + + //hb_ToOutDebug( "cDir = %s, nPos = %i, cParentDir = %s\n\r", cDir, nPos, cParentDir ) + + IF !Empty( cParentDir ) + // Add parent directory + hb_aIns( aDir, 1, { "", 0, "", "", "D" }, .T. ) + ENDIF + + FOR EACH aF IN aDir + IF aF[ 1 ] == "" + uWrite( '[DIR] ..' + ; + CR_LF ) + ELSEIF LEFT( aF[ 1 ], 1 ) == "." + ELSEIF "D" $ aF[ 5 ] + uWrite( '[DIR] '+ aF[ 1 ] + '' + SPACE( 50 - LEN( aF[ 1 ] ) ) + ; + DTOC( aF[ 3 ] ) + ' ' + aF[ 4 ] + CR_LF ) + ELSE + uWrite( ' '+ aF[ 1 ] + '' + SPACE( 50 - LEN( aF[ 1 ] ) ) + ; + DTOC( aF[ 3 ]) + ' ' + aF[ 4 ] + STR( aF[ 2 ], 12 ) + CR_LF ) + ENDIF + NEXT + uWrite( "
" ) + +RETURN + +// ------------------------------- Utility functions -------------------------------- + +STATIC PROCEDURE Help() + LOCAL cPrg := hb_argv( 0 ) + LOCAL nPos := RAt( "\", cPrg ) + //__OutDebug( hb_argv(0) ) + IF nPos > 0 + cPrg := SubStr( cPrg, nPos + 1 ) + ENDIF + ? + ? "(C) 2009 Francesco Saverio Giudice " + ? + ? APP_NAME + " - web server - v. " + APP_VERSION + ? "Based on original work of Mindaugas Kavaliauskas " + ? + ? "Parameters: (all optionals)" + ? + ? "-p | --port webserver tcp port (default: " + LTrim( Str( LISTEN_PORT ) ) + ")" + ? "-c | --config Configuration file (default: " + APP_NAME + ".ini)" + ? " It is possibile to define file path" + ? "-d | --docroot Document root directory (default: \home)" + ? "-i | --indexes Allow directory view (default: no)" + ? "-s | --stop Stop webserver" + ? "-ts | --start-threads Define starting threads (default: " + LTrim( Str( START_RUNNING_THREADS ) ) + ")" + ? "-tm | --max-threads Define max threads (default: " + LTrim( Str( MAX_RUNNING_THREADS ) ) + ")" + ? "-h | -? | --help This help message" + ? + WAIT +RETURN + +STATIC PROCEDURE SysSettings() + SET SCOREBOARD OFF + SET CENTURY ON + SET DATE ITALIAN + SET BELL OFF + SET DELETED ON + SET EXACT OFF + SET CONFIRM ON + SET ESCAPE ON + SET WRAP ON + SET EPOCH TO 2000 + //RDDSetDefault( "DBFCDX" ) +RETURN + +STATIC FUNCTION Exe_Path() + LOCAL cPath := Exe_FullPath() + LOCAL nPos := RAt( "\", cPath ) + IF nPos == 0 + cPath := "" + ELSE + cPath := SubStr( cPath, 1, nPos-1 ) + ENDIF +RETURN cPath + +STATIC FUNCTION Exe_Name() + LOCAL cPrg := Exe_FullPath() + LOCAL nPos := RAt( "\", cPrg ) + IF nPos > 0 + cPrg := SubStr( cPrg, nPos+1 ) + ENDIF +RETURN cPrg + +STATIC PROCEDURE Progress( nProgress ) + LOCAL cString := "[" + + DO CASE + CASE nProgress == 0 + cString += "-" + CASE nProgress == 1 + cString += "\" + CASE nProgress == 2 + cString += "|" + CASE nProgress == 3 + cString += "/" + ENDCASE + + cString += "]" + + nProgress++ + + IF nProgress == 4 + nProgress := 0 + ENDIF + + hb_dispOutAt( 10, 5, cString ) + hb_dispOutAt( 0, 60, "Time: " + Time() ) + +RETURN + +// Show messages in console +#define CONSOLE_FIRSTROW 12 +#define CONSOLE_LASTROW MaxRow() +STATIC PROCEDURE WriteToConsole( ... ) + LOCAL cMsg + + IF hb_mutexLock( hmtxConsole ) + IF s_lConsole + + FOR EACH cMsg IN hb_aParams() + + Scroll( CONSOLE_FIRSTROW, 0, CONSOLE_LASTROW, MaxCol(), -1 ) + DispOutAt( CONSOLE_FIRSTROW, 0, PadR( "> " + hb_cStr( cMsg ), MaxCol() ) ) + + hb_ToOutDebug( ">>> %s\n\r", cMsg ) + + NEXT + + ENDIF + hb_mutexUnlock( hmtxConsole ) + ENDIF + +RETURN + + + +//------------------------------------------------------------------------------ +// FUNZIONI C +//------------------------------------------------------------------------------ +#PRAGMA BEGINDUMP + +#ifdef __WIN32__ + +#include +#include "hbapi.h" +#include "hbvm.h" + +HB_FUNC_STATIC( EXE_FULLPATH ) +{ + char szPath[512]; + + if ( !(GetModuleFileName( NULL, szPath, 512) == 0) ) + hb_retc( szPath ); + +} + +BOOL win_SysRefresh( int iMsec ) +{ + int iQuit = (int) FALSE; + + HANDLE hDummyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + // Begin the operation and continue until it is complete + // or until the user clicks the mouse or presses a key. + + while (MsgWaitForMultipleObjects(1, &hDummyEvent, FALSE, ( iMsec == 0, INFINITE, iMsec ), QS_ALLINPUT | QS_ALLPOSTMESSAGE) == WAIT_OBJECT_0 + 1) + { + MSG msg; + + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + + switch(msg.message) + { + case WM_QUIT: + { + iQuit = (int) msg.wParam; + goto stopLoop; + } + //case WM_LBUTTONDOWN: + //case WM_RBUTTONDOWN: + //case WM_KEYDOWN: + //case WM_LBUTTONUP: + //case WM_RBUTTONUP: + //case WM_KEYUP: + // // + // // Perform any required cleanup. + // // + // break; + // //exit; + // + default: + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + } + if (!iQuit) + { + goto stopLoop; + } + } + +stopLoop: + + CloseHandle( hDummyEvent ); + + return iQuit; + +} + +HB_FUNC_STATIC( WIN_SYSREFRESH ) +{ + hb_retl( win_SysRefresh( ( ISNIL( 1 ) ? 0 : hb_parni( 1 ) ) ) ); +} + +HB_FUNC_STATIC( WIN_SETENV ) +{ + hb_retl( SetEnvironmentVariable( hb_parc( 1 ), hb_parc( 2 ) ) ); +} + +HB_FUNC_STATIC( WIN_TIMEZONEBIAS ) +{ + TIME_ZONE_INFORMATION tzInfo; + //LONG lBias; + int nLen; + char *szRet = (char *) hb_xgrab( 6 ); + + if ( GetTimeZoneInformation( &tzInfo ) == TIME_ZONE_ID_INVALID ) + { + tzInfo.Bias = 0; + } + else + { + tzInfo.Bias = -tzInfo.Bias; + } + + hb_snprintf( szRet, 6, "%+03d%02d", + (int)( tzInfo.Bias / 60 ), + (int)( tzInfo.Bias % 60 > 0 ? - tzInfo.Bias % 60 : tzInfo.Bias % 60 ) ); + + nLen = strlen( szRet ); + + if ( nLen < 6 ) + { + szRet = (char *) hb_xrealloc( szRet, nLen + 1 ); + } + hb_retclen_buffer( szRet, nLen ); + +} + +#endif +#PRAGMA ENDDUMP