PB native CGI more complete solution

Share your advanced PureBasic knowledge/code with the community.
Pantcho!!
Enthusiast
Enthusiast
Posts: 538
Joined: Tue Feb 24, 2004 3:43 am
Location: Israel
Contact:

PB native CGI more complete solution

Post by Pantcho!! »

Hi all,

Maybe useful to someone

Code: Select all

; CGI using native PB
;---------------------------------
; Tested on Windows with SAMBAR server
; Tested on linux hosting with apache
;---------------------------------
; This code is inspired and big parts of it using rings code here:
; http://www.purebasic.fr/english/viewtopic.php?p=174680#174680
; 
; Also it seems the strings in PB can't include NULL chars so i used pdwyer inMem function from here:
; http://www.purebasic.fr/english/viewtopic.php?p=201392#201392
;
; The diffrence between this code and Rings is that:
; - GET was not supported - Now it is
; - POST allowed only 1 file upload - Now unlimited files
; - POST with only 1 file did not allowed variables - Now unlimited variables + files in the same POST
; 
; I did the code to myself since it can be hosted in linux hosting, but i decided to post it in the forums
; Maybe someone could make use of it and maybe improve it
; 
; By no means i am held responsible for any server/client data loss etc, use it on your own risk(!)
; 
; Any suggestions and comments are welcome tough :D
;---------------------------------------------------------------------------------------------------------

;{ Header Variables

Structure CGI_Var
  Name.s
  Value.s
  Filename.s ; wont be needed unless multipart
  CType.s ; same as above
EndStructure 

Global NewList  FormVars.CGI_Var()

#TEXTPLAIN = 1
#MULTIPART = 2
#POST = 1
#GET = 2
Global *Buffer,ContentLength,DATA_TYPE,DATA_METHOD,MP_BOUNDARY.s,EOL$,DBLEOL$
EOL$ = Chr(13)+Chr(10) ;/ Note #CRLF$ = EOL$ , i did that because of pointer memory search
DBLEOL$ = EOL$+EOL$

;/ This is the top header before we post back to the browser, for example if you want to send out
;/ a jpeg file use
;/ OutputContentType.s="Content-type: image/jpeg" + #CRLF$ + CRLF$ + JPEGFILE$

OutputContentType.s="Content-type: text/html"
OutputContentType.s="Content-type: text/html;charset=UTF-8"
AnswerTitle.s="Page Title"

;/ If you are using linux i suggest to use a "../tempfolder/"
;Folder.s="../upload/"; "C:\" or "../upload/" ; 
Folder.s = "" ; this will save the file where the cgi-bin directory runs
DebugInput=1   

;}

;{ // Enviroment Variables
Global CGI_AUTH_TYPE.s
Global CGI_CONTENT_LENGTH.s
Global CGI_CONTENT_TYPE.s
Global CGI_DOCUMENT_ROOT.s
Global CGI_GATEWAY_INTERFACE.s
Global CGI_PATH_INFO.s
Global CGI_PATH_TRANSLATED.s
Global CGI_QUERY_STRING.s
Global CGI_REMOTE_ADDR.s
Global CGI_REMOTE_HOST.s
Global CGI_REMOTE_IDENT.s
Global CGI_REMOTE_PORT.s
Global CGI_REMOTE_USER.s
Global CGI_REQUEST_URI.s
Global CGI_REQUEST_METHOD.s
Global CGI_SCRIPT_NAME.s
Global CGI_SCRIPT_FILENAME.s
Global CGI_SERVER_ADMIN.s
Global CGI_SERVER_NAME.s
Global CGI_SERVER_PORT.s
Global CGI_SERVER_PROTOCOL.s
Global CGI_SERVER_SIGNATURE.s
Global CGI_SERVER_SOFTWARE.s
Global CGI_HTTP_ACCEPT.s
Global CGI_HTTP_ACCEPT_ENCODING.s
Global CGI_HTTP_ACCEPT_LANGUAGE.s
Global CGI_HTTP_COOKIE.s
Global CGI_HTTP_FORWARDED.s
Global CGI_HTTP_HOST.s
Global CGI_HTTP_PRAGMA.s
Global CGI_HTTP_REFERER.s
Global CGI_HTTP_USER_AGENT.s
;}

Procedure.l InMem(StartPos.l, *MainMem, MainLen.l, *FindMem, FindLen.l)
  
  If StartPos < 1 : StartPos = 1 : EndIf
  
  FoundPos.l = 0
  
  For MainArrayLoop.l = StartPos - 1 To MainLen -1
    
    If MainArrayLoop + FindLen = MainLen
      ;End reached
      Break
    EndIf
    
    If CompareMemory(*MainMem + MainArrayLoop, *FindMem, FindLen) = 1
      FoundPos = MainArrayLoop + 1
      Break
    EndIf   
    
  Next
  
  ProcedureReturn FoundPos
  
EndProcedure

Procedure SaveFileValue(BufferAndLen$,FullFileName$)
  FF=CreateFile(#PB_Any,FullFileName$)
  If FF
    a = Val(StringField(BufferAndLen$,1,"|"))
    b = Val(StringField(BufferAndLen$,2,"|")) 
    WriteData(FF,*Buffer+a,b)
    CloseFile(FF)
  EndIf
  
EndProcedure

Procedure Debug_Data(folder$)
  FF=CreateFile(#PB_Any,folder$+Str(Date())+"-test.hex") ;/ make the save log dynamic by date
  If FF
    WriteData(FF,*Buffer,ContentLength)
    CloseFile(FF)
  EndIf
EndProcedure

Procedure Get_Form_Vars()
  If DATA_METHOD = #POST 
    
    If DATA_TYPE = #TEXTPLAIN ; it means it is only text with no file attachments simple lines in short
      BufferData$ = PeekS(*Buffer,ContentLength,#PB_UTF8)
      i = 0 
      Repeat
        i+1
        Argument.s=Trim(StringField(BufferData$,i, #CRLF$))
        If Len(Argument)>0 And Argument<>#CRLF$ 
          AddElement(FormVars())
          FormVars()\Name = StringField(Argument,1,"=")
          FormVars()\Value = StringField(Argument,2,"=")      
        EndIf 
      Until Argument.s=""
      
    ElseIf DATA_TYPE = #MULTIPART ; content disposition is found  = FILE(S) + data to be parsed

      DoneMP.l=0
      a.l=1
      ;/ all these vars are for memory search to cut and paste in our linked list 
      MP_B_EOL.s = MP_BOUNDARY + EOL$
      BLen=Len(MP_BOUNDARY)
      B_EOL_Len=Len(MP_B_EOL)
      length=ContentLength
      
      Repeat  
        a = InMem(a,*Buffer,length,@MP_B_EOL,B_EOL_Len) ;/ Searching for Boundary+EOL first time 
        If a
          b = InMem(a+B_EOL_Len,*Buffer,length,@EOL$,2) ;/ Now getting content disposition part info 
          PartInfo$ = PeekS(*Buffer+a+B_EOL_Len-1,b-(a+B_EOL_Len))
          
          If FindString(LCase(PartInfo$),"filename=",1) ; oh its a file ;) 
            AddElement(FormVars())
            ; This is done in a loop to get values from impropar browsers
            ; who gives non valid syntax by values order
            j = 2 ; starting from 2 since 1 is content disposition
            Repeat 
              Tempa$ = Trim(StringField(PartInfo$,j,";"))  
              If Len(Tempa$) > 0
                Tempb$ = StringField(Tempa$,1,"=")          
                Tempc$ = StringField(Tempa$,2,"=")          
                If LCase(Tempb$)="name" 
                  FormVars()\Name = RemoveString(Tempc$,Chr(34)) ; removing the ""
                ElseIf LCase(Tempb$)="filename"
                  FormVars()\Filename = RemoveString(Tempc$,Chr(34)) ; same as above + gives full path name! 
                EndIf 
              EndIf 
              j+1
            Until Tempa$ = ""
            ; Now content type 
            c = InMem(b+1,*Buffer,length,@DBLEOL$,4)
            FormVars()\CType = Trim(  StringField(    PeekS(*Buffer+b+1,c-(b+1))    ,2,":"))    
            ; Now get the file content
            d = InMem(c+4,*Buffer,length,@MP_BOUNDARY,BLen)
            FileLen.l=d-(c+5+1)
            FormVars()\Value = Str(c+3)+"|"+Str(FileLen) ;/ save this in [*Buffer+startpos|length] using SaveFileValue()
            a = d ; to the next file/value          
            
          Else ; its a normal variable to process
            AddElement(FormVars())
            Tempa$ = (StringField(PartInfo$,2,";"))  
            FormVars()\Name = RemoveString(StringField(Tempa$,2,"="),Chr(34))
            c = InMem(b+3,*Buffer,length,@EOL$,2)
            FormVars()\Value = PeekS(*Buffer+b+3,c-(b+3)) 
            a = c
          EndIf 
          
        Else ; could not found boundary to our next element so we are out of here
          DoneMP=1
        EndIf       
      Until DoneMP=1
      
    EndIf   
  ElseIf DATA_METHOD = #GET
    ; No need to check here the DATA_TYPE since get can only get text and no binary data!
    i = 0
    Repeat
      i+1
      Argument.s=Trim(StringField(CGI_QUERY_STRING,i, "&"))
      If Len(Argument)>0
        AddElement(FormVars())
        FormVars()\Name = StringField(Argument,1,"=")
        FormVars()\Value = StringField(Argument,2,"=")      
      EndIf 
    Until Argument.s=""
    ; Thats it! we got all data - BUT a special chars like =&? and more needed to be parsed in the value field
    ;// web escape encoding needed here to be done.
  
  Else
  ; Error! exit with error
  EndIf 

EndProcedure 

Procedure Get_CGI_ENV_Vars()  ; // Init Enviroment Variables 
;     * AUTH_TYPE - Describes the authentication method used by the web browser If any authentication method was used. This is Not set unless the script is Protected.
CGI_AUTH_TYPE  = GetEnvironmentVariable("AUTH_TYPE")   


;     * CONTENT_LENGTH - This is used For scripts that are receiving form Data using the POST method. This variable tells the byte length of the CGI input Data stream. This is required To Read the Data from the standard input With the POST method.
CGI_CONTENT_LENGTH  = GetEnvironmentVariable("CONTENT_LENGTH")   


;     * CONTENT_TYPE - Tells the media type of Data being received from the user. this is used For scripts called using the POST method.
CGI_CONTENT_TYPE  = GetEnvironmentVariable("CONTENT_TYPE")   


;     * DOCUMENT_ROOT - The root path To the home HTML page For the server. Example:
;       /home/httpd/html
CGI_DOCUMENT_ROOT  = GetEnvironmentVariable("DOCUMENT_ROOT")   


;     * GATEWAY_INTERFACE - The version of the common gateway Interface (CGI) specification being used To exchange the Data between the client And server. this is normally CGI/1.1 For the current revision level of 1.1.
;   Example:   CGI/1.1
CGI_GATEWAY_INTERFACE  = GetEnvironmentVariable("GATEWAY_INTERFACE")   


;     * PATH_INFO - Extra path information added To the End of the URL that accessed the server side script program.
CGI_PATH_INFO  = GetEnvironmentVariable("PATH_INFO")   


;     * PATH_TRANSLATED - A translated version of the PATH_INFO variable translated by the webserver from virtual To physical path information.
CGI_PATH_TRANSLATED  = GetEnvironmentVariable("PATH_TRANSLATED")   


;     * QUERY_STRING - This string contains any information at the End of the server side script path that followed a question mark. Used To Return Data If the GET method was used by a form. There are length restrictions To the QUERY_STRING. Example of how To set it in HTML:
;       <A HREF="/cgi-bin/hits.pl?mainpage></A>
;       The information after the ? is the QUERY_STRING which is "mainpage" in this Case. How it looks on the server side
;       mainpage
CGI_QUERY_STRING  = GetEnvironmentVariable("QUERY_STRING")   


;     * REMOTE_ADDR - The IP address of the client computer. Example:
;       132.15.28.124
CGI_REMOTE_ADDR  = GetEnvironmentVariable("REMOTE_ADDR")   


;     * REMOTE_HOST - The fully qualified domain name of the client machine making the HTTP request. It may Not be possible To determine this name since many client computers names are Not recorded in the DNS system. Example:
;       comp11.mycompanyproxy.com
CGI_REMOTE_HOST  = GetEnvironmentVariable("REMOTE_HOST")   


;     * REMOTE_IDENT - The ability To use this variable is limited To servers that support RFC 931. This variable may contain the client machine's username, but it is intended to be used for logging purposes only, when it is available.
CGI_REMOTE_IDENT  = GetEnvironmentVariable("REMOTE_IDENT")   


;     * REMOTE_PORT - The clients requesting port. An example:
;       3465
CGI_REMOTE_PORT  = GetEnvironmentVariable("REMOTE_PORT")   


;     * REMOTE_USER - If the CGI script was Protected And the user had To be logged in To get access To the script, this value will contain the user's log in name
CGI_REMOTE_USER  = GetEnvironmentVariable("REMOTE_USER")   


;     * REQUEST_URI - The path To the requested file by the client. An example:
;       /cgi-bin/join.pl?button=on
CGI_REQUEST_URI  = GetEnvironmentVariable("REQUEST_URI")   


;     * REQUEST_METHOD - This describes the request method used by the browser which is normally GET, POST, Or HEAD.
CGI_REQUEST_METHOD  = GetEnvironmentVariable("REQUEST_METHOD")   


;     * SCRIPT_NAME - The virtual path of the CGI script being executed. Example:
;       /cgi-bin/join.pl
CGI_SCRIPT_NAME  = GetEnvironmentVariable("SCRIPT_NAME")   


;     * SCRIPT_FILENAME - Example:
;       /home/httpd/cgi-bin/join.pl
CGI_SCRIPT_FILENAME  = GetEnvironmentVariable("SCRIPT_FILENAME")   

;     * SERVER_ADMIN - The e-mail address of the server administrator. Example:
;       webadmin@myhost.mycompany.org
CGI_SERVER_ADMIN  = GetEnvironmentVariable("SERVER_ADMIN")   


;     * SERVER_NAME - The server hostname, IP address Or DNS alias name shown As a self referencing URL. This does Not include the protocol identifier such As "HTTP:", the machine name, Or port number. Example:
;       myhost
CGI_SERVER_NAME  = GetEnvironmentVariable("SERVER_NAME")   


;     * SERVER_PORT - The port number the HTTP requests And responses are being sent on. Example:
;       80
CGI_SERVER_PORT  = GetEnvironmentVariable("SERVER_PORT")   


;     * SERVER_PROTOCOL - This value is normally HTTP which describes the protocol being used between the client And server computers. Example:
;       HTTP/1.1
CGI_SERVER_PROTOCOL  = GetEnvironmentVariable("SERVER_PROTOCOL")   


;     * SERVER_SIGNATURE - Server information specifying the name And version of the web server And the port being serviced. Example:
;       Apache/1.3.12 Server at mdct-dev3 Port 80
CGI_SERVER_SIGNATURE  = GetEnvironmentVariable("SERVER_SIGNATURE")   


;     * SERVER_SOFTWARE - The name And version of the web server. Example:
;       Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
CGI_SERVER_SOFTWARE  = GetEnvironmentVariable("SERVER_SOFTWARE")     


;     * HTTP_ACCEPT - The media types of Data that the client browser can accept. these Data types are separated by commas. An example:
;       image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, */*
CGI_HTTP_ACCEPT  = GetEnvironmentVariable("HTTP_ACCEPT")   


;     * HTTP_ACCEPT_ENCODING An example:
;       gzip, deflate
CGI_HTTP_ACCEPT_ENCODING  = GetEnvironmentVariable("HTTP_ACCEPT_ENCODING")   


;     * HTTP_ACCEPT_LANGUAGE - The language the client browser accepts. Example:
;       en-us
CGI_HTTP_ACCEPT_LANGUAGE  = GetEnvironmentVariable("HTTP_ACCEPT_LANGUAGE")   


;     * HTTP_COOKIE - Used As an environment variable that contains cookies associated With the server domain from the browser.
CGI_HTTP_COOKIE  = GetEnvironmentVariable("HTTP_COOKIE")   


;     * HTTP_FORWARDED An example:
;       by http://proxy-nt1.yourcompany.org:8080 (Netscape-Proxy/3.5)
CGI_HTTP_FORWARDED  = GetEnvironmentVariable("HTTP_FORWARDED")   


;     * HTTP_HOST An example:
;       yourwebhost.yourcompany.org
CGI_HTTP_HOST  = GetEnvironmentVariable("HTTP_HOST")   


;     * HTTP_PRAGMA - An example:
;       No-Cache
CGI_HTTP_PRAGMA  = GetEnvironmentVariable("HTTP_PRAGMA")   


;     * HTTP_REFERER - The page address where the HTTP request originated. An example:
;       http://ctdp.tripod.com/independent/web/cgi/cgimanual/index.html
CGI_HTTP_REFERER  = GetEnvironmentVariable("HTTP_REFERER")   


;     * HTTP_USER_AGENT - The name of the client web browser being used To make the request. Example:
;       Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)
CGI_HTTP_USER_AGENT  = GetEnvironmentVariable("HTTP_USER_AGENT")

EndProcedure

Procedure Init_CGI_Data() ;// Must come first
  OpenConsole() ;Must come before Any console stuff
  EnableGraphicalConsole(0) ;set for redirecting also text mode only
  Get_CGI_ENV_Vars(); init Env Vars

  ContentLength = Val(CGI_CONTENT_LENGTH) ;length of read data
  *Buffer = AllocateMemory(ContentLength)   ;We read the buffer now
  Result= ReadConsoleData(*Buffer, ContentLength)
  
  If LCase(CGI_CONTENT_TYPE)="text/plain"
    DATA_TYPE = #TEXTPLAIN
  ElseIf Left(LCase(CGI_CONTENT_TYPE),Len("multipart/form-data"))="multipart/form-data"
    DATA_TYPE = #MULTIPART ;// In multipart we have BOUNDARY signatures to seperate each file/value
    MP_BOUNDARY="--"+StringField(CGI_CONTENT_TYPE,2,"=") ; adding -- to start of signature
  EndIf 
  
  If LCase(CGI_REQUEST_METHOD)="post" : DATA_METHOD = #POST : EndIf
  If LCase(CGI_REQUEST_METHOD)="get" : DATA_METHOD = #GET : EndIf
  
  Get_Form_Vars()
EndProcedure

;//----------------------
; Start the program here
Init_CGI_Data()
;//----------------------

;Debug Content If flagged
If DebugInput : Debug_Data(Folder) : EndIf

Info$ =""

ForEach FormVars()
  objline$ = FormVars()\Name
  If FormVars()\Filename = "" 
    objline$ + " ["+FormVars()\Value+"]<br>" 
  Else
    ; its a file so lets save it
    fn$ = Folder + GetFilePart(FormVars()\Filename)
    SaveFileValue(FormVars()\Value,fn$)
    objline$ + " [file '" + fn$ + "' saved]<br>"
  EndIf
  
  Info$ + objline$ 
Next


HttpAnswer$ = OutputContentType.s + #CRLF$ + #CRLF$
HttpAnswer$ + "<html>"
HttpAnswer$ + "<header>"
HttpAnswer$ + "<title>" + AnswerTitle.s + "</title>"
HttpAnswer$ + "</header>"
HttpAnswer$ + "<body>"
HttpAnswer$ + "Hi, received your Data:<br>"

;we wanna show the cgi_Vars
Info$ + "<br>"
Info$ +  "CGI_AUTH_TYPE=" + CGI_AUTH_TYPE + "<br>"
Info$ +  "CGI_CONTENT_LENGTH=" + CGI_CONTENT_LENGTH + "<br>"
Info$ +  "CGI_CONTENT_TYPE=" + CGI_CONTENT_TYPE + "<br>"
Info$ +  "CGI_DOCUMENT_ROOT=" + CGI_DOCUMENT_ROOT + "<br>"
Info$ +  "CGI_GATEWAY_INTERFACE=" + CGI_GATEWAY_INTERFACE + "<br>"
Info$ +  "CGI_PATH_INFO=" + CGI_PATH_INFO + "<br>"
Info$ +  "CGI_PATH_TRANSLATED=" + CGI_PATH_TRANSLATED + "<br>"
Info$ +  "CGI_QUERY_STRING=" + CGI_QUERY_STRING + "<br>"
Info$ +  "CGI_REMOTE_ADDR=" + CGI_REMOTE_ADDR + "<br>"
Info$ +  "CGI_REMOTE_HOST=" + CGI_REMOTE_HOST + "<br>"
Info$ +  "CGI_REMOTE_IDENT=" + CGI_REMOTE_IDENT + "<br>"
Info$ +  "CGI_CGI_REMOTE_PORT=" + CGI_REMOTE_PORT + "<br>"
Info$ +  "CGI_REMOTE_USER=" + CGI_REMOTE_USER + "<br>"
Info$ +  "CGI_REQUEST_URI=" + CGI_REQUEST_URI + "<br>"
Info$ +  "CGI_REQUEST_METHOD=" + CGI_REQUEST_METHOD + "<br>"
Info$ +  "CGI_SCRIPT_NAME=" + CGI_SCRIPT_NAME + "<br>"
Info$ +  "CGI_SCRIPT_FILENAME=" + CGI_SCRIPT_FILENAME + "<br>"
Info$ +  "CGI_SERVER_ADMIN=" + CGI_SERVER_ADMIN + "<br>"
Info$ +  "CGI_SERVER_NAME=" + CGI_SERVER_NAME + "<br>"
Info$ +  "CGI_SERVER_PORT=" + CGI_SERVER_PORT + "<br>"
Info$ +  "CGI_SERVER_PROTOCOL=" + CGI_SERVER_PROTOCOL + "<br>"
Info$ +  "CGI_SERVER_SIGNATURE=" + CGI_SERVER_SIGNATURE + "<br>"
Info$ +  "CGI_SERVER_SOFTWARE=" + CGI_SERVER_SOFTWARE + "<br>"
Info$ +  "CGI_HTTP_ACCEPT=" + CGI_HTTP_ACCEPT + "<br>"
Info$ +  "CGI_HTTP_ACCEPT_ENCODING=" + CGI_HTTP_ACCEPT_ENCODING + "<br>"
Info$ +  "CGI_HTTP_ACCEPT_LANGUAGE=" + CGI_HTTP_ACCEPT_LANGUAGE + "<br>"
Info$ +  "CGI_HTTP_COOKIE=" + CGI_HTTP_COOKIE + "<br>"
Info$ +  "CGI_HTTP_FORWARDED=" + CGI_HTTP_FORWARDED + "<br>"
Info$ +  "CGI_HTTP_HOST=" + CGI_HTTP_HOST + "<br>"
Info$ +  "CGI_HTTP_PRAGMA=" + CGI_HTTP_PRAGMA + "<br>"
Info$ +  "CGI_HTTP_REFERER=" + CGI_HTTP_REFERER + "<br>"
Info$ +  "CGI_HTTP_USER_AGENT=" + CGI_HTTP_USER_AGENT + "<br>"     

HttpAnswer$ + Info$

HttpAnswer$ + "</body>"
HttpAnswer$ + "</html>"


FreeMemory(*Buffer);Free Buffer

Written=WriteConsoleData(@HttpAnswer$, Len(HttpAnswer$)) ;write Data To console pipe
The HTML to test it:

Code: Select all

<html><head></head><body><h3>MultiImage Request Form cgitest</h3>
<p>POST test with 2 files and line of text<br>
</p><form action="../cgi-bin/cgitemp" method="post" enctype="multipart/form-data">

<input name="P1_imagefile" size="35" maxlength="50" type="file">
<br>
<input name="P2_imagefile" size="35" maxlength="50" type="file">
<br>
<input name="P3_MyText" value="Enter line of text here" size="35" maxlength="50" type="text"><p></p>

<br>
<p></p>
<p>
<input value="Send the files" type="submit">
</p>
</form>

now a simple POST with 2 lines of text<br>
<form name="textsending_easy" action="../cgi-bin/cgitemp" enctype="text/plain" method="post">

<input name="P1_MyText" value="text1" size="35" maxlength="50" type="text"><br>
<input name="P2_MyText" value="text2" size="35" maxlength="50" type="text"><br>
<p></p>
<p>
<input value="Send the Text" type="submit">
</p>
</form>

now a simple GET with few variables
<form name="Layoutbereich1FORM" action="../cgi-bin/cgitemp" enctype="text/plain" method="get">
using Purebasic<input id="Formularmarkierungsfeld1" name="Purebasic" value="yes" type="checkbox"><br>
using VB<input id="Formularmarkierungsfeld2" name="Visual Basic" value="holla" type="checkbox"><br>
using DotNet<input id="Formularmarkierungsfeld3" name="VB.NET" value="uups" type="checkbox"><br>
Use other stuff<input id="Formularmarkierungsfeld4" name="Other" value="ok" type="checkbox"><br>
your name<input id="Formulareditierfeld5" name="NickName" value="RINGS" size="40" maxlength="40" type="text"><br>

your email<input id="Formulareditierfeld6" name="email-addi" value="karl@nospam.fr" size="43" maxlength="43" type="text"><br>
<input name="FormularHandler1" value="Sending" id="FormularHandler1" type="submit">
</form>

</body></html>
:)
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Post by Kwai chang caine »

Excuse me, but i have don't understand how i must use it.
I have an error block size 0 at the line 393 :cry:
ImageThe happiness is a road...
Not a destination
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

Ouch, nice! I was looking for this, now I just have to understand... :?

How you set thus up, as a backend to Apache or something similar, or?
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
Deluxe0321
User
User
Posts: 69
Joined: Tue Sep 16, 2008 6:11 am
Location: ger

Re: PB native CGI more complete solution

Post by Deluxe0321 »

Hey, since I'm using this in one of my projects, I thought why not give it back to the board.

Edit: Updated

Code: Select all

;pb_cgi.pbi
EnableExplicit
; CGI using native PB
;---------------------------------
; Tested on Windows with xampp (apache x86)
; Tested on linux (debian,ubuntu) hosting with apache x64
;---------------------------------
; This code is inspired and big parts of it using rings code here:
; http://www.purebasic.fr/english/viewtopic.php?p=174680#174680
;
; Also it seems the strings in PB can't include NULL chars so i used pdwyer inMem function from here:
; http://www.purebasic.fr/english/viewtopic.php?p=201392#201392
;
;
; The diffrence between this code and Rings is that:
; - GET was not supported - Now it is
; - POST allowed only 1 file upload - Now unlimited files
; - POST with only 1 file did not allowed variables - Now unlimited variables + files in the same POST
;
; I did the code to myself since it can be hosted in linux hosting, but i decided to post it in the forums
; Maybe someone could make use of it and maybe improve it
;
; By no means i am held responsible for any server/client data loss etc, use it on your own risk(!)
;
; Any suggestions and comments are welcome tough :D
;---------------------------------------------------------------------------------------------------------
;  
;  extended/reworked 2012 Deluxe0321
;  
;---------------------------------------------------------------------------------------------------------
;
; + multipart
; + log ability (use deb())
; + some http header comands
; + better "sending" procedure
; + more done, can't remember ;)
;


  ;do you want to debug? --> use the proc deb() 
  #CGI_SETTINGS_DEBUG = #False
  ;where the debug file will be written in.
  #CGI_SETTINGS_DEBUG_FILE = "debug"
  ;add your pattern here (formatdate)
  #CGI_SETTINGS_DEBUG_TIMEPATTERN = "[%dd.%mm.%yyyy::%hh:%ii:%ss]  "  
  #CGI_SETTINGS_LOGFILENAME_PATTERN = "%dd_%mm_%yyyy"
  ;how much data will be readed/written each try (readconsole) (in byte)
  #CGI_SETTINGS_READCONSOLE_MAX  = 1024 * 10 ;should be enough
  #CGI_SETTINGS_WRITECONSOLE_MAX = 1024    ;should be enough
  ;max size of Contenlenght allowed
  #CGI_SETTINGS_RECEIVEDATA_MAX = 104857600 ; 100MB
  ;allow file uploads
  #CGI_SETTINGS_ALLOW_MULITPART = #True
  
  
;for more information look @ http://en.wikipedia.org/wiki/List_of_HTTP_header_fields
Enumeration ;HTTP_HEADER
  #Header_Accept_Ranges = 1
  #Header_Age
  #Header_Allow
  #Header_Cache_Control
  #Header_Connection
  #Header_Content_Encoding
  #Header_Content_Language
  #Header_Content_Length
  #Header_Content_Location
  #Header_Content_MD5
  #Header_Content_Disposition
  #Header_Content_Range
  #Header_Content_Type
  #Header_Date
  #Header_ETag
  #Header_Expires
  #Header_Last_Modified
  #Header_Link
  #Header_Location
  #Header_P3P
  #Header_Pragma
  #Header_Proxy_Authenticate
  #Header_Refresh
  #Header_Retry_After
  #Header_Server
  #Header_Set_Cookie
  #Header_Strict_Transport_Security
  #Header_Trailer
  #Header_Transfer_Encoding
  #Header_Vary
  #Header_Via
  #Header_Warning
  #Header_WWW_Authenticate
 
  #Header_Add = 1
  #Header_AddReplace
  #Header_Replace
  #Header_Delete
EndEnumeration
  
Enumeration ;CGI
  #CGI_DATATYPE_TEXTPLAIN = 1
  #CGI_DATATYPE_TEXTENCODED
  #CGI_DATATYPE_MULTIPART
  #CGI_METHODTYPE_POST = 1
  #CGI_METHODTYPE_GET 
  
  #CGI_TO_MUCH_DATA = - 10
  #CGI_NO_DATA
  #CGI_NO_CONTENT
  #CGI_IS_MULTIPART
  
  #CGI_OK    = #True
  #CGI_ERROR = #False
EndEnumeration

Enumeration
  #CGI_Return_404 = 1
  #CGI_Return_302
EndEnumeration

Structure ReceivedFileData
  Filename.s
  ContentType.s
  Size.i
  *Buffer
EndStructure

;{ CGI_STURUCS
Structure CGI_AUTH
  TYPE.s
EndStructure

Structure CGI_CONTENT
  LENGTH.s
  TYPE.s
EndStructure

Structure CGI_DOCUMENT
  ROOT.s
EndStructure

Structure CGI_GATEWAY
  INTERFACE_.s
EndStructure

Structure CGI_PATH
  INFO.s
  TRANSLATED.s  
EndStructure

Structure CGI_QUERY
  STRING.s
EndStructure

Structure CGI_REMOTE
  ADDR.s
  HOST.s
  PORT.s
  USER.s
  IDENT.s
EndStructure

Structure CGI_REQUEST
  URI.s
  METHOD.s
EndStructure

Structure CGI_SCRIPT
  NAME.s
  FILENAME.s
EndStructure  

Structure CGI_SERVER
  ADMIN.s
  NAME.s
  PORT.s
  PROTOCOL.s
  SIGNATURE.s
  SOFTWARE.s
EndStructure

Structure CGI_HTTP
  ACCEPT.s
  ACCEPT_ENCODING.s
  ACCEPT_LANGUAGE.s
  COOKIE.s
  FORWARDED.s
  HOST.s
  PRAGMA.s
  RANGE.s
  REFERER.s
  USER_AGENT.s
EndStructure

; Structure CGI_
; EndStructure
;}
Structure CGI
  AUTH.CGI_AUTH
  CONTENT.CGI_CONTENT
  DOCUMENT.CGI_DOCUMENT
  GATEWAY.CGI_GATEWAY
  PATH.CGI_PATH
  QUERY.CGI_QUERY
  REMOTE.CGI_REMOTE
  REQUEST.CGI_REQUEST
  SCRIPT.CGI_SCRIPT
  SERVER.CGI_SERVER
  HTTP.CGI_HTTP
  ContentLength.i
  DataMethod.i
  DataType.i
  Boundary.s
  *Received
  Map Post.s()
  Map Multipart.ReceivedFileData()
  Map Get.s()
  Map Cookie.s()
EndStructure

;HTTP_ANSWER
Structure HTTP_HEADER
  Type.i
  Content.s
EndStructure

Structure HTTP_ANSWER
  List Header.HTTP_HEADER()
  List Content.s()
EndStructure


CompilerIf Defined(CurrentProcessId,#PB_Procedure)
CompilerElse
  Procedure.l CurrentProcessId()
    Protected lPID.l
    lPID.l = 0
    CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Windows
        lPID.l = GetCurrentProcessId_()
      CompilerCase #PB_OS_Linux
        lPID.l = getpid_()
      CompilerCase #PB_OS_MacOS
        ;-todo
    CompilerEndSelect
    ProcedureReturn lPID.l
  EndProcedure
CompilerEndIf

;can be used for debuging
Procedure Deb(Str.s)
  Protected FileID.i
  Debug Str.s
  If #CGI_SETTINGS_DEBUG = #True
    If FileSize(#CGI_SETTINGS_DEBUG_FILE+FormatDate(#CGI_SETTINGS_LOGFILENAME_PATTERN,Date())+".log") > 0
      FileID.i = OpenFile(#PB_Any,#CGI_SETTINGS_DEBUG_FILE+FormatDate(#CGI_SETTINGS_LOGFILENAME_PATTERN,Date())+".log")
    Else
      FileID.i = CreateFile(#PB_Any,#CGI_SETTINGS_DEBUG_FILE+FormatDate(#CGI_SETTINGS_LOGFILENAME_PATTERN,Date())+".log")
    EndIf
    If FileID
      FileSeek(FileID,Lof(FileID))
      WriteStringN(FileID,FormatDate(#CGI_SETTINGS_DEBUG_TIMEPATTERN,Date())+"("+Str(CurrentProcessId())+") "+Str.s+"<br>")
      CloseFile(FileID)
    EndIf
  EndIf
EndProcedure

Procedure.l InMem(StartPos.l, *MainMem, MainLen.l, *FindMem, FindLen.l)
  Protected FoundPos.i, MainArrayLoop.i 
  
  If StartPos < 1 : StartPos = 1 : EndIf
 
  FoundPos.i = 0
 
  For MainArrayLoop.i = StartPos - 1 To MainLen -1
   
    If MainArrayLoop + FindLen = MainLen
      ;End reached
      Break
    EndIf
   
    If CompareMemory(*MainMem + MainArrayLoop, *FindMem, FindLen) = 1
      FoundPos = MainArrayLoop + 1
      Break
    EndIf   
   
  Next
 
  ProcedureReturn FoundPos
 
EndProcedure

Procedure CGI_GetCookies(*io.CGI)
  Protected Counter.i,Argument.s
  
  If *io
    If *io\HTTP\COOKIE.s
      For Counter.i = 1 To CountString(*io\HTTP\COOKIE.s,"; ") + 1
        Argument.s = StringField(*io\HTTP\COOKIE.s,Counter.i,"; ")
        If Argument.s
          AddMapElement(*io\Cookie(),StringField(Argument.s,1,"="))
            *io\Cookie() = StringField(Argument.s,2,"=")
        EndIf
      Next
    EndIf
  EndIf
EndProcedure

;get post fields
Procedure CGI_GetPost(*io.CGI,Do.i = #CGI_DATATYPE_TEXTPLAIN)
  Protected BufferData.s, Argument.s
  Protected Counter.i, Fields.i
  
  If *io 
    If *io\Received
      BufferData.s = PeekS(*io\Received,*io\ContentLength)
      If BufferData.s
        If Do.i = #CGI_DATATYPE_TEXTPLAIN
          Fields.i = CountString(BufferData.s,#CRLF$)
          If Fields.i > 0
            For Counter.i = 1 To Fields.i + 1
              Argument.s = Trim(StringField(BufferData.s,Counter.i,#CRLF$))
              If Argument.s
                AddMapElement(*io\Post(),StringField(Argument,1,"="),#PB_Map_NoElementCheck)
                *io\Post() = StringField(Argument,2,"=")
              EndIf
            Next
            ProcedureReturn #True
          EndIf
        Else
          For Counter.i = 1 To CountString(BufferData.s,"&") + 1
            Argument.s = Trim(StringField(BufferData.s,Counter.i,"&"))
            If Argument.s
              AddMapElement(*io\Post(),StringField(Argument,1,"="),#PB_Map_NoElementCheck)
              *io\Post() = StringField(Argument,2,"=")
            EndIf 
          Next
        EndIf
      EndIf
    EndIf  
  EndIf
  
EndProcedure

;get get fields
Procedure CGI_GetGet(*io.CGI)
  Protected Counter.i, Argument.s
  If *io  
    If *io\QUERY\STRING
      Repeat
        Counter.i+1
        Argument.s=Trim(StringField(*io\QUERY\STRING, Counter.i, "&"))
        If Len(Argument)>0
          AddMapElement(*io\Get(),StringField(Argument,1,"="),#PB_Map_NoElementCheck)
          *io\Get() = StringField(Argument,2,"=")  
        EndIf
      Until Argument.s=""
      ProcedureReturn #CGI_OK
    EndIf
  EndIf
EndProcedure

;processing, GET ALL at once + multipart
Procedure CGI_Get_Form_Vars(*io.CGI)
  Protected BufferData.s, Argument.s, EndofLine.s = #CRLF$ , DoubleEndofLine.s = #CRLF$ + #CRLF$
  Protected DataStatus.i, MemoryPos.i = 1,InMemoryPos.i, ContentInMemoryPos.i, ContentTypeInMemoryPos.i
  Protected ContentSize.i, DoneMP.l = #False, GetParsingDone.i = #False, BoundaryEndofLine.s
  Protected BoundaryLenght.i, BoundaryEndOfLineLenght.i, Boundary.s, j.i
  
  CGI_GetGet(*io)
  Select *io\DataMethod.i
    Case #CGI_METHODTYPE_POST
      Select *io\DataType.i
        Case #CGI_DATATYPE_TEXTPLAIN
          DataStatus.i = CGI_GetPost(*io)
        Case #CGI_DATATYPE_TEXTENCODED
          DataStatus.i = CGI_GetPost(*io,#CGI_DATATYPE_TEXTENCODED)
        Case #CGI_DATATYPE_MULTIPART
          BoundaryEndofLine.s = *io\Boundary.s + EndofLine.s
          BoundaryLenght.i=Len(*io\Boundary.s)
          BoundaryEndOfLineLenght.i=Len(BoundaryEndofLine.s)
          Boundary.s = *io\Boundary.s
          Repeat 
            MemoryPos.i = InMem(MemoryPos.i,*io\Received, *io\ContentLength.i, @BoundaryEndofLine.s, BoundaryEndOfLineLenght.i) ;/ Searching for Boundary+EOL first time
            If MemoryPos.i
              InMemoryPos.i = InMem(MemoryPos.i + BoundaryEndOfLineLenght.i, *io\Received, *io\ContentLength.i, @EndofLine.s, 2) ;/ Now getting content disposition part info
              BufferData.s = PeekS(*io\Received + MemoryPos.i + BoundaryEndOfLineLenght.i - 1, InMemoryPos.i - (MemoryPos.i + BoundaryEndOfLineLenght.i))
              If FindString(LCase(BufferData.s),"filename=",1) Or FindString(LCase(BufferData.s),"filename*=",1)  ; oh its a file ;)
                ; This is done in a loop to get values from impropar browsers
                ; who gives non valid syntax by values order
                j = 2 ; starting from 2 since 1 is content disposition
                Repeat
                  Argument.s = Trim(StringField(BufferData.s,j,";")) 
                  If Len(Argument.s) > 0
                    Select LCase(StringField(Argument.s,1,"="))
                      Case "name"
                        *io\Multipart(RemoveString(StringField(Argument.s ,2,"="),Chr(34)))    
                      Case "filename","filename*" 
                        *io\Multipart()\Filename.s = RemoveString(StringField(Argument.s ,2,"="),Chr(34))
                    EndSelect
                  EndIf
                  j+1
                Until Argument.s = ""
                ; Now content type
                ContentTypeInMemoryPos.i = InMem(InMemoryPos.i + 1, *io\Received,*io\ContentLength.i,@DoubleEndofLine.s,4)
                *io\Multipart()\ContentType.s = Trim(StringField(PeekS(*io\Received + InMemoryPos.i + 1, ContentTypeInMemoryPos.i  - (InMemoryPos.i + 1)), 2 , ":"))   
                ; Now get the file content
                ContentInMemoryPos.i = InMem(ContentTypeInMemoryPos.i + 4, *io\Received, *io\ContentLength.i, @Boundary.s, BoundaryLenght.i)
                ContentSize.i = ContentInMemoryPos.i - (ContentTypeInMemoryPos.i + 5 + 1)
                *io\Multipart()\Buffer = ContentTypeInMemoryPos.i + 3
                *io\Multipart()\Size.i = ContentSize.i
                MemoryPos.i = ContentInMemoryPos.i ; to the next file/value         
              Else ; its a normal variable to process
                Argument.s = StringField(BufferData.s, 2, ";") 
                ContentTypeInMemoryPos.i = InMem(InMemoryPos.i+3,*io\Received,*io\ContentLength.i,@EndofLine.s,2)
                *io\Post(RemoveString(StringField(Argument.s,2,"="),Chr(34))) = PeekS(*io\Received+InMemoryPos.i + 3,ContentTypeInMemoryPos.i - (InMemoryPos.i + 3))
                MemoryPos.i = ContentTypeInMemoryPos.i
              EndIf
            Else ; could not found boundary to our next element so we are out of here
              DoneMP = #True
            EndIf       
          Until DoneMP = #True
          ProcedureReturn #True  
        Default
          ProcedureReturn #False
      EndSelect
    Case #CGI_METHODTYPE_GET
      If Not GetParsingDone.i
        CGI_GetGet(*io)
      EndIf  
    Default
      ProcedureReturn #False
  EndSelect
  ProcedureReturn #True
EndProcedure

;get Environment variables
Procedure _CGI_GetVariables(*io.CGI)
  If *io
    ; * AUTH_TYPE - Describes the authentication method used by the web browser If any 
    ;   authentication method was used. This is Not set unless the script is Protected.
    *io\AUTH\TYPE.s  = GetEnvironmentVariable("AUTH_TYPE")   
    
    ; * CONTENT_LENGTH - This is used For scripts that are receiving form Data using the POST method. 
    ;   This variable tells the byte length of the CGI input Data stream. This is required 
    ;   To Read the Data from the standard input With the POST method.
    *io\CONTENT\LENGTH.s  = GetEnvironmentVariable("CONTENT_LENGTH")   
    
    ; * CONTENT_TYPE - Tells the media type of Data being received from the user. this is 
    ;   used For scripts called using the POST method.
    *io\CONTENT\TYPE.s  = GetEnvironmentVariable("CONTENT_TYPE")   
    
    ; * DOCUMENT_ROOT - The root path To the home HTML page For the server. Example:
    ;   /home/httpd/html
    *io\DOCUMENT\ROOT.s  = GetEnvironmentVariable("DOCUMENT_ROOT")   
    
    ; * GATEWAY_INTERFACE - The version of the common gateway Interface (CGI) specification being 
    ;   used To exchange the Data between the client And server. this is normally CGI/1.1 For 
    ;   the current revision level of 1.1.
    ;   Example:   CGI/1.1
    *io\GATEWAY\INTERFACE_.s  = GetEnvironmentVariable("GATEWAY_INTERFACE")   
    
    ; * PATH_INFO - Extra path information added To the End of the URL that accessed the server side 
    ;   script program.
    *io\PATH\INFO.s  = GetEnvironmentVariable("PATH_INFO")   
    
    ; * RANGE - GOOD FOR DLS (CONT)
    ;
    *io\HTTP\RANGE.s = GetEnvironmentVariable("PATH_INFO")
    
    ; * PATH_TRANSLATED - A translated version of the PATH_INFO variable translated by the webserver 
    ;   from virtual To physical path information.
    *io\PATH\TRANSLATED.s  = GetEnvironmentVariable("PATH_TRANSLATED")   
    
    ; * QUERY_STRING - This string contains any information at the End of the server side script path 
    ;   that followed a question mark. Used To Return Data If the GET method was used by a form. There are 
    ;   length restrictions To the QUERY_STRING. Example of how To set it in HTML:
    ;   <A HREF="/cgi-bin/hits.pl?mainpage></A>
    ;   The information after the ? is the QUERY_STRING which is "mainpage" in this Case. How it looks
    ;   on the server side mainpage
    *io\QUERY\STRING.s  = GetEnvironmentVariable("QUERY_STRING")   
    
    ; * REMOTE_ADDR - The IP address of the client computer. Example:
    ;   132.15.28.124
    *io\REMOTE\ADDR.s  = GetEnvironmentVariable("REMOTE_ADDR")   
    
    ; * REMOTE_HOST - The fully qualified domain name of the client machine making the HTTP request.
    ;   It may Not be possible To determine this name since many client computers names are Not recorded 
    ;   in the DNS system. 
    ;   Example: comp11.mycompanyproxy.com
    *io\REMOTE\HOST.s  = GetEnvironmentVariable("REMOTE_HOST")   
    
    ; * REMOTE_IDENT - The ability To use this variable is limited To servers that support 
    ;   RFC 931. This variable may contain the client machine's username, but it is intended 
    ;   to be used For logging purposes only, when it is available.
    *io\REMOTE\IDENT.s  = GetEnvironmentVariable("REMOTE_IDENT")   
    
    ; * REMOTE_PORT - The clients requesting port. An example:
    ;   3465
    *io\REMOTE\PORT.s  = GetEnvironmentVariable("REMOTE_PORT")   
    
    ; * REMOTE_USER - If the CGI script was Protected And the user had To be logged in To get 
    ;   access To the script, this value will contain the user's log in name
    *io\REMOTE\USER.s  = GetEnvironmentVariable("REMOTE_USER")   
    
    ; * REQUEST_URI - The path To the requested file by the client. An example:
    ;   /cgi-bin/join.pl?button=on
    *io\REQUEST\URI.s  = GetEnvironmentVariable("REQUEST_URI")   
    
    ; * REQUEST_METHOD - This describes the request method used by the browser which is normally 
    ;   GET, POST, Or HEAD.
    *io\REQUEST\METHOD.s  = GetEnvironmentVariable("REQUEST_METHOD")   
    
    ; * SCRIPT_NAME - The virtual path of the CGI script being executed. Example:
    ;   /cgi-bin/join.pl
    *io\SCRIPT\NAME.s  = GetEnvironmentVariable("SCRIPT_NAME")   
    
    ; * SCRIPT_FILENAME - Example:
    ;   /home/httpd/cgi-bin/join.pl
    *io\SCRIPT\FILENAME.s  = GetEnvironmentVariable("SCRIPT_FILENAME")   
    
    ; * SERVER_ADMIN - The e-mail address of the server administrator. Example:
    ;   webadmin@myhost.mycompany.org
    *io\SERVER\ADMIN.s  = GetEnvironmentVariable("SERVER_ADMIN")   
    
    ; * SERVER_NAME - The server hostname, IP address Or DNS alias name shown As a self referencing URL. 
    ;   This does Not include the protocol identifier such As "HTTP:", the machine name, Or port number. 
    ;   Example: myhost
    *io\SERVER\NAME.s  = GetEnvironmentVariable("SERVER_NAME")   
    
    ; * SERVER_PORT - The port number the HTTP requests And responses are being sent on. Example:
    ;   80
    *io\SERVER\PORT.s  = GetEnvironmentVariable("SERVER_PORT")   
    
    ; * SERVER_PROTOCOL - This value is normally HTTP which describes the protocol being used between the client And server computers. Example:
    ;   HTTP/1.1
    *io\SERVER\PROTOCOL.s  = GetEnvironmentVariable("SERVER_PROTOCOL")   
    
    ; * SERVER_SIGNATURE - Server information specifying the name And version of the web server And the port being serviced. Example:
    ;   Apache/1.3.12 Server at mdct-dev3 Port 80
    *io\SERVER\SIGNATURE.s  = GetEnvironmentVariable("SERVER_SIGNATURE")   
    
    ; * SERVER_SOFTWARE - The name And version of the web server. Example:
    ;   Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
    *io\SERVER\SOFTWARE.s  = GetEnvironmentVariable("SERVER_SOFTWARE")     
    
    ; * HTTP_ACCEPT - The media types of Data that the client browser can accept. these Data types are separated by commas. An example:
    ;   image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, */*
    *io\HTTP\ACCEPT.s  = GetEnvironmentVariable("HTTP_ACCEPT")   
    
    ; * HTTP_ACCEPT_ENCODING An example:
    ;   gzip, deflate
    *io\HTTP\ACCEPT_ENCODING.s  = GetEnvironmentVariable("HTTP_ACCEPT_ENCODING")   
    
    ; * HTTP_ACCEPT_LANGUAGE - The language the client browser accepts. Example:
    ;   en-us
    *io\HTTP\ACCEPT_LANGUAGE.s  = GetEnvironmentVariable("HTTP_ACCEPT_LANGUAGE")   
    
    ; * HTTP_COOKIE - Used As an environment variable that contains cookies associated With the server domain from the browser.
    *io\HTTP\COOKIE.s  = GetEnvironmentVariable("HTTP_COOKIE")   
    
    ; * HTTP_FORWARDED An example:
    ;   by http://proxy-nt1.yourcompany.org:8080 (Netscape-Proxy/3.5)
    *io\HTTP\FORWARDED.s  = GetEnvironmentVariable("HTTP_FORWARDED")   
    
    ; * HTTP_HOST An example:
    ;   yourwebhost.yourcompany.org
    *io\HTTP\HOST.s  = GetEnvironmentVariable("HTTP_HOST")   
    
    ; * HTTP_PRAGMA - An example:
    ;   No-Cache
    *io\HTTP\PRAGMA.s  = GetEnvironmentVariable("HTTP_PRAGMA")   
    
    ; * HTTP_REFERER - The page address where the HTTP request originated. An example:
    ;   http://ctdp.tripod.com/independent/web/cgi/cgimanual/index.html
    *io\HTTP\REFERER.s  = GetEnvironmentVariable("HTTP_REFERER")   
    
    ; * HTTP_USER_AGENT - The name of the client web browser being used To make the request. Example:
    ;   Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)
    *io\HTTP\USER_AGENT.s  = GetEnvironmentVariable("HTTP_USER_AGENT")
    ProcedureReturn #CGI_OK
  EndIf
EndProcedure

; open console and read input
; will return:  
; #CGI_TO_MUCH_DATA ==> ContentLenght > #CGI_SETTINGS_RECEIVEDATA_MAX 
; #CGI_NO_DATA      ==> No Input (POST,MULTIPART), GET WILL WORK!
; #CGI_NO_CONTENT   ==> ContentLenght = 0, GET WILL WORK!
; #CGI_False        ==> ERROR
; #CGI_True         ==> everything went fine (and there was a POST or MULTIPART CMD
Procedure CGI_Init(*io.CGI) ;// Must come first
  Protected Readed.i,ReadedBytes.i,Tried.i,MaxByte.i
  
  If OpenConsole() ;Must come before Any console stuff
    EnableGraphicalConsole(#False) ;set for redirecting also text mode only
    _CGI_GetVariables(*io) ; init Env Vars

    If *io\HTTP\COOKIE 
      CGI_GetCookies(*io)
    EndIf        
    
    *io\ContentLength.i = Val(*io\CONTENT\LENGTH.s) ;length of read data
    If *io\ContentLength.i > 0     
      
      If *io\ContentLength.i > #CGI_SETTINGS_RECEIVEDATA_MAX ;
        ProcedureReturn #CGI_TO_MUCH_DATA  
      EndIf

      *io\Received = AllocateMemory(*io\ContentLength)   ;We read the buffer now
      If *io\ContentLength.i < #CGI_SETTINGS_READCONSOLE_MAX 
        MaxByte.i = *io\ContentLength.i 
      Else
        MaxByte.i =  #CGI_SETTINGS_READCONSOLE_MAX 
      EndIf
      
      Readed.i = ReadConsoleData(*io\Received, MaxByte.i)
      If Readed.i <= *io\ContentLength
        ReadedBytes.i = Readed.i
        Repeat
          Readed.i = ReadConsoleData(*io\Received + ReadedBytes, MaxByte.i)
          ReadedBytes.i + Readed.i
          Delay(1) ; don't stress the server!
          If *io\ContentLength < ReadedBytes.i + MaxByte.i
            MaxByte.i =  *io\ContentLength  - ReadedBytes.i
          EndIf
          If Readed.i = 0 And Tried.i = #False ; try again
            Readed.i = ReadConsoleData(*io\Received + ReadedBytes, MaxByte.i)
            Tried.i = #True
          EndIf
        Until Readed.i = 0 Or ReadedBytes >= *io\ContentLength
      EndIf 
      
      If Readed.i Or ReadedBytes.i
        If LCase(*io\CONTENT\TYPE)="text/plain"
          *io\DataType.i = #CGI_DATATYPE_TEXTPLAIN
        ElseIf Left(LCase(*io\CONTENT\TYPE),Len("multipart/form-data"))="multipart/form-data"
          *io\DataType.i= #CGI_DATATYPE_MULTIPART ;// In multipart we have BOUNDARY signatures to seperate each file/value
          *io\Boundary.s="--"+StringField(*io\CONTENT\TYPE,2,"=") ; adding -- to start of signature
        ElseIf FindString(LCase(*io\CONTENT\TYPE),"application/x-www-form-urlencoded")
          *io\DataType.i = #CGI_DATATYPE_TEXTENCODED
        EndIf
        If LCase(*io\REQUEST\METHOD)="post" : *io\DataMethod.i = #CGI_METHODTYPE_POST : EndIf
        If LCase(*io\REQUEST\METHOD)="get"  : *io\DataMethod.i = #CGI_METHODTYPE_GET  : EndIf
        
        If *io\DataType.i = #CGI_DATATYPE_TEXTPLAIN And #CGI_SETTINGS_ALLOW_MULITPART = #False
          ProcedureReturn #CGI_IS_MULTIPART 
        EndIf
        
        ProcedureReturn #True
      Else
        ProcedureReturn #CGI_NO_DATA 
      EndIf
    Else
      ProcedureReturn #CGI_NO_CONTENT
    EndIf
  EndIf
EndProcedure

;header & Output procs

Procedure.s _HTTP_HeaderBuildField(Field.i,Content.s)
  Protected OutPutField.s
  If Field.i > 0
    Select Field.i
      Case #Header_Accept_Ranges
         OutputField.s = "Accept-Ranges: "
      Case #Header_Age
         OutputField.s = "Age: "
      Case #Header_Allow
         OutputField.s = "Allow: "
      Case #Header_Cache_Control
         OutputField.s = "Cache-Control: "
      Case #Header_Connection
         OutputField.s = "Connection: "
      Case #Header_Content_Encoding
         OutputField.s = "Content-Encoding: "
      Case #Header_Content_Language
         OutputField.s = "Content-Language: "
      Case #Header_Content_Length
         OutputField.s = "Content-Length: "
      Case #Header_Content_Location
         OutputField.s = "Content-Location: "
      Case #Header_Content_MD5
         OutputField.s = "Content-MD5: "
      Case #Header_Content_Disposition
         OutputField.s = "Content-Disposition: "
      Case #Header_Content_Range
         OutputField.s = "Content-Range: "
      Case #Header_Content_Type
         OutputField.s = "Content-Type: "
      Case #Header_Date
         OutputField.s = "Date: "
      Case #Header_ETag
         OutputField.s = "ETag: "
      Case #Header_Expires
         OutputField.s = "Expires: "
      Case #Header_Last_Modified
         OutputField.s = "Last-Modified: "
      Case #Header_Link
         OutputField.s = "Link: "
      Case #Header_Location
         OutputField.s = "Location: "
      Case #Header_P3P
         OutputField.s = "P3P: "
      Case #Header_Pragma
         OutputField.s = "Pragma: "
      Case #Header_Proxy_Authenticate
         OutputField.s = "Proxy-Authenticate: "
      Case #Header_Refresh
         OutputField.s = "Refresh: "
      Case #Header_Retry_After
         OutputField.s = "Retry-After: "
      Case #Header_Server
         OutputField.s = "Server: "
      Case #Header_Set_Cookie
         OutputField.s = "Set-Cookie: "
      Case #Header_Strict_Transport_Security
         OutputField.s = "Strict-Transport-Security: "
      Case #Header_Trailer
         OutputField.s = "Trailer: "
      Case #Header_Transfer_Encoding
         OutputField.s = "Transfer-Encoding: "
      Case #Header_Vary
         OutputField.s = "Vary: "
      Case #Header_Via
         OutputField.s = "Via: "
      Case #Header_Warning
         OutputField.s = "Warning: "
      Case #Header_WWW_Authenticate
         OutputField.s = "WWW-Authenticate: "
    EndSelect
   
    If OutputField.s
      ProcedureReturn OutPutField.s + Content.s 
    EndIf
     
  EndIf
EndProcedure

;Add a Header (see enumeration on top)
;  Do.i-Flags              WillDo
; #Header_Add              Simply add to the header ouput (fast no check)
; #Header_AddReplace       replace if allready there  (slow check!)
; #Header_Replace          replace only if in header already
; #Header_Delete           delte entry (Content will be ignored)
Procedure.i HTTP_Header(*io.HTTP_ANSWER, Field.i, Content.s = "", Do.i = #Header_AddReplace)
  Protected Done.i = #False
 
  If *io
    If Field.i > 0
      Select Do.i
        Case #Header_AddReplace
          If ListSize(*io\Header())
            ForEach *io\Header()
              If *io\Header()\Type.i = Field.i
                *io\Header()\Content.s = Content.s
                Done.i = #True
                Break
              EndIf
            Next
          EndIf 
          If Not Done.i
            AddElement(*io\Header())
              *io\Header()\Type      = Field.i
              *io\Header()\Content.s = Content.s
          EndIf
          ProcedureReturn #True 
        Case #Header_Add
          AddElement(*io\Header())
            *io\Header()\Type      = Field.i
            *io\Header()\Content.s = Content.s
          ProcedureReturn #True
        Case #Header_Replace
          If ListSize(*io\Header())
            ForEach *io\Header()
              If *io\Header()\Type.i = Field.i
                *io\Header()\Content.s = Content.s
                ProcedureReturn #True
              EndIf
            Next
          EndIf         
        Case #Header_Delete
          If ListSize(*io\Header())
            ForEach *io\Header()
              If *io\Header()\Type.i = Field.i
                DeleteElement(*io\Header())
                ProcedureReturn #True
              EndIf
            Next
          EndIf
      EndSelect
    EndIf
  EndIf
EndProcedure

;Clears a header
Procedure.i HTTP_HeaderClear(*io.HTTP_ANSWER)
  If *io
   ClearList(*io\Header())
   ProcedureReturn #True
  EndIf
EndProcedure

;Add New Content (HTML,XML,etc.)
Procedure.i HTTP_Content(*io.HTTP_ANSWER,Content.s,AddEndLineFeed = #True)
  If *io
    AddElement(*io\Content())
      If AddEndLineFeed.i
        *io\Content() = Content.s + #CRLF$
      Else
        *io\Content() = Content.s
      EndIf
  EndIf
EndProcedure

;clear content
Procedure.i HTTP_ContentClear(*io.HTTP_ANSWER)
  If *io
   ClearList(*io\Content())
   ProcedureReturn #True
  EndIf
EndProcedure

;bulid the header and return it as string
Procedure.s HTTP_HeaderBuild(*io.HTTP_ANSWER,AddEndLineFeed.i = #True)
  Protected Output.s, Process.s
 
  If *io
    If ListSize(*io\Header())
      ForEach *io\Header()
        Process.s = _HTTP_HeaderBuildField(*io\Header()\Type.i,*io\Header()\Content.s)
        If Process.s
          Output.s + Process.s + #CRLF$ 
        EndIf
      Next
    EndIf
   
    If Output.s
      If AddEndLineFeed.i
        Output.s + #CRLF$ ; only need to add one!
      EndIf
      ProcedureReturn Output.s
    EndIf
   
  EndIf
EndProcedure

;build the content and return it as string
Procedure.s HTTP_ContentBuild(*io.HTTP_ANSWER)
  Protected Output.s
  If *io
    If ListSize(*io\Content())
      ForEach *io\Content()
        Output.s + *io\Content()
      Next
    EndIf
  EndIf
 
  ProcedureReturn Output.s
EndProcedure

;build header and content and return it ready for sending
Procedure HTTP_Build(*io.HTTP_ANSWER)
  Protected ToSendLength.i, ToSend.s, *Buffer
 
  If *io
    ToSend.s = HTTP_HeaderBuild(*io)
    ToSend.s + HTTP_ContentBuild(*io)
   
    ToSendLength.i = StringByteLength(ToSend.s) + SizeOf(Character)
    If ToSendLength.i > 0
      *Buffer = AllocateMemory(ToSendLength.i)
      If *Buffer
        PokeS(*Buffer,ToSend.s ,ToSendLength.i)
        ProcedureReturn *Buffer
      Else
        ProcedureReturn #False
      EndIf
    Else
      ProcedureReturn #False
    EndIf
 EndIf
EndProcedure


;Send Data --> WriteConsoleData
ProcedureDLL Http_Send(*Buffer,Size.i,FreeMem = #True)
  Protected Written.i,BytesWritten,MaxWrite.i
  If *Buffer And Size.i > 0
    
    If Size.i < #CGI_SETTINGS_WRITECONSOLE_MAX
      MaxWrite.i = Size.i  
    Else
      MaxWrite.i = #CGI_SETTINGS_WRITECONSOLE_MAX
    EndIf
    
    Written.i = WriteConsoleData(*Buffer, MaxWrite.i)
    If Written.i < Size.i
      Repeat
        BytesWritten.i + Written.i
        If Size.i < BytesWritten.i + MaxWrite.i
          MaxWrite.i =  Size.i - BytesWritten.i
        EndIf
        Written = WriteConsoleData(*Buffer + BytesWritten.i,MaxWrite.i)
      Until BytesWritten >= Size.i 
    EndIf  
    
    If FreeMem And MemorySize(*buffer)
      FreeMemory(*Buffer)
    EndIf
    
    ProcedureReturn #True
    
  EndIf
EndProcedure


DisableExplicit
Example:

Code: Select all

CGI.CGI ; create cgi struc
 HTTP.HTTP_ANSWER ;create answer struc
; 
 CGI_Init(@CGI) ; init the console get VARS
 CGI_Get_Form_Vars(@CGI) ; get form data
; 
; ;we send html
 HTTP_Header(@HTTP, #Header_Content_Type, "text/html")
; 
; 
 HTTP_Content(@HTTP,"<html>")
 HTTP_Content(@HTTP,"<meta http-equiv='Content-Type' content='text/html'/> ")
 HTTP_Content(@HTTP,"<header>")
 HTTP_Content(@HTTP,"<title>PB_CGI</title>")
 HTTP_Content(@HTTP,"</header>")
 HTTP_Content(@HTTP,"<body>")
; 
 ;Showing Input information..
 HTTP_Content(@HTTP,"Get:<br>") ; get
 ForEach CGI\Get()
   objline$ = MapKey(CGI\Get())
   objline$ + " = ["+CGI\Get()+"]<br>"
   HTTP_Content(@HTTP,objline$)
 Next
 HTTP_Content(@HTTP,"<br><br>")
 
; 
 HTTP_Content(@HTTP,"Multipart:<br>") ;"files"
 ForEach CGI\Multipart()
   objline$ = MapKey(CGI\Multipart())
   objline$ + " = ["+CGI\Multipart()\ContentType+" | @"+Str(CGI\Multipart()\Buffer)+" | "+Str(CGI\Multipart()\Size)+" | "+CGI\Multipart()\Filename.s+"]<br>"
   HTTP_Content(@HTTP,objline$)
 Next
 HTTP_Content(@HTTP,"<br><br>")
; 
 HTTP_Content(@HTTP,"Post: <br>") ; post
 ForEach CGI\Post()
   objline$ = MapKey(CGI\Post())
   objline$ + " = ["+CGI\Post()+"]<br>"
   HTTP_Content(@HTTP,objline$ )
 Next
 HTTP_Content(@HTTP,"<br><br>")
; 
 ;writing the lines (all VARS)..
 HTTP_Content(@HTTP,"Hi, received your Data:<br>")
 HTTP_Content(@HTTP,"CGI\AUTH\TYPE=" + CGI\AUTH\TYPE + "<br>")
 HTTP_Content(@HTTP,"CGI\CONTENT\LENGTH=" + CGI\CONTENT\LENGTH + "<br>")
 HTTP_Content(@HTTP,"CGI\CONTENT\TYPE=" + CGI\CONTENT\TYPE + "<br>")
 HTTP_Content(@HTTP,"CGI\DOCUMENT\ROOT=" + CGI\DOCUMENT\ROOT + "<br>")
 HTTP_Content(@HTTP,"CGI\GATEWAY\INTERFACE=" + CGI\GATEWAY\INTERFACE_ + "<br>")
 HTTP_Content(@HTTP,"CGI\PATH\INFO=" + CGI\PATH\INFO + "<br>")
 HTTP_Content(@HTTP,"CGI\PATH\TRANSLATED=" + CGI\PATH\TRANSLATED + "<br>")
 HTTP_Content(@HTTP,"CGI\QUERY\STRING=" + CGI\QUERY\STRING + "<br>")
 HTTP_Content(@HTTP,"CGI\REMOTE\ADDR=" + CGI\REMOTE\ADDR + "<br>")
 HTTP_Content(@HTTP,"CGI\REMOTE\HOST=" + CGI\REMOTE\HOST + "<br>")
 HTTP_Content(@HTTP,"CGI\REMOTE\IDENT=" + CGI\REMOTE\IDENT + "<br>")
 HTTP_Content(@HTTP,"CGI\REMOTE\PORT=" + CGI\REMOTE\PORT + "<br>")
 HTTP_Content(@HTTP,"CGI\REMOTE\USER=" + CGI\REMOTE\USER + "<br>")
 HTTP_Content(@HTTP,"CGI\REQUEST\URI=" + CGI\REQUEST\URI + "<br>")
 HTTP_Content(@HTTP,"CGI\REQUEST\METHOD=" + CGI\REQUEST\METHOD + "<br>")
 HTTP_Content(@HTTP,"CGI\SCRIPT\NAME=" + CGI\SCRIPT\NAME + "<br>")
 HTTP_Content(@HTTP,"CGI\SCRIPT\FILENAME=" + CGI\SCRIPT\FILENAME + "<br>")
 HTTP_Content(@HTTP,"CGI\SERVER\ADMIN=" + CGI\SERVER\ADMIN + "<br>")
 HTTP_Content(@HTTP,"CGI\SERVER\NAME=" + CGI\SERVER\NAME + "<br>")
 HTTP_Content(@HTTP,"CGI\SERVER\PORT=" + CGI\SERVER\PORT + "<br>")
 HTTP_Content(@HTTP,"CGI\SERVER\PROTOCOL=" + CGI\SERVER\PROTOCOL + "<br>")
 HTTP_Content(@HTTP,"CGI\SERVER\SIGNATURE=" + CGI\SERVER\SIGNATURE + "<br>")
 HTTP_Content(@HTTP,"CGI\SERVER\SOFTWARE=" + CGI\SERVER\SOFTWARE + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\ACCEPT=" + CGI\HTTP\ACCEPT + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\ACCEPT_ENCODING=" + CGI\HTTP\ACCEPT_ENCODING + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\ACCEPT_LANGUAGE=" + CGI\HTTP\ACCEPT_LANGUAGE + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\COOKIE=" + CGI\HTTP\COOKIE + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\FORWARDED=" + CGI\HTTP\FORWARDED + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\HOST=" + CGI\HTTP\HOST + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\PRAGMA=" + CGI\HTTP\PRAGMA + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\REFERER=" + CGI\HTTP\REFERER + "<br>")
 HTTP_Content(@HTTP,"CGI\HTTP\USER_AGENT=" + CGI\HTTP\USER_AGENT + "<br>" )    
 HTTP_Content(@HTTP,"</body>")
 HTTP_Content(@HTTP,"</html>")
; 
; ;we dont process any files here.. so free the memory
 If CGI\Received
   FreeMemory(CGI\Received)
 EndIf
; 
 ;build the page
 *Buffer = HTTP_Build(@HTTP)
 If *Buffer
   ;send the stuff
   HTTP_Send(*Buffer,MemorySize(*Buffer))
 EndIf
 
Debug Input()


For testing you can use Pantcho!!'s HTML stuff --> http://www.purebasic.fr/english/viewtop ... 35#p268335

Greetings,
Deluxe0321
Last edited by Deluxe0321 on Wed Dec 12, 2012 1:53 pm, edited 2 times in total.
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Re: PB native CGI more complete solution

Post by djes »

Wow. Thank you! :)
jamirokwai
Enthusiast
Enthusiast
Posts: 796
Joined: Tue May 20, 2008 2:12 am
Location: Cologne, Germany
Contact:

Re: PB native CGI more complete solution

Post by jamirokwai »

Hi Deluxe0321,

thanks for this snippet, and thanks to Rings for creating it! I tried on my MacBook. Works great!
If you run the example-html, only the first and third options work.
The example using POST-variables doesn't work. If you change the GET for third example to POST, it doesn't work either.

1) POST test with 2 files and line of text
--> works

2) now a simple POST with 2 lines of text
--> does not work

3) now a simple GET with few variables
--> works

EDIT: The example by Pantcho!! doesn't work with POST either...
Regards,
JamiroKwai
Deluxe0321
User
User
Posts: 69
Joined: Tue Sep 16, 2008 6:11 am
Location: ger

Re: PB native CGI more complete solution

Post by Deluxe0321 »

Hey jamirokwai,
I updated the source - should be working now.
omarpta
New User
New User
Posts: 4
Joined: Tue May 07, 2013 8:19 pm

Re: PB native CGI more complete solution

Post by omarpta »

Thanks a lot...

How do i save my uploaded file to disc?


tnks in advance.
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: PB native CGI more complete solution

Post by infratec »

Hi,

you get the buffer position and the size, so simply use

Code: Select all

CreateFile()
WriteData()
CloseFile()
Maybe this works (not tested):
Add the following in the example:

Code: Select all

If CGI\Received
   
   ForEach CGI\Multipart()
     If CGI\Multipart()\Filename
       File = CreateFile(#PB_Any, CGI\Multipart()\Filename)
       If File
         WriteData(File, CGI\Multipart()\Buffer, CGI\Multipart()\Size)
         CloseFile(File)
       EndIf
     EndIf
   Next
  
   FreeMemory(CGI\Received)
 EndIf
Bernd
omarpta
New User
New User
Posts: 4
Joined: Tue May 07, 2013 8:19 pm

Re: PB native CGI more complete solution

Post by omarpta »

infratec,

The file is created but it is always an empty file with 0kb.
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: PB native CGI more complete solution

Post by infratec »

In 9 hours I can test it in my office.

Bernd
omarpta
New User
New User
Posts: 4
Joined: Tue May 07, 2013 8:19 pm

Re: PB native CGI more complete solution

Post by omarpta »

Ok thanks in advance
infratec
Always Here
Always Here
Posts: 7575
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: PB native CGI more complete solution

Post by infratec »

Hi,

use this and it works:

Code: Select all

WriteData(File,  CGI\Received + CGI\Multipart()\Buffer, CGI\Multipart()\Size)
CGI\Multipart()\Buffer is only the offset inside the Buffer, so we need the buffer itself too :D

Bernd
omarpta
New User
New User
Posts: 4
Joined: Tue May 07, 2013 8:19 pm

Re: PB native CGI more complete solution

Post by omarpta »

Ow Thank you very much, it works!

Omar
Post Reply