Jump to content
jvanegmond

Powerful HTTP Server in Pure AutoIt

Recommended Posts

I've updated the source code fixing the security flaw you pointed out in your PM along with some other things that I've fixed previously. Added some mime-types and updated with extensive comments.

All in all, it's worth downloading again.

Thanks so much for finding the security hole, james! I'm glad you found it before anyone got hurt.

Glad it helped :mellow: And nice script :(

"There are 10 types of people in this world - those who can read binary, and those who can't.""We've heard that a million monkeys at a million keyboards could produce the complete works of Shakespeare; now, thanks to the Internet, we know that is not true." ~Robert Wilensky0101101 1001010 1100001 1101101 1100101 1110011 0110011 1001101 10001110000101 0000111 0001000 0001110 0001101 0010010 1010110 0100001 1101110

Share this post


Link to post
Share on other sites

Haha, no problem. I am glad we have it all sorted out ... 8)

I've done a line-by-line comment on this code. I hope it'll help you understand it better.

Sorry I haven't documented the functions just as extensive. I couldn't really explain the POST functions in as much detail. If you have any specific questions or requests, just ask me.

#cs 
Resources:
    Internet Assigned Number Authority - all Content-Types: http://www.iana.org/assignments/media-types/
    World Wide Web Consortium - An overview of the HTTP protocol: http://www.w3.org/Protocols/
    
Credits:
    Manadar for starting on the webserver.
    Alek for adding POST and some fixes
    Creator for providing the "application/octet-stream" MIME type.
#ce

; // OPTIONS HERE //
Dim $sRootDir = @ScriptDir & "\www\" ; The absolute path to the root directory of the server.
Dim $sIP = @IPAddress1 ; ip address as defined by AutoIt
Dim $iPort = 80 ; the listening port
Dim $iMaxUsers = 15 ; Maximum number of users who can simultaneously get/post
; // END OF OPTIONS //

Dim $aSocket[$iMaxUsers] ; Creates an array to store all the possible users
Dim $sBuffer[$iMaxUsers] ; All these users have buffers when sending/receiving, so we need a place to store those

For $x = 0 to UBound($aSocket)-1 ; Fills the entire socket array with -1 integers, so that the server knows they are empty.
    $aSocket[$x] = -1
Next

TCPStartup() ; AutoIt needs to initialize the TCP functions

$iMainSocket = TCPListen($sIP,$iPort) ;create main listening socket
If @error Then ; if you fail creating a socket, exit the application
    MsgBox(0x20, "AutoIt Webserver", "Unable to create a socket on port " & $iPort & ".") ; notifies the user that the HTTP server will not run
    Exit ; if your server is part of a GUI that has nothing to do with the server, you'll need to remove the Exit keyword and notify the user that the HTTP server will not work.
EndIf

ConsoleWrite( "Server created on http://" & $sIP & "/" & @CRLF) ;; If you're in SciTE,

While 1
    $iNewSocket = TCPAccept($iMainSocket) ;; Tries to accept incoming connections
    
    If $iNewSocket >= 0 Then ; Verifies that there actually is an incoming connection
        For $x = 0 to UBound($aSocket)-1 ;; Attempts to store the incoming connection
            If $aSocket[$x] = -1 Then
                $aSocket[$x] = $iNewSocket ;store the new socket
                ExitLoop
            EndIf
        Next
    EndIf

    For $x = 0 to UBound($aSocket)-1 ; A big loop to receive data from everyone connected
        If $aSocket[$x] = -1 Then ContinueLoop ;; if the socket is empty, it will continue to the next iteration, doing nothing
        $sNewData = TCPRecv($aSocket[$x],1024) ; Receives a whole lot of data if possible
        If @error Then ;; Client has disconnected
            $aSocket[$x] = -1 ; Socket is freed so that a new user may join
            ContinueLoop ; Go to the next iteration of the loop, not really needed but looks oh so good
        ElseIf $sNewData Then ; data received
            $sBuffer[$x] &= $sNewData ;store it in the buffer
            If StringInStr(StringStripCR($sBuffer[$x]),@LF&@LF) Then ; if the request has ended .. 
                $sFirstLine = StringLeft($sBuffer[$x],StringInStr($sBuffer[$x],@LF)) ;; helps to get the type of the request
                $sRequestType = StringLeft($sFirstLine,StringInStr($sFirstLine," ")-1) ;; gets the type of the request
                If $sRequestType = "GET" Then ;; user wants to download a file or whatever .. 
                    $sRequest = StringTrimRight(StringTrimLeft($sFirstLine,4),11) ;; let's see what file he actually wants
                    If $sRequest = "/" Then ;; user has requested the root
                        $sRequest = "/index.html" ;; instead of root we'll give him the index page :)
                    EndIf
                    $sRequest = StringReplace($sRequest,"/","\") ; convert HTTP slashes to windows slashes, not really required because windows accepts both
                    If FileExists($sRootDir & "\" & $sRequest) Then ;; makes sure the file that the user wants exists :D
                        $sFileType = StringRight($sRequest,4) ;; determines the file type, so that we may choose what mine type to use
                        Switch $sFileType
                            Case "html", ".htm" ;; in case of normal HTML files
                                _SendFile($sRootDir & "\" & $sRequest, "text/html", $aSocket[$x])
                            Case ".css" ;; in case of style sheets
                                _SendFile($sRootDir & "\" & $sRequest, "text/css", $aSocket[$x])
                            Case ".jpg", "jpeg" ;; for common images
                                _SendFile($sRootDir & "\" & $sRequest, "image/jpeg", $aSocket[$x])
                            Case ".png" ;; another common image format
                                _SendFile($sRootDir & "\" & $sRequest, "image/png", $aSocket[$x])
                            Case Else ; this is for .exe, .zip, or anything else that is not supported is downloaded to the client using a application/octet-stream
                                _SendFile($sRootDir & "\" & $sRequest, "application/octet-stream", $aSocket[$x])
                        EndSwitch
                    Else
                        _SendError($aSocket[$x]) ;; File does not exist, so we'll send back an error..
                    EndIf
                ElseIf $sRequestType = "POST" Then ;; user has come to us with data, we need to parse that data and based on that do something special
                    
                    $aPOST = _Get_Post($sBuffer[$x]) ;; parses the post data
                    
                    $sName = _POST("Name",$aPOST) ; Like PHPs _POST, but it requires the second parameter to be the return value from _Get_Post
                    $sComment = _POST("Comment",$aPOST) ;; Gets the comment
                    
                    _POST_ConvertString($sName) ;; Needs to convert the POST HTTP string into a normal string
                    _POST_ConvertString($sComment) ;; same ..
                    
                    FileWrite($sRootDir & "\index.html", FileRead($sRootDir & "\index.html") & "<br />" & $sName & " made comment: " & $sComment) ;Ofcourse, in real situations you have to prevent people to use HTML/PHP/Javascript etc. in their comments.
                    ;; The last line adds whatever Name:Comment said in the root file .. this creates some sort of chatty effect
                   
                    _SendFile($sRootDir & "\index.html", "text/html", $aSocket[$x]) ; Sends back the new file we just created
                EndIf
                
                $sBuffer[$x] = "" ;; clears the buffer because we just used to buffer and did some actions based on them
                TCPCloseSocket($aSocket[$x]) ;; we have defined connection: close, so we close the connection
                $aSocket[$x] = -1 ;; reset the socket so that we may accept new clients
                
            EndIf
        EndIf
    Next
    
    Sleep(10)
WEnd

Func _POST_ConvertString(ByRef $sString) ;; converts any characters like %20 into space 8)
    $sString = StringReplace($sString, '+', ' ')
    StringReplace($sString, '%', '')
    For $t = 0 To @extended
        $Find_Char = StringLeft( StringTrimLeft($sString, StringInStr($sString, '%')) ,2)
        $sString = StringReplace($sString, '%' & $Find_Char, Chr(Dec($Find_Char)))
    Next
EndFunc

Func _SendHTML($sHTML,$sSocket) ;; sends HTML data back to the client on X socket
    Local $iLen, $sPacket, $sSplit
    
    $iLen = StringLen($sHTML)
    $sPacket = Binary("HTTP/1.1 200 OK" & @CRLF & _
    "Server: ManadarX/1.0 (" & @OSVersion & ") AutoIt " & @AutoItVersion & @CRLF & _
    "Connection: close" & @CRLF & _
    "Content-Lenght: " & $iLen & @CRLF & _
    "Content-Type: text/html" & @CRLF & _
    @CRLF & _
    $sHTML)
    $sSplit = StringSplit($sPacket,"")
    $sPacket = ""
    For $i = 1 to $sSplit[0]
        If Asc($sSplit[$i]) <> 0 Then ; Just make sure we don't send any null bytes, because they show up as ???? in your browser.
            $sPacket = $sPacket & $sSplit[$i]
        EndIf
    Next
    TCPSend($sSocket,$sPacket)
EndFunc

Func _SendFile($sAddress, $sType, $sSocket) ;; Sends a file back to the client on X socket, with X mime-type
    Local $hFile, $sImgBuffer, $sPacket, $a
    
    $hFile = FileOpen($sAddress,16)
    $sImgBuffer = FileRead($hFile)
    FileClose($hFile)
   
    $sPacket = Binary("HTTP/1.1 200 OK" & @CRLF & _
    "Server: ManadarX/1.3.26 (" & @OSVersion & ") AutoIt " & @AutoItVersion & @CRLF & _
    "Connection: close" & @CRLF & _
    "Content-Type: " & $sType & @CRLF & _
    @CRLF)
    TCPSend($sSocket,$sPacket)
   
    While BinaryLen($sImgbuffer) ;LarryDaLooza's idea to send in chunks to reduce stress on the application
        $a = TCPSend($sSocket,$sImgbuffer)
        $sImgbuffer = BinaryMid($sImgbuffer,$a+1,BinaryLen($sImgbuffer)-$a)
    WEnd
   
    $sPacket = Binary(@CRLF & _
    @CRLF)
    TCPSend($sSocket,$sPacket)
    TCPCloseSocket($sSocket)
EndFunc

Func _SendError($sSocket) ;; Sends back a basic 404 error
    _SendHTML("404 Error: " & @CRLF & @CRLF & "The file you requested could not be found.", $sSocket)
EndFunc

Func _Get_Post($s_Buffer) ;; parses incoming POST data
    Local $sTempPost, $sLen, $sPostData, $sTemp
    
    ;Get the lenght of the data in the POST
    $sTempPost = StringTrimLeft($s_Buffer,StringInStr($s_Buffer,"Content-Length:"))
    $sLen = StringTrimLeft($sTempPost,StringInStr($sTempPost,": "))
   
    ;Create the base struck
    $sPostData = StringSplit(StringRight($s_Buffer,$sLen),"&")
    
    Local $sReturn[$sPostData[0]+1][2]
    
    For $t = 1 To $sPostData[0]
        $sTemp = StringSplit($sPostData[$t],"=")
        If $sTemp[0] >= 2 Then
            $sReturn[$t][0] = $sTemp[1]
            $sReturn[$t][1] = $sTemp[2]
        EndIf
    Next
   
    Return $sReturn
EndFunc

Func _POST($sName,$sArray) ;; Returns a POST variable based on their name and not their array index. This function basically makes up for the lack of associative arrays in Au3
    For $i = 1 to UBound($sArray)-1
        If $sArray[$i][0] = $sName Then
            Return $sArray[$i][1]
        EndIf
    Next
    Return ""
EndFunc
OK

Thanks all the same.

Share this post


Link to post
Share on other sites

Good script, Manadar. Hows it hold up, performance wise? Have you done any stress tests?

Pretty good, actually. I haven't done any time measurements, because I had no reference material. I'm positive that at least 15 users can GET pages at the same time. POST might be a little slower because it relies on FileWrite in my example, but in a real situation where you're going to be using this you can delete POST all together.

Share this post


Link to post
Share on other sites

How about storing POST in a variable? Is that possible? and call the variable $IPaddress-POSTname.. or something like that.. Just adding my 2 cents in ideas ;-)


[right]~What can I say, I'm a Simplistic person[/right]

Share this post


Link to post
Share on other sites

How about storing POST in a variable? Is that possible? and call the variable $IPaddress-POSTname.. or something like that.. Just adding my 2 cents in ideas ;-)

I don't think I fully understand what you're saying .. Maybe you could rephrase it in Dutch and I'll be able to understand.

Storing the POST in a separate global variable is a bad thing though. You should try to avoid globals where you can.

Share this post


Link to post
Share on other sites

Ik had een idee om de POST in een variable op te slaan, dit werkt immers sneller dan filewrite...

Dan noem je de variabele $IPADDRESS-POSTNAME, bijvoorbeeld als je een form hebt waarin je de inputbox genaamd 'username' hebt, en degene die de form submit heeft IP address 81.154.12.64 (random IP address) dan sla je de waarde die hij in 'username' heeft ingevuld op in de variabele $81.154.12.64-username.

Maar als je zegt dat het opslaan van die variabele een slecht idee is, dan zou ik dat beter niet doen.. (care to explain why it is a bad thing?)


[right]~What can I say, I'm a Simplistic person[/right]

Share this post


Link to post
Share on other sites

Ik had een idee om de POST in een variable op te slaan, dit werkt immers sneller dan filewrite...

Dan noem je de variabele $IPADDRESS-POSTNAME, bijvoorbeeld als je een form hebt waarin je de inputbox genaamd 'username' hebt, en degene die de form submit heeft IP address 81.154.12.64 (random IP address) dan sla je de waarde die hij in 'username' heeft ingevuld op in de variabele $81.154.12.64-username.

Maar als je zegt dat het opslaan van die variabele een slecht idee is, dan zou ik dat beter niet doen.. (care to explain why it is a bad thing?)

Ik snap nu wat je bedoelt. Het zou ook kunnen zonder FileWrite en het dan inderdaad opslaan in een variabele. In dit geval is het toegestaan om een global te gebruiken, omdat je dit niet met een local variabele kan doen.

Ik heb echt gekozen om het in een bestand op te slaan omdat het 1) Sneller te maken was, en 2) omdat het opgeslagen blijft ook als de server crashed.

In principe word POST altijd in een variabele opgeslagen, maar de implementatie van POST hier gebruikt FileWrite. Als je zelf met deze server aan de slag gaat, zul je allereerst de bestaande POST code moeten verwijderen, omdat het totaal geen nut heeft. 8)

Edited by Manadar

Share this post


Link to post
Share on other sites

Hey Manadar! How bout a translation!

I'm trying to follow on with whats happening :

Oh and is there are way to have like htpasswd or something?

Edited by BrettF

Share this post


Link to post
Share on other sites

Maar als de server crasht, dan Maakt het toch niet uit of de post variabele wordt behouden, je bent je sessionid immers toch al kwijt. Zou ik zo zeggen.


[right]~What can I say, I'm a Simplistic person[/right]

Share this post


Link to post
Share on other sites

Ik snap nu wat je bedoelt. Het zou ook kunnen zonder FileWrite en het dan inderdaad opslaan in een variabele. In dit geval is het toegestaan om een global te gebruiken, omdat je dit niet met een local variabele kan doen.

Ik heb echt gekozen om het in een bestand op te slaan omdat het 1) Sneller te maken was, en 2) omdat het opgeslagen blijft ook als de server crashed.

In principe word POST altijd in een variabele opgeslagen, maar de implementatie van POST hier gebruikt FileWrite. Als je zelf met deze server aan de slag gaat, zul je allereerst de bestaande POST code moeten verwijderen, omdat het totaal geen nut heeft. 8)

I now understand what you mean. It could also be without WriteSHIELD File and then save it in fact a variable. In this case it is permissible for global use, because you do not with a local variable can do.

I've really taken to it in a file because it is 1) to make faster, and 2) because it remains stored even when the server crashed.

In principle POST always be stored in a variable, but the implementation of POST used here File Write. If you own this server by using it, you will first of all the existing POST code should remove, because it has no usefulness

Share this post


Link to post
Share on other sites

Google translation ;-)?

I was suggesting storing the POST in a variable which is includes the IP Address and the name of the <input> in the html. But manadar replied that this was not such a good idea, since if the server crashed, you would lose the POST. Then I replied, but if the server crashes, you lose the session anyways, so it's not that big of a deal.


[right]~What can I say, I'm a Simplistic person[/right]

Share this post


Link to post
Share on other sites

Google translation ;-)?

I was suggesting storing the POST in a variable which is includes the IP Address and the name of the <input> in the html. But manadar replied that this was not such a good idea, since if the server crashed, you would lose the POST. Then I replied, but if the server crashes, you lose the session anyways, so it's not that big of a deal.

All very true. But you see have to realise that the actual POST data, and handling/storing the data that comes out of POST are two separate things. The incoming POST data is parsed, which is the standard part that is the same for all web-servers. The FileWrite part is the implementation of the POST, to show how you could use POST in a code example.

So, that being out of the way. If you store the posted data in on a variable, instead of on a file, you can no longer retreive old messages when the server starts up. Look at it like this in a linear way:

1) Manadar posts a message

2) toonboom posts a message

3) Server crashes

4) Manadar posts a message

The server now holds 3 messages.

If you were to use a variable you'd only keep 1 message, because the previously posted data hasn't been stored anywhere and is lost with the server crashing.

Share this post


Link to post
Share on other sites

That's not the exact use of a POST function though, what you are referring to is database-like storage. and that, indeed should be written to a file. I am referring to this:

<form name="input" action="formprocess.php" method="post">
Username: 
<input type="text" name="user">
<input type="submit" value="Submit">
</form>

Then formprocess.php would contain something along these lines:

Welcome to AutoIt <?echo($_POST["username"]);! <BR> Enjoy your stay!

[right]~What can I say, I'm a Simplistic person[/right]

Share this post


Link to post
Share on other sites

Ok.. Ok.. my bad xD I confused it with another autoit webserver I saw...

silly clear-minded aussie (no offense, just joking)


[right]~What can I say, I'm a Simplistic person[/right]

Share this post


Link to post
Share on other sites

Yes I am sorry.. I misunderstood a big part of it.. I'm at home, my stomach hurts, I ate something wrong three days ago and I'm still sick of it (literally) the only thing I can do now is sit behing my computer and read forums, and code some autoit snippets... I sometimes misinterpret things I read the last few days. I am sorry.


[right]~What can I say, I'm a Simplistic person[/right]

Share this post


Link to post
Share on other sites

Yes I am sorry.. I misunderstood a big part of it.. I'm at home, my stomach hurts, I ate something wrong three days ago and I'm still sick of it (literally) the only thing I can do now is sit behing my computer and read forums, and code some autoit snippets... I sometimes misinterpret things I read the last few days. I am sorry.

Get well soon so that we may engage in intelligent conversation some other time. :mellow:

Share this post


Link to post
Share on other sites

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...