Harbour as CGI (Was: LETODBF comms with php)

837 views
Skip to first unread message

Mario Lobo

unread,
Feb 6, 2018, 3:05:59 PM2/6/18
to Harbour Users
Hi;

Moving forward with Harbour as a CGI app.

I tested the following which worked!

cgi2.prg
function main(x)

   OutStd("Content-type: text/html" + hb_eol() + hb_eol())
   OutStd("<html><body><h1>It works! </h1>" + hb_eol()+ hb_eol())
   OutStd("user: " + x + "</body></html>" + hb_eol())
  
return nil
 
hbmk2 -gtcgi  cgi2.prg

http://10.3.51.45/cgi-bin/cgi2?mario

It outputs:

It works!

User:mario



Now I am attempting this:

cgi.prg
function main(x,y)

   OutStd("Content-type: text/html" + hb_eol() + hb_eol())
   OutStd("<html><body><h1>It works! </h1>" + hb_eol()+ hb_eol())
   OutStd("User: " + x + " test: " + y + "</body></html>" + hb_eol())
  
return nil
 
http://10.3.51.45/cgi-bin/cgi2?mario&teste

It outputs:

It works!

Error BASE/1081 Argument error: + (Quit)



How can I pass more than one parameter to the cgi?

This is fundamental to work with forms, GET and POST, where the above test url would be more like

http://10.3.51.45/cgi-bin/cgi2?User=mario&pass=teste

Thanks for any suggestion,

Mario

Itamar Lins

unread,
Feb 6, 2018, 3:21:38 PM2/6/18
to Harbour Users
Hi!
Using apache, I was able to make it work with the shebang file .prg and calling hbrun.exe

And I was also able to make it work by using the hbmk2 -gh directive
But I lose the configuration of httpd.conf alias I do not even know what was the formula I used to run the .hrb file. Can you help me in this matter?

Best regards,
Itamar M. Lins Jr.

Miroslav Georgiev

unread,
Feb 6, 2018, 3:39:31 PM2/6/18
to Harbour Users
Nice question. 
I'd like to add one more - please if anybody know how to implement FastCGI or similar to post an examples or howto.
Pure CGI is slow because executable has to be launched for every request.

elch

unread,
Feb 7, 2018, 12:43:24 PM2/7/18
to Harbour Users
Hi Mario,

out of the blue !,
and you probably found it out meanwhile yourself.

Now I am attempting this:

cgi.prg
function main(x,y)

   OutStd("Content-type: text/html" + hb_eol() + hb_eol())
   OutStd("<html><body><h1>It works! </h1>" + hb_eol()+ hb_eol())
   OutStd("User: " + x + " test: " + y + "</body></html>" + hb_eol())
  
return nil
 
http://10.3.51.45/cgi-bin/cgi2?mario&teste

It outputs:

It works!

Error BASE/1081 Argument error: + (Quit)

If an executable is called with multiple param,
the <shell> is responsible with 'dividing' the params.

Example, a call:
myexe par1 par2 par3
then there is a blank/ 'white char' in between causing that.
This is sometimes even unwanted, then the exe have to concatenate them.

Your case seems vice versa,
all param[s] seem in 'x',
and you have to split them by delimiter '&'.
Multiple ways lead to Rome, AT() is a surely known,
but there are more ways ...

best regards
Rolf

Mario Lobo

unread,
Feb 7, 2018, 2:36:53 PM2/7/18
to Harbour Users
Hi Rolf !

Thanks for digging in with me. The problem is that it is NOT the shell that determines  the parameter format but Apache!
And there is an extra trap. The format varies according to which method (GET/POST) is used by the HTML form on the page.

Lets say I have a login page like this:

User:___________________
Password:_______________

                      [SUBMIT]

If I use the GET method, AFTER I click the SUBMIT button, my URL will look like this on the address bar:

http://10.3.51.45/cgi-bin/cgi2?User=USERNAME&password=PASSWORD

which is obviously no good...


If I use the POST method, AFTER I click the SUBMIT button, my URL will look like this on the address bar:

http://10.3.51.45/cgi-bin/cgi2


The problem is that the GET method puts "User=USERNAME&password=PASSWORD" into the command line and calls the shell for cgi.exe with this whole string as a parameter.
The shell doesn't work well with "=" amidst its chars so it doesn't pass anything to the "x' of .PRG function main(x), and the cgi exe fails.

POST, on the other hand, does not populate QUERY_STRING but it DOES fill the stdin buffer with ""User=USERNAME&password=PASSWORD".

So my solution was to implement a C function that reads the stdin buffer and passes it back to Harbour for processing (break it up using "=" and "&" as markers).

Now it works as expected!

It works!

Param: User=USERNAME&password=PASSWORD

Here is how to implement it:

CGI.PRG

function main()

   // pre-allocate the buffer

   local Query := Replicate( " ", 255 )
  
   collect(Query)

  
   OutStd("Content-type: text/html" + hb_eol() + hb_eol())
   OutStd("<html><body><h1>It works! </h1>" + hb_eol()+ hb_eol())
   OutStd("Param: " + Query + "</body></html>" + hb_eol())
  
return nil

COLLECT.C

#include <stdio.h>
#include <stdlib.h>
#include "hbapi.h"

#define MAXLEN 255

HB_FUNC(COLLECT) {

   char *lenstr;
   char *query;
   long len;
  
   // get the pre-allocated string buffer pointer
   query = hb_parvcx(1); 
 
   lenstr = getenv("CONTENT_LENGTH");
   if(lenstr == NULL || sscanf(lenstr,"%ld",&len)!=1 || len > MAXLEN)
       printf("<P>Error in invocation - wrong FORM probably.");
   else
       fgets(query, len+1, stdin);
  
   // return the modified buffer to harbour
   hb_retc(query);  
}

hbmk2 -gtcgi  cgi2.prg collect.c


INDEX.HTML

<form action="/cgi-bin/cgi2" id="UserLogonForm" method="post" accept-charset="utf-8">
               <div class="input user">
                           <label for="User">Usuário</label>
                            <input name="User" maxlength="255" type="user" id="User"/>
               </div>
               <div class="input password">
                         <label for="UserPassword">Senha</label>
                         <input name="password" type="password" id="Password"/>
               </div>
               <div class="submit">
                        <input type="submit" value="Entrar"/>
               </div>
  </form>

I'll keep posting as I move on!

Mario

Itamar Lins

unread,
Feb 7, 2018, 5:51:45 PM2/7/18
to Harbour Users
Hi Mario!

See this.

function main(...)
Local  aParams := hb_aParams(),n:=0

 
   OutStd("Content-type: text/html" + hb_eol() + hb_eol())
   OutStd("<html><body><h1>It works! </h1>" + hb_eol()+ hb_eol())
   OutStd("Len array of parameters: " , str( len( aParams ) )  + hb_eol()+ hb_eol())

do while n++ < len( aParams )
   OutStd( "<p> Parameters: ", aParams[n] , "</p>")
enddo
   OutStd("</body></html>" + hb_eol())
  
Return nil

Works fine!


Best regards,
Itamar M. Lins Jr.

Itamar Lins

unread,
Feb 7, 2018, 5:54:44 PM2/7/18
to Harbour Users
Hi!
Screen of apache server.

http://localhost/cgi-bin/teste.exe?a+b+c+d+e

It works!

Len array of parameters: 5

Parameters: a

Parameters: b

Parameters: c

Parameters: d

Parameters: e


Best regards,
Itamar M. Lins Jr.

Mario Lobo

unread,
Feb 7, 2018, 6:00:57 PM2/7/18
to Harbour Users
Wow! Great Itamar!

Did you use <form> on the .HTML? Which method (GET/POST)?

Mario

Mario Lobo

unread,
Feb 7, 2018, 6:12:22 PM2/7/18
to Harbour Users
Ill test it tomorrow but it must work with POST method.

Mario

Itamar Lins

unread,
Feb 7, 2018, 6:25:13 PM2/7/18
to Harbour Users

Hi!


It works!

Len array of parameters: 5

Parameters: a

Parameters: b

Parameters: c

Parameters: d

Parameters: e

Method: GET

Accept lang: pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3

user agent: Mozilla/5.0 (Windows NT 6.1; rv:58.0) Gecko/20100101 Firefox/58.0

Connection: keep-alive

Apache ver: Apache/2.2.21 (Win32)


function main(...)
Local  aParams := hb_aParams(),n:=0
 
   OutStd("Content-type: text/html" + hb_eol() + hb_eol())
   OutStd("<html><body><h1>It works! </h1>" + hb_eol()+ hb_eol())
   OutStd("Len array of parameters: " , str( len( aParams ) )  + hb_eol()+ hb_eol())

do while n++ < len( aParams )
   OutStd( "<p> Parameters: ", aParams[n] , "</p>")
enddo

   OutStd("<p>Method: " , hb_getenv("REQUEST_METHOD") + hb_eol())
   OutStd("<p>Accept lang: " , hb_getenv("HTTP_ACCEPT_LANGUAGE") + hb_eol())
   OutStd("<p>user agent: " , hb_getenv("HTTP_USER_AGENT") + hb_eol())
   OutStd("<p>Connection: " , hb_getenv("HTTP_CONNECTION") + hb_eol())
   OutStd("<p>Apache ver: " , hb_getenv("SERVER_SOFTWARE") + hb_eol())



   OutStd("</body></html>" + hb_eol())


Return nil

Best regards,

Itamar M. Lins Jr.



Itamar Lins

unread,
Feb 7, 2018, 8:26:48 PM2/7/18
to Harbour Users
Hi!
<form action="/cgi-bin/teste.prg" or <form action="/cgi-bin/teste.exe"  works! with harbour 3.4
For post method is necessary  hb_GetStdIn() function see below.

<start test.prg>
#!"c:\hb34\bin\hbrun.exe"
proc main(...)

Local aParams := hb_aParams(),n:=0
local nLen, cTemp, nRead

 
   OutStd("Content-type: text/html" + hb_eol() + hb_eol())
   OutStd("<html><body><h1>It works! </h1>" + hb_eol()+ hb_eol())
   OutStd("Len array of parameters: " , str( len( aParams ) )  + hb_eol()+ hb_eol())

do while n++ < len( aParams )
   OutStd( "<p> Parameters: ", aParams[n] , "</p>")
enddo
   OutStd("<p>Method: " , hb_getenv("REQUEST_METHOD") + hb_eol())
   OutStd("<p>Accept lang: " , hb_getenv("HTTP_ACCEPT_LANGUAGE") + hb_eol())
   OutStd("<p>user agent: " , hb_getenv("HTTP_USER_AGENT") + hb_eol())
   OutStd("<p>Connection: " , hb_getenv("HTTP_CONNECTION") + hb_eol())
   OutStd("<p>Apache ver: " , hb_getenv("SERVER_SOFTWARE") + hb_eol())
   OutStd("<p>QUERY_STRING: " , hb_getenv("QUERY_STRING") + hb_eol())
   OutStd("<p>Content Type: " , hb_getenv("CONTENT_TYPE") + hb_eol())
   OutStd("<p>Content len: " , hb_getenv("CONTENT_LENGTH") + hb_eol())
   OutStd("<p>Script FileName: " , hb_getenv("SCRIPT_FILENAME") + hb_eol())
   OutStd("<p>Script Name: " , hb_getenv("SCRIPT_NAME") + hb_eol())
   OutStd("<p>Request URI: " , hb_getenv("REQUEST_URI") + hb_eol())

   IF "POST" $ Upper( hb_GetEnv( "REQUEST_METHOD" ) )
      nLen := val(hb_getenv("CONTENT_LENGTH"))
      cTemp := Space( nLen )
      IF ( nRead := FRead( hb_GetStdIn(), @cTemp, nLen ) ) != nLen
    OutStd("<p>Post error read " + hb_ntos( nRead ) + " instead of " + hb_ntos( nLen )  + hb_eol() )    
      ELSE
        OutStd("<p>Post string is: " , cTemp + hb_eol())      
      ENDIF

   ENDIF


   OutStd("</body></html>" + hb_eol())

</end test.prg>
Message has been deleted

elch

unread,
Feb 8, 2018, 4:41:43 AM2/8/18
to Harbour Users
Hi Mario,

function main()


   local Query := Replicate( " ", 255 )
   collect(Query)
   
return nil

...
HB_FUNC(COLLECT) {

   char *query;

   
   // get the pre-allocated string buffer pointer
   query = hb_parvcx(1);  
 ...

i wonder that this compile,
because hb_parvcx() returns a: _const_ char *

You should in general strong avoid to *directly* modify Harbour strings.

---
Good would be:
collect(@Query) /* transfer by reference */

if you need the content to modify:
char *query = hb_strdup( hb_parc( 1 ) )
--> you get an hb_xgrab() allocated ! buffer of: hb_parclen( 1 ) + 1 length,
which must be hb_xfree() again -- or see below (**)

and then return content with:
if( HB_ISBYREF( 1 ) )  /* not really needed, as verified also by internal ITEM-API */

hb_storc( query, 1 ) /* looks for terminating '\0' -- non hb_xfree() */
hb_storclen( query, nShortenToWant, 1 )  /* no hb:xfree() */
(**) hb_storclen_buffer( query, nShortenToWant, 1 )  /* includes an hb_xfree(query) */

---
as ultra-short intro for Harbour ITEM-API.
general: be careful if you see an 'const char *' in the prototypes as param or return value.

best regards
Rolf

Mario Lobo

unread,
Feb 8, 2018, 11:43:39 AM2/8/18
to Harbour Users
This worked perfectly, Itamar! I had no idea there was a hb_GetStdIn() function. Thank you!

Rolf, the code that I posted compiled and worked just like Itamar's code bellow. Although I received a warning from the compiler about
char/ cont char conversion. But in the end it worked fine.

Since Itamar's solution is much more what I was looking for, I'll stick to it.

Again, thanks to you both!

Mario

Mario Lobo

unread,
Feb 8, 2018, 1:25:43 PM2/8/18
to Harbour Users
Rolf,

For the record, I applied your changes and they worked also.

Thanks,

elch

unread,
Feb 8, 2018, 2:04:01 PM2/8/18
to Harbour Users
Fine!, Mario,

 
For the record, I applied your changes and they worked also.
 
and FYI:
variables at PRG level are pointers! to a *struct*: HB_ITEM,
with additional internal info about type, length.
Not only 'char *' allocated buffer, we know from C-level.
With a 'garbage collecting' system around ...

Making an error at this level, i described to avoid, lead to Harbour crash
-- often not immediately, then 'nice' to search for ...

best regards
Rolf

Pete

unread,
Feb 9, 2018, 4:21:57 AM2/9/18
to Harbour Users


On Thursday, 8 February 2018 11:41:43 UTC+2, elch wrote:
hb_storclen( query, nShortenToWant, 1 )  /* no hb:xfree() */
(**) hb_storclen_buffer( query, nShortenToWant, 1 )  /* includes an hb_xfree(query) */

---
as ultra-short intro for Harbour ITEM-API.
general: be careful if you see an 'const char *' in the prototypes as param or return value.

best regards
Rolf


(by an educative/learning point of view) above remarks was very useful! (or might I say "lesson"? ;-> ). thanks!
I whish we had such an even "ultra-short" guide for harbour internals (extend API).

regards,
Pete


Amos

unread,
Feb 10, 2018, 1:51:57 AM2/10/18
to Harbour Users
I use the CGI class from the HBTIP contrib to get the variables in my CGI programs.

Regards
Amos

Mario Lobo

unread,
Feb 10, 2018, 9:18:43 AM2/10/18
to Harbour Users
Hi Amos.

I must say that it is not very encouraging when warning.txt inside hbtip states:

"Due to the excessive amount of problem reports and long known (and unfixed)
problems and due to the non-trivial nature of internet protocols, this fork
_strongly recommends to avoid_ using this library in anything production or
in fact anything more serious than simple test code for educational purposes.
Please notice that even if something happens to work in some specific
scenario, it's highly likely it's not a stable solution, nor is it a secure
solution."

In any case, do you have any howto on it? or you just went through the examples to find your way?
Any code of your own you could share?

thanks,

Mario

Eric Lendvai

unread,
Feb 12, 2018, 1:09:25 AM2/12/18
to Harbour Users
The warning text is one of the reasons we should create a fast-cgi interface. I posted an entry on this group for this. The more people view that post or comment on it, the higher chance we will have that someone can work done on this. Almost all other mother languages use Fast-CGI instead of CGI at this point.

Thanks, Eric

Amos

unread,
Feb 13, 2018, 1:44:38 PM2/13/18
to Harbour Users
Hi Mario,

The warning has been added a couple of months ago. I do not know if there have been any problems with the CGI part of this lib. I have had none. 

Picking up on your original post, your PRG code would like this:

oCGI := TIPCgi():New()
oCgi:Header("Content-Type: text/html")
oCGI:Write("<html><body><h1>It works! </h1>")
oCGI:Write("User: " + oCGI:hGets['User'] + " test: " + oCGI:hGets['pass'] + "</body></html>")
oCGI:Flush()

Regards
Amos 

Mario Lobo

unread,
Feb 16, 2018, 2:07:49 PM2/16/18
to Harbour Users
Amos, first thanks for the sample. I tried it and here is the result:

** cgi4.prg

// compiled with: hbmk2 -gtcgi hbtip.hbc /Dbase/projects/cgi/cgi4.prg

Function main

oCGI := TIPCgi():New()
oCgi:Header()

oCGI:Write("<html><body><h1>It works! </h1>")
oCGI:Write("User: " + oCGI:hGets['User'] + " test: " + oCGI:hGets['Password'])    <= line 8
oCGI:EndHtml()
oCGI:Flush()

return nil

No errors in compilation!

** Form used on index.html

<form action="/cgi-bin/cgi4" id="UserLogonForm" method="post" accept-charset="utf-8">

                           <div class="input user">
                                <label for="User">Usuário</label>
                                <input name="User" maxlength="255" type="user" id="User"/>
                           </div>
                           <div class="input password">
                                <label for="UserPassword">Senha</label>
                                <input name="Password" type="password" id="Password"/>

                           </div>
                           <div class="submit">
                                <input type="submit" value="Entrar"/>
                           </div>
</form>

And here is the response on the browser:

It works!

SCRIPT NAME:/cgi-bin/cgi4
CRITICAL ERROR:Bound error
OPERATION:array access
OS ERROR:0 IN BASE/1132
FILENAME:
PROC/LINE:(b)TIPCGI_NEW/99
PROC/LINE:MAIN/8


And this is line 99 of hbtip/cgi.prg

METHOD New() CLASS TIPCgi

   LOCAL aVar
   LOCAL nLen
   LOCAL nRead
   LOCAL cTemp
   LOCAL item

   ::bSavedErrHandler := ErrorBlock( {| e | ::ErrHandler( e ) } )    <= line 99

Are the hGets[] subscripts really initialized with the form's ids?

Mario

Mel Smith

unread,
Feb 16, 2018, 5:23:25 PM2/16/18
to Harbour Users
Hi Mario:

   I think it would be instructive for you to dump out your HTML page to a file (e.g., lobo.htm) and then pass it to a validation service that checks for errors in your html code.

   I use a service called https://validator.w3.org  --- altho there are several other groups to choose from.

   For example, I note that you use an <input ... >  option with type="user".  I don't think there is any such option. I presume you mean : type="text"  

-Mel

Mario Lobo

unread,
Feb 17, 2018, 11:02:23 AM2/17/18
to Harbour Users
Mel;

I don't believe that's the problem.

When I process the post string myself, in previous attempts, the string comes correctly from the  browser/html form.

The problem is inside the hbtip parsing of the post string. It is an array bound error.


Mario

Domenico D'Oria

unread,
Apr 6, 2021, 7:47:41 AM4/6/21
to Harbour Users
Hi Mario

probably you have just found where is the problem, in your code you use hgets, but the form is using post, so you have to use hposts

regards

Domenico
Reply all
Reply to author
Forward
0 new messages