Przeglądaj źródła

More work on the application server for OurBiz

mingodad 13 lat temu
rodzic
commit
a3949cd386

+ 664 - 663
ourbiz/happyhttp.nut

@@ -1,663 +1,664 @@
-/*
- * HappyHTTP - a simple HTTP library
- * Version 0.1
- *
- * Copyright (c) 2006 Ben Campbell
- *
- * This software is provided 'as-is', without any express or implied
- * warranty. In no event will the authors be held liable for any damages
- * arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose,
- * including commercial applications, and to alter it and redistribute it
- * freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not
- * claim that you wrote the original software. If you use this software in a
- * product, an acknowledgment in the product documentation would be
- * appreciated but is not required.
- *
- * 2. Altered source versions must be plainly marked as such, and must not
- * be misrepresented as being the original software.
- *
- * 3. This notice may not be removed or altered from any source distribution.
- *
- *
- *Ported to SquiLu http://code.google.com/p/squilu/ by Domingo Alvarez Duarte
- */
- 
-// HTTP status codes
-enum HTTP_status_code {
-	// 1xx informational
-	CONTINUE = 100,
-	SWITCHING_PROTOCOLS = 101,
-	PROCESSING = 102,
-
-	// 2xx successful
-	OK = 200,
-	CREATED = 201,
-	ACCEPTED = 202,
-	NON_AUTHORITATIVE_INFORMATION = 203,
-	NO_CONTENT = 204,
-	RESET_CONTENT = 205,
-	PARTIAL_CONTENT = 206,
-	MULTI_STATUS = 207,
-	IM_USED = 226,
-
-	// 3xx redirection
-	MULTIPLE_CHOICES = 300,
-	MOVED_PERMANENTLY = 301,
-	FOUND = 302,
-	SEE_OTHER = 303,
-	NOT_MODIFIED = 304,
-	USE_PROXY = 305,
-	TEMPORARY_REDIRECT = 307,
-
-	// 4xx client error
-	BAD_REQUEST = 400,
-	UNAUTHORIZED = 401,
-	PAYMENT_REQUIRED = 402,
-	FORBIDDEN = 403,
-	NOT_FOUND = 404,
-	METHOD_NOT_ALLOWED = 405,
-	NOT_ACCEPTABLE = 406,
-	PROXY_AUTHENTICATION_REQUIRED = 407,
-	REQUEST_TIMEOUT = 408,
-	CONFLICT = 409,
-	GONE = 410,
-	LENGTH_REQUIRED = 411,
-	PRECONDITION_FAILED = 412,
-	REQUEST_ENTITY_TOO_LARGE = 413,
-	REQUEST_URI_TOO_LONG = 414,
-	UNSUPPORTED_MEDIA_TYPE = 415,
-	REQUESTED_RANGE_NOT_SATISFIABLE = 416,
-	EXPECTATION_FAILED = 417,
-	UNPROCESSABLE_ENTITY = 422,
-	LOCKED = 423,
-	FAILED_DEPENDENCY = 424,
-	UPGRADE_REQUIRED = 426,
-
-	// 5xx server error
-	INTERNAL_SERVER_ERROR = 500,
-	NOT_IMPLEMENTED = 501,
-	BAD_GATEWAY = 502,
-	SERVICE_UNAVAILABLE = 503,
-	GATEWAY_TIMEOUT = 504,
-	HTTP_VERSION_NOT_SUPPORTED = 505,
-	INSUFFICIENT_STORAGE = 507,
-	NOT_EXTENDED = 510,
-};
-
-//-------------------------------------------------
-// Connection
-//
-// Handles the socket connection, issuing of requests and managing
-// responses.
-// ------------------------------------------------
-
-enum Connection_State { IDLE, REQ_STARTED, REQ_SENT };
-class HappyHttpConnection {
-	m_Port = null;
-	m_Host = null;
-	m_State = null;
-	m_Sock = null;
-	m_Buffer = null; // lines of request
-	m_Outstanding = null; // responses for outstanding requests
-	
-	
-	// doesn't connect immediately
-	constructor(host, port){
-		m_Host = host;
-		m_Port = port;
-		m_Buffer = [];
-		m_Outstanding = [];
-		m_State =Connection_State.IDLE;
-	}
-
-	// Don't need to call connect() explicitly as issuing a request will
-	// call it automatically if needed.
-	// But it could block (for name lookup etc), so you might prefer to
-	// call it in advance.
-	function connect(){
-		m_Sock = socket.tcp();
-		m_Sock.connect(m_Host, m_Port);
-		m_Sock.settimeout(0.01);
-	}
-
-	// close connection, discarding any pending requests.
-	function close(){
-		m_Sock.close();
-		m_Sock = null;
-	}
-
-	// Update the connection (non-blocking)
-	// Just keep calling this regularly to service outstanding requests.
-	function pump(milisec=10) { //10 miliseconds to prevent high cpu load
-		if( m_Outstanding.empty() ) return;		// no requests outstanding
-		assert( m_Sock != null );	// outstanding requests but no connection!
-
-		if( !datawaiting( m_Sock, milisec) ) return;	// recv will block
-
-		local rc = m_Sock.receive(2048);
-		switch(rc[1]){
-			case socket.IO_DONE:
-			case socket.IO_TIMEOUT:
-				break;
-			default:
-				throw(format("socket io error %d", rc[1]));
-		}
-		local buf = rc[0];
-		local a = buf.len();
-		if( a== 0 )
-		{
-			// connection has closed
-			local r = m_Outstanding[0];
-			r->notifyconnectionclosed();
-			assert( r->completed() );
-			m_Outstanding.remove(0);
-
-			// any outstanding requests will be discarded
-			close();
-		}
-		else
-		{
-			local used = 0;
-			while( used < a && !m_Outstanding.empty() )
-			{
-				local r = m_Outstanding[0];
-				local u = r->pump( buf, used, a-used );
-
-				// delete response once completed
-				if( r->completed() )	m_Outstanding.remove(0);
-				used += u;
-			}
-
-			// NOTE: will lose bytes if response queue goes empty
-			// (but server shouldn't be sending anything if we don't have
-			// anything outstanding anyway)
-			assert( used == a );	// all bytes should be used up by here.
-		}
-	}
-
-	// any requests still outstanding?
-	function outstanding() { return m_Outstanding && !m_Outstanding.empty(); }
-
-	// ---------------------------
-	// high-level request interface
-	// ---------------------------
-
-	// method is "GET", "POST" etc...
-	// url is only path part: eg  "/index.html"
-	// headers is array of name/value pairs, terminated by a null-ptr
-	// body & bodysize specify body data of request (eg values for a form)
-	function request( method, url, headers=null, body=null, bodysize=0){
-		local gotcontentlength = false;	// already in headers?
-
-		// check headers for content-length
-		// TODO: check for "Host" and "Accept-Encoding" too
-		// and avoid adding them ourselves in putrequest()
-		if( headers ) gotcontentlength = headers.get("content-length", false);
-
-		putrequest( method, url );
-
-		if( body && !gotcontentlength ) putheader( "Content-Length", bodysize );
-
-		if( headers ) foreach(name, value in headers) putheader( name, value );
-
-		endheaders();
-
-		if( body ) send( body, bodysize );
-	}
-
-	// ---------------------------
-	// low-level request interface
-	// ---------------------------
-
-	// begin request
-	// method is "GET", "POST" etc...
-	// url is only path part: eg  "/index.html"
-	function putrequest( method, url ){
-		if( m_State != Connection_State.IDLE ) throw ("Request already issued" );
-		if( !method || !url ) throw ( "Method and url can't be NULL" );
-
-		m_State = Connection_State.REQ_STARTED;
-
-		local req = format("%s %s HTTP/1.1", method, url);
-		m_Buffer.push( req );
-
-		putheader( "Host", m_Host );	// required for HTTP1.1
-
-		// don't want any fancy encodings please
-		putheader("Accept-Encoding", "identity");
-
-		// Push a new response onto the queue
-		local r = new HappyHttpResponse( method, url, this );
-		m_Outstanding.push( r );
-	}
-
-	// Add a header to the request (call after putrequest() )
-	function putheader( header, value ){
-		if( m_State != Connection_State.REQ_STARTED ) throw ( "putheader() failed" );
-		m_Buffer.push( format("%s: %s", header, value.tostring()) );	
-	}
-
-	// Finished adding headers, issue the request.
-	function endheaders(){
-		if( m_State != Connection_State.REQ_STARTED ) throw "Cannot send header";
-		m_State = Connection_State.IDLE;
-		m_Buffer.push( "" );
-		m_Buffer.push( "" );
-		local msg = m_Buffer.concat("\r\n");
-		m_Buffer.clear();
-		send( msg , msg.len() );
-	}
-
-	// send body data if any.
-	// To be called after endheaders()
-	function send( buf, numbytes ){
-		if( !m_Sock ) connect();
-		local n = m_Sock.send( buf , 0, numbytes );
-		if(n != numbytes) throw("Could not send ");
-	}
-
-	// return true if socket has data waiting to be read
-	static function datawaiting( sock , milisec=0)
-	{
-		try {
-			local rc = socket.select( [sock], [], milisec ? milisec / 1000.0 : 0);
-			local rar = rc[0];
-			if(rar.len() == 1 && rar[0] == sock) return true;
-			return false;
-		}
-		catch(e){
-			if(e == "timeout") return false;
-			else throw(e);
-		}
-	}
-
-    function response_begin( r ) {}
-    function response_data( r, data, numbytes ) {}
-    function response_complete( r ) {}
-};
-
-enum Response_state {
-		STATUSLINE,		// start here. status line is first line of response.
-		HEADERS,		// reading in header lines
-		BODY,			// waiting for some body data (all or a chunk)
-		CHUNKLEN,		// expecting a chunk length indicator (in hex)
-		CHUNKEND,		// got the chunk, now expecting a trailing blank line
-		TRAILERS,		// reading trailers after body.
-		COMPLETE,		// response is complete!
-	};
-	
-class HappyHttpResponse {
-	m_Connection = null; 	// to access callback ptrs
-	m_Method = null; 		// req method: "GET", "POST" etc...
-	m_Url = null; 			// req url: /image.php?d=2...
-	m_VersionString = null;	// HTTP-Version
-	m_Version = null;		// 10: HTTP/1.0    11: HTTP/1.x (where x>=1)
-	m_Status = null;		// Status-Code
-	m_Reason = null;		// Reason-Phrase
-	m_Headers = null;		// header/value pairs
-	m_BytesRead = null;		// body bytes read so far
-	m_Chunked = null;		// response is chunked?
-	m_ChunkLeft = null;		// bytes left in current chunk
-	m_Length = null;		// -1 if unknown
-	m_WillClose = null;		// connection will close at response end?
-	m_LineBuf = null;		// line accumulation for states that want it
-	m_HeaderAccum = null;	// accumulation buffer for headers
-	m_State = null;
-	
-	// only Connection creates Responses.
-	constructor(method, url, conn){
-		m_Method = method;
-		m_Url = url;
-		m_Connection = conn;
-		m_Version = 0;
-		m_Status = 0;
-		m_BytesRead = 0;
-		m_Chunked = false;
-		m_ChunkLeft = 0;
-		m_Length = -1;
-		m_WillClose = false;
-		m_Headers = {};
-		m_HeaderAccum = "";
-		m_LineBuf = "";
-		m_State = Response_state.STATUSLINE;
-	}
-	
-	// retrieve a header (returns null if not present)
-	function getheader( name ){
-		local lname = name.tolower();
-		return m_Headers.get(lname, null);
-	}
-	
-	function completed() { return m_State == Response_state.COMPLETE; }
-	
-	// get the HTTP status code
-	function getstatus(){
-		// only valid once we've got the statusline
-		assert( m_State != Response_state.STATUSLINE );
-		return m_Status;
-	}
-	
-	// get the HTTP response reason string
-	function getreason(){
-		// only valid once we've got the statusline
-		assert( m_State != Response_state.STATUSLINE );
-		return m_Reason;
-	}
-	
-	// true if connection is expected to close after this response.
-	function willclose() { return m_WillClose; }
-	
-	function getconnection(){return m_Connection;}
-	function geturl() { return m_Url; }
-	function getLength() { return m_Length;}
-	
-	// pump some data in for processing.
-	// Returns the number of bytes used.
-	// Will always return 0 when response is complete.
-	function pump( data, start, datasize ){
-		assert( datasize != 0 );
-		local count = datasize;
-		local data_idx = 0;
-
-		while( count > 0 && m_State != Response_state.COMPLETE )
-		{
-			if( m_State == Response_state.STATUSLINE ||
-				m_State == Response_state.HEADERS ||
-				m_State == Response_state.TRAILERS ||
-				m_State == Response_state.CHUNKLEN ||
-				m_State == Response_state.CHUNKEND )
-			{
-				// we want to accumulate a line
-				local pos = data.find("\n", data_idx);
-				if( pos >= 0 )
-				{
-					count -= pos-data_idx+1;
-					local new_pos = pos;
-					if(pos > 0 && data[pos-1] == '\r') --new_pos;
-					m_LineBuf = data.slice(data_idx, new_pos);
-					data_idx = pos+1;
-					// now got a whole line!
-					switch( m_State )
-					{
-						case Response_state.STATUSLINE:
-							ProcessStatusLine( m_LineBuf );
-							break;
-						case Response_state.HEADERS:
-							ProcessHeaderLine( m_LineBuf );
-							break;
-						case Response_state.TRAILERS:
-							ProcessTrailerLine( m_LineBuf );
-							break;
-						case Response_state.CHUNKLEN:
-							ProcessChunkLenLine( m_LineBuf );
-							break;
-						case Response_state.CHUNKEND:
-							// just soak up the crlf after body and go to next state
-							assert( m_Chunked == true );
-							m_State = Response_state.CHUNKLEN;
-							break;
-						default:
-							break;
-					}
-					m_LineBuf = "";
-				}
-			}
-			else if( m_State == Response_state.BODY )
-			{
-				local bytesused = 0;
-				if( m_Chunked )
-					bytesused = ProcessDataChunked( data, count );
-				else
-					bytesused = ProcessDataNonChunked( data, count );
-				data += bytesused;
-				count -= bytesused;
-			}
-		}
-		// return number of bytes used
-		return datasize - count;
-	}
-	
-	// tell response that connection has closed
-	function notifyconnectionclosed(){
-		if( m_State == Response_state.COMPLETE ) return;
-
-		// eof can be valid...
-		if( m_State == Response_state.BODY && 
-			!m_Chunked && m_Length <= 0 ) Finish();	// we're all done!
-		else throw ( "Connection closed unexpectedly" );
-	}
-	
-	function FlushHeader(){
-		if( m_HeaderAccum.empty() ) return;	// no flushing required
-		local rc = m_HeaderAccum.match("([^:]+):%s*(.+)");
-		if(!rc) throw(format("Invalid header (%s)", m_HeaderAccum));
-		m_Headers[ rc[0].tolower() ] <- rc[1];
-		m_HeaderAccum = "";
-	}
-
-	function ProcessStatusLine( line ){
-		//HTTP/1.1 200 OK
-		local rc = line.match("%s*(%S+)%s+(%d+)%s+(.+)");
-		if(!rc) throw(format("BadStatusLine (%s)", line));
-		m_VersionString = rc[0];
-		m_Status = rc[1].tointeger();
-		m_Reason = rc[2];
-
-		if( m_Status < 100 || m_Status > 999 ) throw ( format("BadStatusLine (%s)", line) );
-
-		if( m_VersionString == "HTTP:/1.0" ) m_Version = 10;
-		else if( m_VersionString.startswith( "HTTP/1." ) ) m_Version = 11;
-		else throw ( format("UnknownProtocol (%s)", m_VersionString) );
-		// TODO: support for HTTP/0.9
-
-		// OK, now we expect headers!
-		m_State = Response_state.HEADERS;
-		m_HeaderAccum = "";		
-	}
-	
-	function ProcessHeaderLine( line ){
-		if( line.empty() )
-		{
-			FlushHeader();
-			// end of headers
-
-			// HTTP code 100 handling (we ignore 'em)
-			if( m_Status == HTTP_status_code.CONTINUE )
-				m_State = Response_state.STATUSLINE;	// reset parsing, expect new status line
-			else
-				BeginBody();			// start on body now!
-			return;
-		}
-
-		if( line[0] == ' ' )
-		{
-			// it's a continuation line - just add it to previous data
-			m_HeaderAccum += line;
-		}
-		else
-		{
-			// begin a new header
-			FlushHeader();
-			m_HeaderAccum = line;
-		}
-	}
-	
-	function ProcessTrailerLine(line){
-		// TODO: handle trailers?
-		// (python httplib doesn't seem to!)
-		if( line.empty() ) Finish();
-		// just ignore all the trailers...
-	}
-	
-	function ProcessChunkLenLine( line ){
-		// chunklen in hex at beginning of line
-		m_ChunkLeft = line.tointeger(16);
-
-		if( m_ChunkLeft == 0 )
-		{
-			// got the whole body, now check for trailing headers
-			m_State = Response_state.TRAILERS;
-			m_HeaderAccum = "";
-		}
-		else
-		{
-			m_State = Response_state.BODY;
-		}
-	}
-	
-	function ProcessDataChunked( data, count ){
-		assert( m_Chunked );
-
-		local n = count;
-		if( n>m_ChunkLeft ) n = m_ChunkLeft;
-
-		// invoke callback to pass out the data
-		m_Connection.response_data( this, data, n );
-
-		m_BytesRead += n;
-
-		m_ChunkLeft -= n;
-		assert( m_ChunkLeft >= 0);
-		if( m_ChunkLeft == 0 )
-		{
-			// chunk completed! now soak up the trailing CRLF before next chunk
-			m_State = Response_state.CHUNKEND;
-		}
-		return n;
-	}
-	
-	function ProcessDataNonChunked( data, count ){
-		local n = count;
-		if( m_Length != -1 )
-		{
-			// we know how many bytes to expect
-			local remaining = m_Length - m_BytesRead;
-			if( n > remaining ) n = remaining;
-		}
-
-		// invoke callback to pass out the data
-		m_Connection.response_data( this, data, n );
-		m_BytesRead += n;
-
-		// Finish if we know we're done. Else we're waiting for connection close.
-		if( m_Length != -1 && m_BytesRead == m_Length ) Finish();
-		return n;
-	}
-	
-	// OK, we've now got all the headers read in, so we're ready to start
-	// on the body. But we need to see what info we can glean from the headers
-	// first...
-	function BeginBody(){
-		m_Chunked = false;
-		m_Length = -1;	// unknown
-		m_WillClose = false;
-
-		// using chunked encoding?
-		local trenc = getheader( "transfer-encoding" );
-		if( trenc && trenc == "chunked" )
-		{
-			m_Chunked = true;
-			m_ChunkLeft = -1;	// unknown
-		}
-
-		m_WillClose = CheckClose();
-
-		// length supplied?
-		local contentlen = getheader( "content-length" );
-		if( contentlen && !m_Chunked )
-		{
-			m_Length = contentlen.tointeger();
-		}
-
-		// check for various cases where we expect zero-length body
-		if( m_Status == HTTP_status_code.NO_CONTENT ||
-			m_Status == HTTP_status_code.NOT_MODIFIED ||
-			( m_Status >= 100 && m_Status < 200 ) ||		// 1xx codes have no body
-			m_Method == "HEAD" )
-		{
-			m_Length = 0;
-		}
-
-
-		// if we're not using chunked mode, and no length has been specified,
-		// assume connection will close at end.
-		if( !m_WillClose && !m_Chunked && m_Length == -1 ) m_WillClose = true;
-
-		// Invoke the user callback, if any
-		m_Connection.response_begin( this );
-
-	/*
-		printf("---------BeginBody()--------\n");
-		printf("Length: %d\n", m_Length );
-		printf("WillClose: %d\n", (int)m_WillClose );
-		printf("Chunked: %d\n", (int)m_Chunked );
-		printf("ChunkLeft: %d\n", (int)m_ChunkLeft );
-		printf("----------------------------\n");
-	*/
-		// now start reading body data!
-		if( m_Chunked ) m_State = Response_state.CHUNKLEN;
-		else m_State = Response_state.BODY;
-	}
-	
-	function CheckClose(){
-		if( m_Version == 11 )
-		{
-			// HTTP1.1
-			// the connection stays open unless "connection: close" is specified.
-			local conn = getheader( "connection" );
-			if( conn && conn == "close" ) return true;
-			else return false;
-		}
-
-		// Older HTTP
-		// keep-alive header indicates persistant connection
-		if( getheader( "keep-alive" ) ) return false;
-
-		// TODO: some special case handling for Akamai and netscape maybe?
-		// (see _check_close() in python httplib.py for details)
-		return true;
-	}
-	
-	function Finish(){
-		m_State = Response_state.COMPLETE;
-		
-		// invoke the callback
-		m_Connection.response_complete( this );
-	}
-}
-
-/*
-class MyHappyHttpConnection extends HappyHttpConnection {
-	count = null;
-	
-	constructor(host, port){
-		base.constructor(host, port);
-		count = 0;
-	}
-	function response_begin( r ) {
-		printf( "BEGIN (%d %s)\n", r->getstatus(), r->getreason() );
-		count = 0;
-	}
-	function response_data( r, data, numbytes ) {
-		print( data );
-		count += numbytes;
-	}
-	function response_complete( r ) {
-		printf( "COMPLETE (%d bytes)\n", count );
-	}
-}
-
-function test_happyhttp(){
-	local start = os.getmillicount();
-	local conn = MyHappyHttpConnection( "www.scumways.com", 80);
-	conn.request( "GET", "/happyhttp/test.php" );
-	while( conn.outstanding() ) conn.pump();
-	print(format("Took : %d ms", os.getmillispan(start)));
-}
-
-test_happyhttp();
-*/
+/*
+ * HappyHTTP - a simple HTTP library
+ * Version 0.1
+ *
+ * Copyright (c) 2006 Ben Campbell
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software in a
+ * product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not
+ * be misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ *
+ *Ported to SquiLu http://code.google.com/p/squilu/ by Domingo Alvarez Duarte
+ */
+ 
+// HTTP status codes
+enum HTTP_status_code {
+	// 1xx informational
+	CONTINUE = 100,
+	SWITCHING_PROTOCOLS = 101,
+	PROCESSING = 102,
+
+	// 2xx successful
+	OK = 200,
+	CREATED = 201,
+	ACCEPTED = 202,
+	NON_AUTHORITATIVE_INFORMATION = 203,
+	NO_CONTENT = 204,
+	RESET_CONTENT = 205,
+	PARTIAL_CONTENT = 206,
+	MULTI_STATUS = 207,
+	IM_USED = 226,
+
+	// 3xx redirection
+	MULTIPLE_CHOICES = 300,
+	MOVED_PERMANENTLY = 301,
+	FOUND = 302,
+	SEE_OTHER = 303,
+	NOT_MODIFIED = 304,
+	USE_PROXY = 305,
+	TEMPORARY_REDIRECT = 307,
+
+	// 4xx client error
+	BAD_REQUEST = 400,
+	UNAUTHORIZED = 401,
+	PAYMENT_REQUIRED = 402,
+	FORBIDDEN = 403,
+	NOT_FOUND = 404,
+	METHOD_NOT_ALLOWED = 405,
+	NOT_ACCEPTABLE = 406,
+	PROXY_AUTHENTICATION_REQUIRED = 407,
+	REQUEST_TIMEOUT = 408,
+	CONFLICT = 409,
+	GONE = 410,
+	LENGTH_REQUIRED = 411,
+	PRECONDITION_FAILED = 412,
+	REQUEST_ENTITY_TOO_LARGE = 413,
+	REQUEST_URI_TOO_LONG = 414,
+	UNSUPPORTED_MEDIA_TYPE = 415,
+	REQUESTED_RANGE_NOT_SATISFIABLE = 416,
+	EXPECTATION_FAILED = 417,
+	UNPROCESSABLE_ENTITY = 422,
+	LOCKED = 423,
+	FAILED_DEPENDENCY = 424,
+	UPGRADE_REQUIRED = 426,
+
+	// 5xx server error
+	INTERNAL_SERVER_ERROR = 500,
+	NOT_IMPLEMENTED = 501,
+	BAD_GATEWAY = 502,
+	SERVICE_UNAVAILABLE = 503,
+	GATEWAY_TIMEOUT = 504,
+	HTTP_VERSION_NOT_SUPPORTED = 505,
+	INSUFFICIENT_STORAGE = 507,
+	NOT_EXTENDED = 510,
+};
+
+//-------------------------------------------------
+// Connection
+//
+// Handles the socket connection, issuing of requests and managing
+// responses.
+// ------------------------------------------------
+
+enum Connection_State { IDLE, REQ_STARTED, REQ_SENT };
+class HappyHttpConnection {
+	m_Port = null;
+	m_Host = null;
+	m_State = null;
+	m_Sock = null;
+	m_Buffer = null; // lines of request
+	m_Outstanding = null; // responses for outstanding requests
+	
+	
+	// doesn't connect immediately
+	constructor(host, port){
+		m_Host = host;
+		m_Port = port;
+		m_Buffer = [];
+		m_Outstanding = [];
+		m_State =Connection_State.IDLE;
+	}
+
+	// Don't need to call connect() explicitly as issuing a request will
+	// call it automatically if needed.
+	// But it could block (for name lookup etc), so you might prefer to
+	// call it in advance.
+	function connect(){
+		m_Sock = socket.tcp();
+		m_Sock.connect(m_Host, m_Port);
+		m_Sock.settimeout(0.01);
+	}
+
+	// close connection, discarding any pending requests.
+	function close(){
+		m_Sock.close();
+		m_Sock = null;
+		m_Outstanding.clear();
+	}
+
+	// Update the connection (non-blocking)
+	// Just keep calling this regularly to service outstanding requests.
+	function pump(milisec=10) { //10 miliseconds to prevent high cpu load
+		if( m_Outstanding.empty() ) return;		// no requests outstanding
+		assert( m_Sock != null );	// outstanding requests but no connection!
+
+		if( !datawaiting( m_Sock, milisec) ) return;	// recv will block
+
+		local rc = m_Sock.receive(2048);
+		switch(rc[1]){
+			case socket.IO_DONE:
+			case socket.IO_TIMEOUT:
+				break;
+			default:
+				if(rc[0].len() == 0) throw(format("socket io error %d", rc[1]));
+		}
+		local buf = rc[0];
+		local a = buf.len();
+		if( a== 0 )
+		{
+			// connection has closed
+			local r = m_Outstanding[0];
+			r->notifyconnectionclosed();
+			assert( r->completed() );
+			m_Outstanding.remove(0);
+
+			// any outstanding requests will be discarded
+			close();
+		}
+		else
+		{
+			local used = 0;
+			while( used < a && !m_Outstanding.empty() )
+			{
+				local r = m_Outstanding[0];
+				local u = r->pump( buf, used, a-used );
+
+				// delete response once completed
+				if( r->completed() )	m_Outstanding.remove(0);
+				used += u;
+			}
+
+			// NOTE: will lose bytes if response queue goes empty
+			// (but server shouldn't be sending anything if we don't have
+			// anything outstanding anyway)
+			assert( used == a );	// all bytes should be used up by here.
+		}
+	}
+
+	// any requests still outstanding?
+	function outstanding() { return m_Outstanding && !m_Outstanding.empty(); }
+
+	// ---------------------------
+	// high-level request interface
+	// ---------------------------
+
+	// method is "GET", "POST" etc...
+	// url is only path part: eg  "/index.html"
+	// headers is array of name/value pairs, terminated by a null-ptr
+	// body & bodysize specify body data of request (eg values for a form)
+	function request( method, url, headers=null, body=null, bodysize=0){
+		local gotcontentlength = false;	// already in headers?
+
+		// check headers for content-length
+		// TODO: check for "Host" and "Accept-Encoding" too
+		// and avoid adding them ourselves in putrequest()
+		if( headers ) gotcontentlength = headers.get("content-length", false);
+
+		putrequest( method, url );
+
+		if( body && !gotcontentlength ) putheader( "Content-Length", bodysize );
+
+		if( headers ) foreach(name, value in headers) putheader( name, value );
+
+		endheaders();
+
+		if( body ) send( body, bodysize );
+	}
+
+	// ---------------------------
+	// low-level request interface
+	// ---------------------------
+
+	// begin request
+	// method is "GET", "POST" etc...
+	// url is only path part: eg  "/index.html"
+	function putrequest( method, url ){
+		if( m_State != Connection_State.IDLE ) throw ("Request already issued" );
+		if( !method || !url ) throw ( "Method and url can't be NULL" );
+
+		m_State = Connection_State.REQ_STARTED;
+
+		local req = format("%s %s HTTP/1.1", method, url);
+		m_Buffer.push( req );
+
+		putheader( "Host", m_Host );	// required for HTTP1.1
+
+		// don't want any fancy encodings please
+		putheader("Accept-Encoding", "identity");
+
+		// Push a new response onto the queue
+		local r = new HappyHttpResponse( method, url, this );
+		m_Outstanding.push( r );
+	}
+
+	// Add a header to the request (call after putrequest() )
+	function putheader( header, value ){
+		if( m_State != Connection_State.REQ_STARTED ) throw ( "putheader() failed" );
+		m_Buffer.push( format("%s: %s", header, value.tostring()) );	
+	}
+
+	// Finished adding headers, issue the request.
+	function endheaders(){
+		if( m_State != Connection_State.REQ_STARTED ) throw "Cannot send header";
+		m_State = Connection_State.IDLE;
+		m_Buffer.push( "\r\n" ); //for double "\r\n\r\n"
+		local msg = m_Buffer.concat("\r\n");
+		m_Buffer.clear();
+		send( msg , msg.len() );
+	}
+
+	// send body data if any.
+	// To be called after endheaders()
+	function send( buf, numbytes ){
+		if( !m_Sock ) connect();
+		local n = m_Sock.send( buf , 0, numbytes );
+		//print(m_Sock.getfd(), n, buf);
+		if(n != numbytes) throw("Could not send ");
+	}
+
+	// return true if socket has data waiting to be read
+	static function datawaiting( sock , milisec=0)
+	{
+		try {
+			local rc = socket.select( [sock], [], milisec ? milisec / 1000.0 : 0);
+			local rar = rc[0];
+			if(rar.len() == 1 && rar[0] == sock) return true;
+			return false;
+		}
+		catch(e){
+			if(e == "timeout") return false;
+			else throw(e);
+		}
+	}
+
+    function response_begin( r ) {}
+    function response_data( r, data, data_idx, numbytes ) {}
+    function response_complete( r ) {}
+};
+
+enum Response_state {
+		STATUSLINE,		// start here. status line is first line of response.
+		HEADERS,		// reading in header lines
+		BODY,			// waiting for some body data (all or a chunk)
+		CHUNKLEN,		// expecting a chunk length indicator (in hex)
+		CHUNKEND,		// got the chunk, now expecting a trailing blank line
+		TRAILERS,		// reading trailers after body.
+		COMPLETE,		// response is complete!
+	};
+	
+class HappyHttpResponse {
+	m_Connection = null; 	// to access callback ptrs
+	m_Method = null; 		// req method: "GET", "POST" etc...
+	m_Url = null; 			// req url: /image.php?d=2...
+	m_VersionString = null;	// HTTP-Version
+	m_Version = null;		// 10: HTTP/1.0    11: HTTP/1.x (where x>=1)
+	m_Status = null;		// Status-Code
+	m_Reason = null;		// Reason-Phrase
+	m_Headers = null;		// header/value pairs
+	m_BytesRead = null;		// body bytes read so far
+	m_Chunked = null;		// response is chunked?
+	m_ChunkLeft = null;		// bytes left in current chunk
+	m_Length = null;		// -1 if unknown
+	m_WillClose = null;		// connection will close at response end?
+	m_LineBuf = null;		// line accumulation for states that want it
+	m_HeaderAccum = null;	// accumulation buffer for headers
+	m_State = null;
+	
+	// only Connection creates Responses.
+	constructor(method, url, conn){
+		m_Method = method;
+		m_Url = url;
+		m_Connection = conn;
+		m_Version = 0;
+		m_Status = 0;
+		m_BytesRead = 0;
+		m_Chunked = false;
+		m_ChunkLeft = 0;
+		m_Length = -1;
+		m_WillClose = false;
+		m_Headers = {};
+		m_HeaderAccum = "";
+		m_LineBuf = "";
+		m_State = Response_state.STATUSLINE;
+	}
+	
+	// retrieve a header (returns null if not present)
+	function getheader( name ){
+		local lname = name.tolower();
+		return m_Headers.get(lname, null);
+	}
+	
+	function completed() { return m_State == Response_state.COMPLETE; }
+	
+	// get the HTTP status code
+	function getstatus(){
+		// only valid once we've got the statusline
+		assert( m_State != Response_state.STATUSLINE );
+		return m_Status;
+	}
+	
+	// get the HTTP response reason string
+	function getreason(){
+		// only valid once we've got the statusline
+		assert( m_State != Response_state.STATUSLINE );
+		return m_Reason;
+	}
+	
+	// true if connection is expected to close after this response.
+	function willclose() { return m_WillClose; }
+	
+	function getconnection(){return m_Connection;}
+	function geturl() { return m_Url; }
+	function getLength() { return m_Length;}
+	
+	// pump some data in for processing.
+	// Returns the number of bytes used.
+	// Will always return 0 when response is complete.
+	function pump( data, start, datasize ){
+		assert( datasize != 0 );
+		local count = datasize;
+		local data_idx = 0;
+		//print(data);
+		while( count > 0 && m_State != Response_state.COMPLETE )
+		{
+			if( m_State == Response_state.STATUSLINE ||
+				m_State == Response_state.HEADERS ||
+				m_State == Response_state.TRAILERS ||
+				m_State == Response_state.CHUNKLEN ||
+				m_State == Response_state.CHUNKEND )
+			{
+				// we want to accumulate a line
+				local pos = data.find("\n", data_idx);
+				if( pos >= 0 )
+				{
+					count -= pos-data_idx+1;
+					local new_pos = pos;
+					if(pos > 0 && data[pos-1] == '\r') --new_pos;
+					m_LineBuf = data.slice(data_idx, new_pos);
+					data_idx = pos+1;
+					// now got a whole line!
+					switch( m_State )
+					{
+						case Response_state.STATUSLINE:
+							ProcessStatusLine( m_LineBuf );
+							break;
+						case Response_state.HEADERS:
+							ProcessHeaderLine( m_LineBuf );
+							break;
+						case Response_state.TRAILERS:
+							ProcessTrailerLine( m_LineBuf );
+							break;
+						case Response_state.CHUNKLEN:
+							ProcessChunkLenLine( m_LineBuf );
+							break;
+						case Response_state.CHUNKEND:
+							// just soak up the crlf after body and go to next state
+							assert( m_Chunked == true );
+							m_State = Response_state.CHUNKLEN;
+							break;
+						default:
+							break;
+					}
+					m_LineBuf = "";
+				}
+			}
+			else if( m_State == Response_state.BODY )
+			{
+				local bytesused = 0;
+				if( m_Chunked )
+					bytesused = ProcessDataChunked( data, data_idx, count );
+				else
+					bytesused = ProcessDataNonChunked( data, data_idx, count );
+				data += bytesused;
+				count -= bytesused;
+			}
+		}
+		// return number of bytes used
+		return datasize - count;
+	}
+	
+	// tell response that connection has closed
+	function notifyconnectionclosed(){
+		if( m_State == Response_state.COMPLETE ) return;
+
+		// eof can be valid...
+		if( m_State == Response_state.BODY && 
+			!m_Chunked && m_Length <= 0 ) Finish();	// we're all done!
+		else throw ( "Connection closed unexpectedly" );
+	}
+	
+	function FlushHeader(){
+		if( m_HeaderAccum.empty() ) return;	// no flushing required
+		local rc = m_HeaderAccum.match("([^:]+):%s*(.+)");
+		if(!rc) throw(format("Invalid header (%s)", m_HeaderAccum));
+		m_Headers[ rc[0].tolower() ] <- rc[1];
+		m_HeaderAccum = "";
+	}
+
+	function ProcessStatusLine( line ){
+		//HTTP/1.1 200 OK
+		local rc = line.match("%s*(%S+)%s+(%d+)%s+(.+)");
+		if(!rc) throw(format("BadStatusLine (%s)", line));
+		m_VersionString = rc[0];
+		m_Status = rc[1].tointeger();
+		m_Reason = rc[2];
+
+		if( m_Status < 100 || m_Status > 999 ) throw ( format("BadStatusLine (%s)", line) );
+
+		if( m_VersionString == "HTTP:/1.0" ) m_Version = 10;
+		else if( m_VersionString.startswith( "HTTP/1." ) ) m_Version = 11;
+		else throw ( format("UnknownProtocol (%s)", m_VersionString) );
+		// TODO: support for HTTP/0.9
+
+		// OK, now we expect headers!
+		m_State = Response_state.HEADERS;
+		m_HeaderAccum = "";		
+	}
+	
+	function ProcessHeaderLine( line ){
+		if( line.empty() )
+		{
+			FlushHeader();
+			// end of headers
+
+			// HTTP code 100 handling (we ignore 'em)
+			if( m_Status == HTTP_status_code.CONTINUE )
+				m_State = Response_state.STATUSLINE;	// reset parsing, expect new status line
+			else
+				BeginBody();			// start on body now!
+			return;
+		}
+
+		if( line[0] == ' ' )
+		{
+			// it's a continuation line - just add it to previous data
+			m_HeaderAccum += line;
+		}
+		else
+		{
+			// begin a new header
+			FlushHeader();
+			m_HeaderAccum = line;
+		}
+	}
+	
+	function ProcessTrailerLine(line){
+		// TODO: handle trailers?
+		// (python httplib doesn't seem to!)
+		if( line.empty() ) Finish();
+		// just ignore all the trailers...
+	}
+	
+	function ProcessChunkLenLine( line ){
+		// chunklen in hex at beginning of line
+		m_ChunkLeft = line.tointeger(16);
+
+		if( m_ChunkLeft == 0 )
+		{
+			// got the whole body, now check for trailing headers
+			m_State = Response_state.TRAILERS;
+			m_HeaderAccum = "";
+		}
+		else
+		{
+			m_State = Response_state.BODY;
+		}
+	}
+	
+	function ProcessDataChunked( data, data_idx, count ){
+		assert( m_Chunked );
+
+		local n = count;
+		if( n>m_ChunkLeft ) n = m_ChunkLeft;
+
+		// invoke callback to pass out the data
+		m_Connection.response_data( this, data, data_idx, n );
+
+		m_BytesRead += n;
+
+		m_ChunkLeft -= n;
+		assert( m_ChunkLeft >= 0);
+		if( m_ChunkLeft == 0 )
+		{
+			// chunk completed! now soak up the trailing CRLF before next chunk
+			m_State = Response_state.CHUNKEND;
+		}
+		return n;
+	}
+	
+	function ProcessDataNonChunked( data, data_idx, count ){
+		local n = count;
+		if( m_Length != -1 )
+		{
+			// we know how many bytes to expect
+			local remaining = m_Length - m_BytesRead;
+			if( n > remaining ) n = remaining;
+		}
+
+		// invoke callback to pass out the data
+		m_Connection.response_data( this, data, data_idx, n );
+		m_BytesRead += n;
+
+		// Finish if we know we're done. Else we're waiting for connection close.
+		if( m_Length != -1 && m_BytesRead == m_Length ) Finish();
+		return n;
+	}
+	
+	// OK, we've now got all the headers read in, so we're ready to start
+	// on the body. But we need to see what info we can glean from the headers
+	// first...
+	function BeginBody(){
+		m_Chunked = false;
+		m_Length = -1;	// unknown
+		m_WillClose = false;
+
+		// using chunked encoding?
+		local trenc = getheader( "transfer-encoding" );
+		if( trenc && trenc == "chunked" )
+		{
+			m_Chunked = true;
+			m_ChunkLeft = -1;	// unknown
+		}
+
+		m_WillClose = CheckClose();
+
+		// length supplied?
+		local contentlen = getheader( "content-length" );
+		if( contentlen && !m_Chunked )
+		{
+			m_Length = contentlen.tointeger();
+		}
+
+		// check for various cases where we expect zero-length body
+		if( m_Status == HTTP_status_code.NO_CONTENT ||
+			m_Status == HTTP_status_code.NOT_MODIFIED ||
+			( m_Status >= 100 && m_Status < 200 ) ||		// 1xx codes have no body
+			m_Method == "HEAD" )
+		{
+			m_Length = 0;
+		}
+
+
+		// if we're not using chunked mode, and no length has been specified,
+		// assume connection will close at end.
+		if( !m_WillClose && !m_Chunked && m_Length == -1 ) m_WillClose = true;
+
+		// Invoke the user callback, if any
+		m_Connection.response_begin( this );
+
+	/*
+		printf("---------BeginBody()--------\n");
+		printf("Length: %d\n", m_Length );
+		printf("WillClose: %d\n", (int)m_WillClose );
+		printf("Chunked: %d\n", (int)m_Chunked );
+		printf("ChunkLeft: %d\n", (int)m_ChunkLeft );
+		printf("----------------------------\n");
+	*/
+		// now start reading body data!
+		if( m_Chunked ) m_State = Response_state.CHUNKLEN;
+		else m_State = Response_state.BODY;
+	}
+	
+	function CheckClose(){
+		if( m_Version == 11 )
+		{
+			// HTTP1.1
+			// the connection stays open unless "connection: close" is specified.
+			local conn = getheader( "connection" );
+			if( conn && conn == "close" ) return true;
+			else return false;
+		}
+
+		// Older HTTP
+		// keep-alive header indicates persistant connection
+		if( getheader( "keep-alive" ) ) return false;
+
+		// TODO: some special case handling for Akamai and netscape maybe?
+		// (see _check_close() in python httplib.py for details)
+		return true;
+	}
+	
+	function Finish(){
+		m_State = Response_state.COMPLETE;
+		
+		// invoke the callback
+		m_Connection.response_complete( this );
+	}
+}
+
+/*
+class MyHappyHttpConnection extends HappyHttpConnection {
+	count = null;
+	
+	constructor(host, port){
+		base.constructor(host, port);
+		count = 0;
+	}
+	function response_begin( r ) {
+		printf( "BEGIN (%d %s)\n", r->getstatus(), r->getreason() );
+		count = 0;
+	}
+	function response_data( r, data, data_idx, numbytes ) {
+		print( data );
+		count += numbytes;
+	}
+	function response_complete( r ) {
+		printf( "COMPLETE (%d bytes)\n", count );
+	}
+}
+
+function test_happyhttp(){
+	local start = os.getmillicount();
+	local conn = MyHappyHttpConnection( "www.scumways.com", 80);
+	conn.request( "GET", "/happyhttp/test.php" );
+	while( conn.outstanding() ) conn.pump();
+	print(format("Took : %d ms", os.getmillispan(start)));
+}
+
+test_happyhttp();
+*/

+ 77 - 41
ourbiz/ourbiz-client.nut

@@ -1,3 +1,6 @@
+local globals = getroottable();
+local constants = getconsttable();
+
 class AuthDigest {
 class AuthDigest {
 	_nc = null;
 	_nc = null;
 	_cnonce = null;
 	_cnonce = null;
@@ -45,7 +48,7 @@ class AuthDigest {
 		_nonce = null;
 		_nonce = null;
 		_opaque = null;
 		_opaque = null;
 		local function checkKV(k,v){
 		local function checkKV(k,v){
-			if(v[0] == '"') v = v.slice(1, -2);
+			if(v[0] == '"') v = v.slice(1, -1);
 			if(k == "realm") _realm = v;
 			if(k == "realm") _realm = v;
 			else if(k == "qop") _qop = v;
 			else if(k == "qop") _qop = v;
 			else if(k == "nonce") _nonce = v;
 			else if(k == "nonce") _nonce = v;
@@ -53,8 +56,8 @@ class AuthDigest {
 			return true;
 			return true;
 		}
 		}
 		checkKV.setenv(this);
 		checkKV.setenv(this);
-		szAuthenticate.gmatch("(%w+)%s*=%s*(%S+),", checkKV);
-		printf("%s, %s, %s, %s\n", _realm || "?", _qop || "?", _nonce || "?", _opaque || "?");
+		szAuthenticate.gmatch("(%w+)%s*=%s*([^%s,]+)", checkKV);
+		//printf("%s, %s, %s, %s\n", _realm || "?", _qop || "?", _nonce || "?", _opaque || "?");
 	}
 	}
 }
 }
 
 
@@ -92,7 +95,7 @@ class HTTPConnBase extends HappyHttpConnection
             _www_authenticate = r->getheader("WWW-Authenticate");
             _www_authenticate = r->getheader("WWW-Authenticate");
         }
         }
     }
     }
-    function response_data( r, data, n )
+    function response_data( r, data, data_idx, n )
     {
     {
     }
     }
     function response_complete( r )
     function response_complete( r )
@@ -122,9 +125,10 @@ class HTTPConnAuthBase extends HTTPConnBase
     function response_begin( r )
     function response_begin( r )
     {
     {
         base.response_begin(r);
         base.response_begin(r);
-        if(my_status == 401)
+        if(my_status != 200)
         {
         {
-            throw ("Authentication required.");
+	    //foreach(k,v in get_last_stackinfo()) print(k,v)
+            throw (format("HTTP Error %d", my_status));
         }
         }
     }
     }
 
 
@@ -146,8 +150,8 @@ class HTTPConnAuthBase extends HTTPConnBase
                     putheader("Content-Type", str);
                     putheader("Content-Type", str);
                 }
                 }
                 if(_digest_auth->okToAuthDigest())
                 if(_digest_auth->okToAuthDigest())
-                    putheader("Authorization", _digest_auth->auth_digest(str, method, uri));
-                putheader("Content-Length", bodysize);
+                    putheader("Authorization", _digest_auth->auth_digest(method, uri));
+                putheader("Content-Length", bodysize || 0);
                 endheaders();
                 endheaders();
                 if(body && bodysize) send( body, bodysize );
                 if(body && bodysize) send( body, bodysize );
                 while( outstanding() )
                 while( outstanding() )
@@ -159,6 +163,7 @@ class HTTPConnAuthBase extends HTTPConnBase
             }
             }
             catch(e)
             catch(e)
             {
             {
+		//foreach(k,v in get_last_stackinfo()) print(k,v)
                 if(my_status == 401){
                 if(my_status == 401){
                         _digest_auth->parse_authenticate(_www_authenticate);
                         _digest_auth->parse_authenticate(_www_authenticate);
                         close();
                         close();
@@ -188,9 +193,9 @@ class HTTPConn extends HTTPConnAuthBase
         if(resp_length > 0) result_out.reserve(resp_length);
         if(resp_length > 0) result_out.reserve(resp_length);
     }
     }
 
 
-    function response_data( r, data, n )
+    function response_data( r, data, data_idx, n )
     {
     {
-        result_out.write(data, n);
+        result_out.write_str(data, data_idx, n);
     }
     }
 };
 };
 
 
@@ -213,14 +218,17 @@ class HTTPConnBin extends HTTPConnAuthBase
         szContentType = r->getheader("Content-type");
         szContentType = r->getheader("Content-type");
     }
     }
 
 
-    function response_data( r, data, n )
+    function response_data( r, data, data_idx, n )
     {
     {
-        my_result.write(data, n);
+        my_result.write_str(data, data_idx, n);
     }
     }
 };
 };
 
 
 enum conn_type_e {e_conn_none, e_conn_http, e_conn_dbfile};
 enum conn_type_e {e_conn_none, e_conn_http, e_conn_dbfile};
 
 
+constants.rawdelete("TimePeriode");
+enum TimePeriode {is_years = 1, is_months, is_weeks, is_days};
+
 local _the_app_server = null;
 local _the_app_server = null;
 class AppServer
 class AppServer
 {
 {
@@ -277,12 +285,11 @@ class AppServer
 
 
     function check_login()
     function check_login()
     {
     {
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local my_result = blob();
             local my_result = blob();
             local http = HTTPConn(my_result);
             local http = HTTPConn(my_result);
-            appServer.httpcon_setup(http);
+            httpcon_setup(http);
             http.send_my_request("GET", "/OURBIZ");
             http.send_my_request("GET", "/OURBIZ");
             if(http.my_status != 200)
             if(http.my_status != 200)
             {
             {
@@ -303,17 +310,28 @@ class AppServer
 
 
     function close()
     function close()
     {
     {
+        if(get_conn_type() == conn_type_e.e_conn_http)
+        {
+            local my_result = blob();
+            local http = HTTPConn(my_result);
+            httpcon_setup(http);
+	    try {
+		http.send_my_request("GET", "/SQ/logout");
+	    }
+	    catch(e){
+		if(http.my_status != 302) throw(my_result.tostring());
+	    }
+        }
     }
     }
 
 
     function _get_data(method, query_string, httpBody, data_out, throwNotFound=true)
     function _get_data(method, query_string, httpBody, data_out, throwNotFound=true)
     {
     {
         data_out.clear();
         data_out.clear();
 
 
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local httpRequest = HTTPConn(data_out);
             local httpRequest = HTTPConn(data_out);
-            appServer.httpcon_setup(httpRequest);
+            httpcon_setup(httpRequest);
 
 
             httpRequest.send_my_request(method, query_string, httpBody,
             httpRequest.send_my_request(method, query_string, httpBody,
                                      httpBody ? httpBody.len() : 0);
                                      httpBody ? httpBody.len() : 0);
@@ -329,11 +347,10 @@ class AppServer
     {
     {
         rec.clear();
         rec.clear();
 
 
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local  httpRequest = HTTPConnBin(rec);
             local  httpRequest = HTTPConnBin(rec);
-            appServer.httpcon_setup(httpRequest);
+            httpcon_setup(httpRequest);
 
 
             httpRequest.send_my_request("GET", get_url);
             httpRequest.send_my_request("GET", get_url);
             if(httpRequest.my_status != 200)
             if(httpRequest.my_status != 200)
@@ -348,8 +365,7 @@ class AppServer
     function get_binary_data(rec, binary_type, table, aid, extra_url=0, throwNotFound=true)
     function get_binary_data(rec, binary_type, table, aid, extra_url=0, throwNotFound=true)
     {
     {
         rec.clear();
         rec.clear();
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local url = format("/DB/GetBin?%s=%s", table, aid);
             local url = format("/DB/GetBin?%s=%s", table, aid);
             if(extra_url) url += extra_url;
             if(extra_url) url += extra_url;
@@ -362,12 +378,11 @@ class AppServer
     {
     {
         data.clear();
         data.clear();
 
 
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local my_result = blob();
             local my_result = blob();
             local  httpRequest = HTTPConn(my_result);
             local  httpRequest = HTTPConn(my_result);
-            appServer.httpcon_setup(httpRequest);
+            httpcon_setup(httpRequest);
 
 
             local url = format("/DB/GetList?list=%s", table);
             local url = format("/DB/GetList?list=%s", table);
             if(query_string) url += query_string;
             if(query_string) url += query_string;
@@ -376,7 +391,8 @@ class AppServer
                                      httpBody ? httpBody.len() : 0);
                                      httpBody ? httpBody.len() : 0);
             if(httpRequest.my_status != 200)
             if(httpRequest.my_status != 200)
             {
             {
-                throw(my_result.tostring());
+		if(my_result.len()) throw(my_result.tostring());
+		else throw(format("HTTP error %d", httpRequest.my_status));
             }
             }
             //parse_jsonArray2Vector(data, my_result);
             //parse_jsonArray2Vector(data, my_result);
             sle2vecOfvec(my_result, data);
             sle2vecOfvec(my_result, data);
@@ -388,12 +404,11 @@ class AppServer
     {
     {
         data.clear();
         data.clear();
 
 
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local my_result = blob();
             local my_result = blob();
             local httpRequest = HTTPConn(my_result);
             local httpRequest = HTTPConn(my_result);
-            appServer.httpcon_setup(httpRequest);
+            httpcon_setup(httpRequest);
 
 
             local url = format("/DB/GetList?list=%s", table);
             local url = format("/DB/GetList?list=%s", table);
             if(qs) url += qs;
             if(qs) url += qs;
@@ -413,12 +428,11 @@ class AppServer
 
 
     function do_dbaction(rec, action, table, aid, version, query_string=0)
     function do_dbaction(rec, action, table, aid, version, query_string=0)
     {
     {
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local my_result = blob();
             local my_result = blob();
             local httpRequest = HTTPConn(my_result);
             local httpRequest = HTTPConn(my_result);
-            appServer.httpcon_setup(httpRequest);
+            httpcon_setup(httpRequest);
 
 
             local url = "/DB/Action";
             local url = "/DB/Action";
             if(query_string) url = format("%s?%s", url, query_string);
             if(query_string) url = format("%s?%s", url, query_string);
@@ -464,12 +478,11 @@ class AppServer
 
 
     function get_str_record(table, qs, aid, extra_url=0)
     function get_str_record(table, qs, aid, extra_url=0)
     {
     {
-        local appServer = AppServer.getAppServer();
-        if(appServer.get_conn_type() == conn_type_e.e_conn_http)
+        if(get_conn_type() == conn_type_e.e_conn_http)
         {
         {
             local my_result = blob();
             local my_result = blob();
             local httpRequest = HTTPConn(my_result);
             local httpRequest = HTTPConn(my_result);
-            appServer.httpcon_setup(httpRequest);
+            httpcon_setup(httpRequest);
 
 
             local url = format("/DB/GetOne?%s", table);
             local url = format("/DB/GetOne?%s", table);
             if(qs) url += qs;
             if(qs) url += qs;
@@ -524,7 +537,7 @@ class AppServer
     }
     }
 
 
     function entities_get_bar_chart_statistics_list(data, entity_id, sab, periode_count=12,
     function entities_get_bar_chart_statistics_list(data, entity_id, sab, periode_count=12,
-            periode=periode_is_months)
+            periode=TimePeriode.is_months)
     {
     {
         local qs = format("&statistics=%d&periode_type=%s&periode_count=%d&sab=%c",
         local qs = format("&statistics=%d&periode_type=%s&periode_count=%d&sab=%c",
                            entity_id, getStatisticsPeriodeType(periode),
                            entity_id, getStatisticsPeriodeType(periode),
@@ -606,7 +619,7 @@ class AppServer
     }
     }
 
 
     function orders_get_bar_chart_statistics_list(data, sab, periode_count=12,
     function orders_get_bar_chart_statistics_list(data, sab, periode_count=12,
-            periode=periode_is_months, paidUnpaid=false)
+            periode=TimePeriode.is_months, paidUnpaid=false)
     {
     {
         local myUrl = format("&statistics=1&periode_type=%s&periode_count=%d&paid_unpaid=%s&sab=%s", 
         local myUrl = format("&statistics=1&periode_type=%s&periode_count=%d&paid_unpaid=%s&sab=%s", 
 		getStatisticsPeriodeType(periode), periode_count, paidUnpaid ? "1" : "0", sab);
 		getStatisticsPeriodeType(periode), periode_count, paidUnpaid ? "1" : "0", sab);
@@ -657,7 +670,7 @@ class AppServer
             parse_jsonObject2map(p, kit_details, js.c_str());
             parse_jsonObject2map(p, kit_details, js.c_str());
         }
         }
 */
 */
-        if(js[0] != '[') throw "Invalid sls encoded !";
+        if(js[0] != '[') throw "Invalid sle encoded !";
         local start = 0;
         local start = 0;
         local pos = sle2map(start, js.len(), rec);
         local pos = sle2map(start, js.len(), rec);
         pos = sle2vecOfvec(pos, js.len() - (pos - start), prices_list);
         pos = sle2vecOfvec(pos, js.len() - (pos - start), prices_list);
@@ -681,7 +694,7 @@ class AppServer
             parse_jsonArray2Vector(p, warranty_data, js.c_str());
             parse_jsonArray2Vector(p, warranty_data, js.c_str());
         }
         }
 */
 */
-        if(js[0] != '[') throw "Invalid sls encoded !";
+        if(js[0] != '[') throw "Invalid sle encoded !";
         local start = 0;
         local start = 0;
         local pos = sle2vecOfvec(start, js.len(), sales_tax_data);
         local pos = sle2vecOfvec(start, js.len(), sales_tax_data);
         pos = sle2vecOfvec(pos, js.len() - (pos - start), measure_units_data);
         pos = sle2vecOfvec(pos, js.len() - (pos - start), measure_units_data);
@@ -726,7 +739,7 @@ class AppServer
     }
     }
 
 
     function products_get_bar_chart_statistics_list(data, product_id, sab, periode_count=12,
     function products_get_bar_chart_statistics_list(data, product_id, sab, periode_count=12,
-            periode=periode_is_months)
+            periode=TimePeriode.is_months)
     {
     {
         local myUrl = format("&statistics=%d&periode_type=%s&periode_count=%d&sab=%s", product_id, 
         local myUrl = format("&statistics=%d&periode_type=%s&periode_count=%d&sab=%s", product_id, 
 		getStatisticsPeriodeType(periode), periode_count, sab);
 		getStatisticsPeriodeType(periode), periode_count, sab);
@@ -833,3 +846,26 @@ class AppServer
     }
     }
 };
 };
 
 
+local data = [];
+local appServer = AppServer.getAppServer();
+appServer.credentials("mingote", "tr14pink");
+appServer.connect("localhost", 8855);
+
+appServer.sales_tax_rates_get_list(data);
+foreach(i, row in data){
+	foreach(j, fld in row) print(i, j, fld);
+}
+
+appServer.order_types_get_list(data);
+foreach(i, row in data){
+	foreach(j, fld in row) print(i, j, fld);
+}
+
+function OurBizClientAtExit(){
+	local as = AppServer.getAppServer();
+	as.close();
+	print("Bye !");
+}
+setatexithandler(OurBizClientAtExit);
+
+//print(OurBizClientAtExit, getatexithandler());

+ 289 - 0
ourbiz/pdf-order.nut

@@ -0,0 +1,289 @@
+class PDF_Order extends Sq_Fpdf
+{
+	logoFileName = null;
+	secondColX = null;
+	cellHeight = null;
+	cellWidth = null;
+	fillColorBG = null;
+	drawColor = null;
+	static borderTop = "LRT";
+	static borderBetween = "LR";
+	static borderBottom = "LBR";
+
+	water_mark = null;
+	page_title = null;
+	page_title2 = null;
+	page_footer = null;
+        labelNumber = null;
+	strNumber = null;
+	labelDate = null;
+	strDate = null;
+
+	lineHeaders = null;
+	totalHeaders = null;
+	strLines = null;
+
+
+	strTotals = null;
+	strEntity = null;
+
+	constructor()
+	{
+	    base.constructor();
+		AliasNbPages();
+		SetFont("Arial", "", 12);
+		SetAuthor("DADBIZ & OurBiz");
+		//SetCompression(false);
+	}
+
+	function _tr(str) {return str;}
+
+	function do_init()
+	{
+		secondColX = 107;
+		cellHeight = 7;
+		cellWidth = 44;
+		fillColorBG = 0xEE;
+		drawColor = 0xCC;
+		page_title = "OURBIZ";
+		page_title2 = "www.dadbiz.es";
+		page_footer = "Thanks for choose our products and services !";
+		labelNumber = _tr("Order Number");
+		strNumber = "20128456";
+		labelDate = _tr("Date");
+		strDate = "10/04/2012";
+		
+		strTotals = [];
+		strEntity = [];
+		strLines = [];
+
+		lineHeaders = [];
+		newColInfo(lineHeaders, _tr("Code"), 16, 'C', false);
+		newColInfo(lineHeaders, _tr("Description"), 93, 'L', true),
+		newColInfo(lineHeaders, _tr("Quantity"), 16, 'R', true),
+		newColInfo(lineHeaders, _tr("Price"), 20, 'R', false),
+		newColInfo(lineHeaders, _tr("Disc. %"), 10, 'R', true),
+		newColInfo(lineHeaders, _tr("S.T. I %"), 10, 'R', true),
+		newColInfo(lineHeaders, _tr("S.T. II %"), 10, 'R', true),
+		newColInfo(lineHeaders, _tr("Subtotal"), 20, 'R', false);
+
+		totalHeaders = [];
+		newColInfo(totalHeaders, _tr("Page"), 20, 'C', false);
+		newColInfo(totalHeaders, _tr("Subtotal"), 45, 'C', true);
+		newColInfo(totalHeaders, _tr("Sales Tax I"), 42, 'C', true);
+		newColInfo(totalHeaders, _tr("Sales Tax II"), 42, 'C', false);
+		newColInfo(totalHeaders, _tr("Total"), 46, 'C', true);
+	}
+
+	function newColInfo(vec, str, width, dataAlign, fitScale)
+	{
+		vec.push({
+			str = str,
+			width = width,
+			dataAlign = dataAlign,
+			fitScale = fitScale,
+		});
+	}
+
+	function getLogo(){
+		if(!logoFileName || logoFileName.empty()){
+			logoFileName = "dadbiz_120.png";
+		}
+		return logoFileName;
+	}
+
+	function logoImage(px, py, pwidth){
+		local logoImg = getLogo();
+		if(!logoImg.empty()) Image(logoImg, px, py, pwidth);
+	}
+
+	function orderTable(lineCount)
+	{
+		local norder_pages = strLines.size()/lineCount;
+		if(strLines.len()%lineCount) ++norder_pages;
+		if(!norder_pages) ++norder_pages;
+
+		local tmp_str;
+		local font_settings = {};
+
+		AliasNbPages();
+		for(int_t _np=0; _np < norder_pages; ++_np)
+		{
+			AddPage();
+			SetFont("Arial", "0", 12);
+			//if(getLogoImage() != null) Image(getLogoImage(), mLogoFileName, 10, 8, 30, 0, 0, 0);
+			//Image("logo.png",10,8);
+			LogoImage(10, 8, 30);
+			// Header
+
+			SetFontSize(12);
+			SetFillColor(fillColorBG);
+			SetDrawColor(drawColor);
+			SetLineWidth(0.3);
+			SetFont("", "B");
+
+			local savedY = GetY();
+
+			Cell(32);
+			Cell(cellWidth*1.65, cellHeight, page_title, "", 0, 'C');
+			Ln();
+			Cell(32);
+			GetFontSettings(font_settings);
+			SetFont("Arial", "i", 9);
+			MultiCell(cellWidth*1.65, cellHeight*0.6, page_title2, "", 'C');
+			SetFontSettings(font_settings);
+
+			SetY(savedY);
+
+			SetFontSize(10);
+			Cell(secondColX);
+
+			Cell(cellWidth, cellHeight, labelNumber, "1", 0, 'C', true);
+			Cell(cellWidth, cellHeight, labelDate, "1", 0, 'C', true);
+
+			Ln();
+			// Color and font restoration
+			SetFillColor(224, 235, 255);
+			SetFontSize(12);
+
+			Cell(secondColX);
+
+			Cell(cellWidth, cellHeight, strNumber, "1", 0, 'C');
+			Cell(cellWidth, cellHeight, strDate, "1", 0, 'C');
+			Ln();
+			SetFontSize(10);
+
+			SetY(GetY() + 2);
+			Cell(secondColX);
+			local savedHeight = cellHeight;
+			cellHeight = 6;
+			local withBorder = borderTop;
+			local x = 0;
+			for(local i=0, size = strEntity.size(); i< size; ++i)
+			{
+				local value = strEntity[i];
+				CellFitScale(cellWidth * 2, cellHeight, value, withBorder,
+					0, 'L', false, 0);
+				if (x++ == size - 2) withBorder = borderBottom;
+				else withBorder = borderBetween;
+				Ln();
+				Cell(secondColX);
+			}
+			cellHeight = savedHeight;
+			SetY(GetY() + 2);
+			SetFillColor(fillColorBG);
+			SetDrawColor(drawColor);
+			SetLineWidth(0.3);
+			SetFont("", "B");
+
+			local savedFontSize = GetFontSize();
+			for(local i=0, size=lineHeaders.size(); i<size; ++i)
+			{
+				local header = lineHeaders[i];
+				if(header.fitScale) CellFitScale(header.width, cellHeight, header.str, "1", 0,
+					'C', true);
+				else Cell(header.width, cellHeight, header.str, "1", 0, 'C', true);
+			}
+			SetFontSize(savedFontSize);
+			Ln();
+			// Color and font restoration
+			SetFillColor(224, 235, 255);
+			SetTextColor(0);
+			SetFont();
+			local fill = false;
+			withBorder = borderBetween;
+			local line_count = (lineCount == 0) ? 32 : lineCount;
+			line_count += line_count * _np;
+			savedHeight = cellHeight;
+			cellHeight = 6;
+			for(local i = _np*lineCount; i < line_count; i++)
+			{
+				x = 0;
+				for (local j=0, size=lineHeaders.size(); j<size; ++j)
+				{
+					local header = lineHeaders[j];
+					if(header.fitScale)  //clipping desciption
+					{
+						//ClippedCell(header.width, mCellHeight, getLineData(i, x), withBorder,
+						//	Position.RIGHTOF, header.dataAlign, fill);
+						CellFitScale(header.width, cellHeight, getLineData(i, x), withBorder,
+							0, header.dataAlign, fill, 0);
+					}
+					else
+					{
+						Cell(header.width, cellHeight, getLineData(i, x).tostring(), withBorder,
+							0, header.dataAlign, fill);
+					}
+					x++;
+				}
+				Ln();
+				fill = !fill;
+				if (i > line_count - 3) withBorder = borderBottom;
+			}
+
+			cellHeight = savedHeight;
+
+			SetY(GetY() + 2);
+			SetFillColor(fillColorBG);
+			SetDrawColor(drawColor);
+			SetLineWidth(0.3);
+			SetFont("", "B");
+
+			for(local i=0, size=totalHeaders.size(); i<size; ++i)
+			{
+				local header = totalHeaders[i];
+				Cell(header.width, cellHeight, header.str, "1", 0, 'C', true);
+			}
+			Ln();
+			// Color and font restoration
+			SetFillColor(224, 235, 255);
+			SetTextColor(0);
+			SetFont("", "B", 12);
+
+			x = 0;
+			for(local i=0, size=totalHeaders.size(); i<size; ++i)
+			{
+				local header = totalHeaders[i];
+				if(x == 0)
+				{
+					tmp_str = format("%d/%s", PageNo(), GetAliasNbPages());
+					Cell(header.width, cellHeight, tmp_str, "1", 0, header.dataAlign);
+				}
+				else
+				{
+					tmp_str = (_np == (norder_pages-1)) ? getTotalData(x) : "...";
+					Cell(header.width, cellHeight, tmp_str, "1", 0, header.dataAlign);
+				}
+				x++;
+			}
+
+			if(!water_mark.empty()){
+				SetAlpha(0.7, "Darken");
+				//SetAlpha(0.5);
+				SetFont("Arial","B",50);
+				SetTextColor(255,192,203);
+				RotatedText(35,190, water_mark, 45);
+				SetAlpha(1);
+				SetTextColor(0);
+			}
+			//footer message
+			Ln(10);
+			SetFont("", "i", 8);
+			MultiCell(0, cellHeight*0.6, page_footer, "", 'C');
+
+		}
+	}
+
+	function getLineData(row, col)
+	{
+		if(strLines.size() && row < strLines.size()) return strLines[row][col];
+		return "";
+	}
+
+	function getTotalData(col)
+	{
+		//<= and col-1 because it start with 1
+		if(strTotals.size() && col <= strTotals.size()) return strTotals[col-1];
+		return "";
+	}
+}

+ 193 - 0
ourbiz/pdf-table.nut

@@ -0,0 +1,193 @@
+class PdfSqlTable extends Sq_Fpdf {
+	col_h = null;
+	col_h0 = null;
+	doFill = null;
+	_cols_info = null;
+	page_title = null;
+	water_mark = null;
+	
+	constructor(){
+		base.constructor();
+		AliasNbPages();
+		SetFont("Arial","",10);
+		SetFillColor(224, 235, 255);
+		SetDrawColor(0xCC);
+		col_h = 5;
+		col_h0 = 8;
+		doFill = false;
+		_cols_info = [];
+		page_title = "";
+		water_mark = "";
+	}
+
+	function parse_field_header(col_name){
+		local ci = col_name.split("|");
+		local col_info = {};
+		col_info.colname <- ci.get(0, "?");
+		col_info.header <- ci.get(1, "?");
+		col_info.width <- ci.get(2, 10).tointeger();
+		col_info.align <- ci.get(3, "L");
+		col_info.format <- ci.get(4, "");
+		col_info.color <- ci.get(5, 0).tointeger();
+		col_info.bgcolor <- ci.get(6, 0).tointeger();
+		col_info.header <- col_info.header;
+		return col_info;
+	}
+
+	function set_cols(szCols, max_cols=12){
+		_cols_info = [];
+		for(local i=0; i < max_cols; ++i){
+			local p = szCols.get(i, null);
+			if (!p) break;
+			local col_info = parse_field_header(p);
+			_cols_info.push(col_info);
+		}
+		calc_cols_width();
+	}
+
+	function calc_cols_width(){
+		local grid_width, total_widths, char_width;
+		grid_width = GetW()-(GetLeftMargin()+GetRightMargin());
+		char_width = GetStringWidth("w");
+
+		total_widths = 0;
+		local col_count = _cols_info.len();
+		for(local k=0; k < col_count; ++k){
+			local v = _cols_info[k].width;
+			if (v > 0) total_widths += ((v * char_width) + 1);
+		}
+
+		for(local k=0; k < col_count; ++k){
+			local v = _cols_info[k];
+			local col_width = v.width;
+			if ( col_width < 0) col_width = grid_width - total_widths;
+			else col_width = col_width * char_width;
+			v.width = col_width;
+		}
+	}
+
+	function Header(){
+		SetFont("","BI",18);
+		Cell(160,0,page_title,"0",0,'L');
+		SetFont("","",10);
+		Cell(0,0,format("%d", PageNo()),"",2,'R');
+		//Line break
+		SetY(20);
+		//MyLine(m_lMargin, 18, m_w - m_rMargin, 18);
+		//Ln(15);
+		SetFont("","B",12);
+		local saved_fill = doFill;
+		doFill = true;
+		local saved_color = GetFillColor();
+		SetFillColor(0xEE);
+		for(local k=0, count=_cols_info.len(); k < count; ++k){
+			local v = _cols_info[k];
+			ClippedCell(v.width, col_h0, v.header, "1", 0, 'C', doFill);
+		}
+		Ln();
+		SetFillColor(saved_color);
+		doFill = saved_fill;
+		SetFont("","",10);
+	}
+
+	function GetPDF(db, sql, clipped, mf=null){
+		local stmt = db.prepare(sql);
+
+		local col_count = stmt.col_count();
+		_cols_info = [];
+
+		stmt.step();
+		for(local i=0; i < col_count; ++i){
+			local col_info = parse_field_header(stmt.col_name(i));
+			_cols_info.push(col_info);
+		}
+		stmt.reset();
+		calc_cols_width();
+		local result = clipped ? _gen_pdf_clipped(stmt, mf) : _gen_pdf_multiline(stmt, mf);
+		stmt.finalize();
+		return result;
+	}
+
+	function _gen_pdf_multiline(stmt, mf=null){
+		AddPage();
+		local cols_size = _cols_info.len();
+
+		while(stmt.next_row()){
+			local max_lines = 0
+			for(local k=0; k < cols_size; ++k){
+				local v = _cols_info[k];
+				local lines = CalcLines(v.width, stmt.col(k).tostring());
+				if(lines > max_lines) max_lines = lines;
+			}
+			local col_height = max_lines * col_h;
+			CheckPageBreak(col_height);
+			local saved_y = GetY();
+			for(local k=0; k < cols_size; ++k){
+				local saved_x = GetX();
+				local v = _cols_info[k];
+				local fill = doFill ? "DF" : "D";
+				Rect(saved_x, saved_y, v.width, col_height, fill);
+				MultiCell(v.width, col_h, stmt.col(k).tostring(), "LRT", v.align[0], doFill);
+				SetXY(saved_x + v.width, saved_y);
+			}
+			doFill = !doFill;
+			Ln(col_height);
+		}
+		//SetDisplayMode(FPDF::e_zoom_fullpage, FPDF::e_layout_single);
+		if(mf) {
+			Output(mf);
+			return true;
+		}
+		return Output("doc.pdf", 'S');
+	}
+
+	function _gen_pdf_clipped(stmt, mf=null){
+		AddPage();
+		local cols_size = _cols_info.len();
+
+		while(stmt.next_row()){
+			for(local k=0; k < cols_size; ++k){
+				local v = _cols_info[k];
+				ClippedCell(v.width, col_h, stmt.col(k).tostring(),
+					"1", 0, v.align[0], doFill);
+			}
+			doFill = !doFill;
+			Ln();
+		}
+		//SetDisplayMode(FPDF::e_zoom_fullpage, FPDF::e_layout_single);
+		if(mf) {
+			Output(mf);
+			return true;
+		}
+		return Output("doc.pdf", 'S');
+	}
+
+	function MyLine(x1, y1, x2, y2){
+		SetLineWidth(0.5);
+		local saved_color = GetDrawColor();
+		SetDrawColor(200,200,200);
+		Line(x1, y1, x2, y2);
+		SetDrawColor(saved_color);
+	}
+
+	function Footer(){
+		//Position at 1.5 cm from bottom
+		//MyLine(m_lMargin, m_h-20, m_w - m_rMargin, m_h-20)
+		SetY(-20);
+		//Page number
+		Cell(0,3, format("Page  %d / %s", PageNo(),
+							GetAliasNbPages()),"",1,'C');
+		SetFont("","I",8);
+		Cell(0,6, "OURBIZ is an excelente application to manage a business.","",0,'C');
+
+		if (water_mark && water_mark.len() > 0) {
+			SetAlpha(0.7, "Darken");
+			//SetAlpha(0.5);
+			SetFont("Arial","B",50);
+			SetTextColor(255,192,203);
+			RotatedText(35,190, water_mark, 45);
+			SetAlpha(1);
+			SetTextColor(0);
+		}
+	}
+}

+ 258 - 0
ourbiz/s/css/styles.css

@@ -0,0 +1,258 @@
+/*
+Design by Free Responsive Templates
+http://www.free-responsive-templates.com
+Released for free under a Creative Commons Attribution 3.0 Unported License (CC BY 3.0)
+*/ 
+* {
+	margin:0;
+	padding:0;
+}
+body {
+	font-family: verdana, serif;
+	font-size:14px;
+	background: url(../images/siteBackground.jpg);
+	margin: 0;
+	padding: 0;
+	color: rgb(50, 60, 70);	
+}
+a img { 
+	border: none;
+}
+a:link {
+	color: #0484c0;
+	text-decoration: underline; 
+}
+a:visited {
+	color: #0484c0;
+	text-decoration: underline;
+}
+a:hover, a:active, a:focus { 
+	color: #0484c0;
+	text-decoration: none;
+}
+h1 {
+	font-size:30px;
+	color:#8dc35a;
+	font-family: verdana, sans-serif;
+	padding: 0 0 10px 0;
+}
+h2 {
+	font-size:24px;
+	color:#ffffff;
+	font-family: verdana, sans-serif;
+	padding: 0 0 10px 0;
+	text-shadow: 0px 0px, 1px 3px #b8bec0;
+}
+h3 {
+	font-size:30px;
+	color:#677a83;
+	font-family: verdana, sans-serif;
+	padding: 0 0 10px 0;
+}
+p {
+	line-height:140%;
+}
+.wrapper {
+	width:960px;
+	margin:40px auto;
+	background: #ffffff;
+	box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+	padding-bottom:50px;
+}
+header {
+	padding:50px 50px 10px 50px;
+}
+.logo {
+	float: left;
+	background: url(../images/logoBackground.png) no-repeat;
+	width: 122px;
+	height: 123px;
+}
+.logo h1 a {
+	text-shadow: 0px 0px, 1px 2px #5b9e11;
+	font-size:60px;
+	color: #ffffff;
+	text-decoration:none;
+	padding:10px;
+	line-height:110px;
+}
+footer {
+	text-align:center;
+	padding-bottom:40px
+}
+.horSeparator {
+	background:url(../images/horSeparator.png) repeat-x;
+	height:13px;
+	margin:20px 0;
+}
+section {
+	padding:0 50px;
+}
+.post {
+	border-bottom:1px solid rgba(200, 207, 210, 1);
+	padding:50px 0;
+	margin:0 50px;
+}
+.post header {
+	float:right;
+	width:519px;
+	padding:0;
+}
+.post p {
+	float:right;
+	width:519px;
+}
+.post aside img {
+	border:1px solid #c8cfd2;
+	margin:10px 0 0 0;
+}
+.sectionFooter {
+	background: #d6d9da;
+	padding:40px 0;
+	margin:0 50px;
+	border-top:1px solid #ffffff;
+}
+.footerBox {
+	width:40%;
+	float:left;
+	padding:0 40px;
+}
+.footerBox p {
+	font-style:italic;
+	padding:0 0px 10px 0px;
+}
+.clearfloat { 
+	clear:both;
+	height:0;
+	font-size: 1px;
+	line-height: 0px;
+}
+
+  
+/**************************/
+/*********************************Begin main menu****************************************/
+/**************************/
+nav {
+	float:right;
+	padding:40px 0 0 0;
+}
+ul#navlist {
+	margin-left: 0;
+	padding-left: 0;
+	white-space: nowrap;
+}
+#navlist li {
+	display: inline;
+	list-style-type: none;
+}
+#navlist a { 
+	padding: 3px 10px; 
+	font-family: 'Lobster', sans-serif;
+	font-size: 36px;
+	color: #677a83;
+}
+#navlist a:link, #navlist a:visited {
+	color: #677a83;
+	text-decoration: none;
+}
+#navlist a:hover {
+	color: #8dc35a;
+	text-decoration: none;
+}
+#navlist #active a {
+	color: #8dc35a;
+	text-decoration: none;
+}
+/**************************/
+/*********************************End main menu****************************************/
+/**************************/
+/*********************************Begin Media Queries****************************************/
+/**************************/
+/* for 980px or less */
+@media screen and (max-width: 980px) {
+	.wrapper {
+		width: 95%;
+	}
+}
+/* for 768px or less */
+@media screen and (max-width: 768px) {
+	h1, h2, h3 {
+		font-size:24px;
+	}
+	header {
+		padding:20px 50px 0px 50px;
+	}
+	.post {
+		padding:20px 0;
+	}
+	.post header, .post p {
+		width:400px;
+	}
+	article aside {
+		width: 170px;
+	}
+	.footerBox {
+		padding:0 20px;
+	}
+	.footerBox p {
+		padding:0 0 10px 0;
+	}
+	#navlist a { 
+		padding: 3px 10px; 
+		font-size: 24px;
+	}
+}
+/* for 480px or less */
+@media screen and (max-width: 480px) {
+	.wrapper {
+		margin-top:10px;
+	}
+	header {
+		padding:10px 10px 0px 10px;
+	}
+	.logo {
+		float:none;
+		margin:0 auto;
+	}
+	nav {
+		float:none;
+		padding:5px 0 0 0;
+	}
+	#navlist a { 
+		padding: 7px 2px; 
+		font-family: 'Lobster', sans-serif;
+		font-size: 16px;
+		color: #677a83;
+	}
+	section {
+		padding:0 10px;
+	}
+	article aside {
+		display:none;
+	}
+	.post {
+		padding:10px 0;
+		margin:0 10px;
+	}
+	.post header, .post p {
+		width:auto;
+		float:none;
+	}
+	.sectionFooter {
+		padding:10px 0;
+		margin:0 10px;
+	}
+	.footerBox {
+		width:auto;
+		float:none;
+		padding:10px;
+	}
+}
+img {
+	max-width: 100%;
+	height: auto;
+	width: auto;
+}
+/**************************/
+/*********************************End Media Queries****************************************/
+/**************************/

+ 167 - 0
ourbiz/s/ourbiz/docs.css

@@ -0,0 +1,167 @@
+body {
+  font-family: Droid Sans, Arial, sans-serif;
+  line-height: 1.5;
+  max-width: 64.3em;
+  margin: 3em auto;
+  padding: 0 1em;
+}
+
+h1 {
+  letter-spacing: -3px;
+  font-size: 3.23em;
+  font-weight: bold;
+  margin: 0;
+}
+
+h2 {
+  font-size: 1.23em;
+  font-weight: bold;
+  margin: .5em 0;
+  letter-spacing: -1px;
+}
+
+h3 {
+  font-size: 1em;
+  font-weight: bold;
+  margin: .4em 0;
+}
+
+pre {
+  background-color: #eee;
+  -moz-border-radius: 6px;
+  -webkit-border-radius: 6px;
+  border-radius: 6px;
+  padding: 1em;
+}
+
+pre.code {
+  margin: 0 1em;
+}
+
+.grey {
+  background-color: #eee;
+  border-radius: 6px;
+  margin-bottom: 1.65em;
+  margin-top: 0.825em;
+  padding: 0.825em 1.65em;
+  position: relative;
+}
+
+img.logo {
+  position: absolute;
+  right: -1em;
+  bottom: 4px;
+  max-width: 23.6875em; /* Scale image down with text to prevent clipping */
+}
+
+.grey > pre {
+  background:none;
+  border-radius:0;
+  padding:0;
+  margin:0;
+  font-size:2.2em;
+  line-height:1.2em;
+}
+
+a:link, a:visited, .quasilink {
+  color: #df0019;
+  cursor: pointer;
+  text-decoration: none;
+}
+
+a:hover, .quasilink:hover {
+  color: #800004;
+}
+
+h1 a:link, h1 a:visited, h1 a:hover {
+  color: black;
+}
+
+ul {
+  margin: 0;
+  padding-left: 1.2em;
+}
+
+a.download {
+  color: white;
+  background-color: #df0019;
+  width: 100%;
+  display: block;
+  text-align: center;
+  font-size: 1.23em;
+  font-weight: bold;
+  text-decoration: none;
+  -moz-border-radius: 6px;
+  -webkit-border-radius: 6px;
+  border-radius: 6px;
+  padding: .5em 0;
+  margin-bottom: 1em;
+}
+
+a.download:hover {
+  background-color: #bb0010;
+}
+
+.rel {
+  margin-bottom: 0;
+}
+
+.rel-note {
+  color: #777;
+  font-size: .9em;
+  margin-top: .1em;
+}
+
+.logo-braces {
+  color: #df0019;
+  position: relative;
+  top: -4px;
+}
+
+.blk {
+  float: left;
+}
+
+.left {
+  margin-right: 20.68em;
+  max-width: 37em;
+  padding-right: 6.53em;
+  padding-bottom: 1em;
+}
+
+.left1 {
+  width: 15.24em;
+  padding-right: 6.45em;
+}
+
+.left2 {
+  max-width: 15.24em;
+}
+
+.right {
+  width: 20.68em;
+  margin-left: -20.68em;
+}
+
+.leftbig {
+  width: 42.44em;
+  padding-right: 6.53em;
+}
+
+.rightsmall {
+  width: 15.24em;
+}
+
+.clear:after {
+  visibility: hidden;
+  display: block;
+  font-size: 0;
+  content: " ";
+  clear: both;
+  height: 0;
+}
+.clear { display: inline-block; }
+/* start commented backslash hack \*/
+* html .clear { height: 1%; }
+.clear { display: block; }
+/* close commented backslash hack */

+ 1 - 0
ourbiz/sq-server.nut

@@ -57,6 +57,7 @@ local mongoose_start_params = {
 	},
 	},
 	user_callback = function(event, request){
 	user_callback = function(event, request){
 		if(event == "MG_NEW_REQUEST"){
 		if(event == "MG_NEW_REQUEST"){
+			//debug_print("\n", request.get_option("num_threads"), request.get_conn_buf());
 			if(AT_DEV_DBG || !this.get("handle_request", false)) {
 			if(AT_DEV_DBG || !this.get("handle_request", false)) {
 				loadfile(APP_CODE_FOLDER + "/sq-server-plugin.nut")();
 				loadfile(APP_CODE_FOLDER + "/sq-server-plugin.nut")();
 			}
 			}