' Gambas class file
'***************************************************************************
'
' miniServer.class
'
' (c)2005 - Daniel Campos Fernández
'
' XML-RPC Component
'
' Realizado para la Junta de Extremadura.
' Consejería de Educación Ciencia y Tecnología.
' Proyecto gnuLinEx
'
' This program Is free software; you can redistribute it And / Or modify
' it under the terms OF the GNU General PUBLIC License AS published by
' the Free Software Foundation; either version 1, Or (at your option)
' any later version.
'
' This program Is distributed IN the hope that it will be useful,
' but WITHOUT ANY WARRANTY; WITHOUT even the implied WARRANTY OF
' MERCHANTABILITY Or FITNESS FOR A PARTICULAR PURPOSE.See the
' GNU General PUBLIC License FOR more details.
'
' You should have received a COPY OF the GNU General PUBLIC License
' along WITH this program; IF Not, WRITE TO the Free Software
' Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
'
'***************************************************************************
PRIVATE Http AS ServerSocket
PRIVATE hSocket AS Object[]
PRIVATE hBuffer AS String[]
PRIVATE hProt AS Byte[]
PRIVATE hLen AS Integer[]
PRIVATE hConn AS Integer[]
PRIVATE hType AS Byte[]
PRIVATE hReply AS String
EVENT GotData(Data AS String) AS Boolean
EVENT ProcessData(Data AS String)
PRIVATE SUB hError(hS AS Socket, hErr AS String)
TRY PRINT #hS, "HTTP/1.1 " & hErr
TRY PRINT #hS, "Server: Gambas XML-RPC Server"
TRY PRINT #hS, "Connection: close"
TRY PRINT #hS, "Content-Type: text/html; charset=iso-8859-1"
TRY PRINT #hS, ""
TRY PRINT #hS, ""
TRY PRINT #hS, "
"
TRY PRINT #hS, "" & hErr & ""
TRY PRINT #hS, ""
TRY PRINT #hS, "Bad Request
"
TRY PRINT #hS, hErr & "."
TRY PRINT #hS, "
"
TRY PRINT #hS, "Gambas XML-RPC Server"
TRY PRINT #hS, ""
TRY PRINT #hS, ""
TRY PRINT #hS, ""
END
PRIVATE SUB RemoveSocket(hS AS Socket)
DIM Bucle AS Integer
FOR Bucle = 0 TO hSocket.Count - 1
IF hS = hSocket[Bucle] THEN
TRY CLOSE #hS
hSocket.Remove(Bucle)
hBuffer.Remove(Bucle)
hLen.Remove(Bucle)
hProt.Remove(Bucle)
hConn.Remove(Bucle)
hType.Remove(Bucle)
BREAK
END IF
NEXT
END
PRIVATE FUNCTION FirstTest(Xml AS XmlReader) AS Boolean
DO WHILE NOT Xml.Eof
TRY Xml.Read()
IF ERROR THEN RETURN FALSE
IF Xml.Eof THEN RETURN TRUE
IF Xml.Node.Type = XmlReaderNodeType.Element THEN
SELECT CASE Xml.Node.Name
CASE "methodCall"
CASE "methodName"
CASE "params"
CASE "param"
CASE "i4"
CASE "int"
CASE "boolean"
CASE "string"
CASE "double"
CASE "dateTime.iso8601"
CASE "base64"
CASE "struct"
CASE "array"
CASE "data"
CASE "member"
CASE "value"
CASE "name"
DEFAULT
RETURN FALSE
END SELECT
END IF
LOOP
RETURN TRUE
END
PUBLIC SUB SetReply(sCad AS String)
hReply = sCad
END
PRIVATE SUB ProcessQuery(hS AS Socket, Buf AS String)
DIM hBuf AS String[]
DIM Bucle AS Integer
DIM Point AS Integer
DIM Resul AS String
DIM sReply AS String
DIM hBol AS Boolean
DIM Xml AS XmlReader
Point = - 1
hBuf = Split(Buf, Chr(13))
FOR Bucle = 0 TO hBuf.Count - 1
hBuf[Bucle] = Replace(hBuf[Bucle], Chr(10), "")
NEXT
FOR Bucle = 0 TO hBuf.Count - 1
IF hBuf[Bucle] = "" THEN
Point = Bucle
BREAK
END IF
NEXT
IF Point = - 1 THEN
hError(hS, "400 Bad Request")
RemoveSocket(hS)
RETURN
END IF
FOR Bucle = Point + 1 TO hBuf.Count - 1
Resul = Resul & hBuf[Bucle]
IF Bucle < (hBuf.Count - 1) THEN Resul = Resul & "\n"
NEXT
Xml = NEW XmlReader
TRY Xml.FromString(Trim(Resul))
IF ERROR THEN
Xml = NULL
hError(hS, "400 Bad Request")
RemoveSocket(Hs)
RETURN
END IF
sReply = "OK"
IF NOT FirstTest(Xml) THEN
sReply = Tools.FaultReply("1", "Malformed XML-RPC document")
ELSE
hBol = RAISE GotData(Resul)
IF hBol THEN
sReply = Tools.FaultReply("2", "Unknown method")
ELSE
sReply = Tools.FaultReply("3", "Can not perform desired request")
hReply = ""
RAISE ProcessData(Resul)
IF hReply <> "" THEN sReply = hReply
END IF
END IF
TRY PRINT #hS, "HTTP/1.1 200 OK"
TRY PRINT #hS, "Connection: close"
TRY PRINT #hS, "Content-Length: " & Len(sReply)
TRY PRINT #hS, "Content-Type: text/xml"
TRY PRINT #hS, "Server: Gambas RPC Server\n"
TRY WRITE #hS, sReply, Len(sReply)
RemoveSocket(hS)
END
PUBLIC SUB Socket_Read()
DIM Buf AS String
DIM sCad AS String
DIM Bucle AS Integer
DIM hS AS Socket
FOR Bucle = 0 TO hSocket.Count - 1
IF hSocket[Bucle] = LAST THEN
hS = LAST
BREAK
END IF
NEXT
IF hS = NULL THEN RETURN
IF hBuffer[Bucle] = "" THEN
IF Lof(hS) >= 5 THEN
TRY READ #hS, Buf, Lof(hS)
hBuffer[Bucle] = hBuffer[Bucle] & Buf
IF Left(hBuffer[Bucle], 5) <> "POST " THEN
hError(hS, "405 Method Not Allowed")
RemoveSocket(hS)
RETURN
END IF
END IF
ELSE
TRY READ #hS, Buf, Lof(hS)
hBuffer[Bucle] = hBuffer[Bucle] & Buf
END IF
IF hProt[Bucle] = - 1 THEN
IF InStr(hBuffer[Bucle], Chr(13)) > 0 THEN
Buf = Trim(Left(hBuffer[Bucle], InStr(hBuffer[Bucle], Chr(13)) - 1))
Buf = Right(Buf, 8)
IF Buf = "HTTP/1.1" THEN
hProt[Bucle] = 1
ELSE IF Buf = "HTTP/1.0" THEN
hProt[Bucle] = 0
ELSE
hError(hS, "505 HTTP Version Not Supported")
RemoveSocket(hS)
RETURN
END IF
ELSE
IF Len(hBuffer[Bucle]) >= 2048 THEN
hError(hS, "413 Request Entity Too Large")
RemoveSocket(hS)
RETURN
END IF
END IF
END IF
IF InStr(hBuffer[Bucle], Chr(13) & Chr(10) & Chr(13) & Chr(10)) THEN
IF hType[Bucle] = 0 THEN
Buf = Left(hBuffer[Bucle], InStr(hBuffer[Bucle], Chr(13) & Chr(13)) - 1)
IF InStr(UCase(Buf), "CONTENT-TYPE:") > 0 THEN
sCad = Mid(Buf, InStr(UCase(Buf), "CONTENT-TYPE:") + 13)
sCad = Trim(Left(sCad, InStr(sCad, Chr(13))))
IF LCase(sCad) <> "text/xml" THEN
hError(hS, "415 Unsupported Media Type")
RemoveSocket(hS)
RETURN
ELSE
hType[Bucle] = 1
END IF
ELSE
hError(hS, "415 Unsupported Media Type")
RemoveSocket(hS)
RETURN
END IF
IF InStr(UCase(Buf), "CONTENT-LENGTH:") > 0 THEN
sCad = Mid(Buf, InStr(UCase(Buf), "CONTENT-LENGTH:") + 15)
sCad = Trim(Left(sCad, InStr(sCad, Chr(13))))
TRY hLen[Bucle] = CInt(sCad)
IF ERROR THEN
hError(hS, "411 Length Required")
RemoveSocket(hS)
RETURN
END IF
ELSE
hError(hS, "411 Length Required")
RemoveSocket(hS)
RETURN
END IF
END IF
END IF
IF InStr(hBuffer[Bucle], Chr(13) & Chr(10) & Chr(13) & Chr(10)) > 0 THEN
IF hLen[Bucle] = - 1 OR hType[Bucle] <> 1 THEN
hError(hS, "406 Not Acceptable")
RemoveSocket(hS)
RETURN
END IF
ProcessQuery(hS, hBuffer[Bucle])
ELSE
IF Len(hBuffer[Bucle]) > 4096 THEN
hError("413 Request Entity Too Large")
RemoveSocket(hS)
END IF
END IF
END
PUBLIC SUB Http_Connection(RemoteHost AS String)
hSocket.Add(Http.Accept())
hBuffer.Add("")
hProt.Add(- 1)
hLen.Add(- 1)
hConn.Add(0)
hType.Add(0)
END
PUBLIC SUB Close()
DO WHILE hSocket.Count > 0
RemoveSocket(hSocket[0])
LOOP
TRY Http.Close()
Http = NULL
END
PUBLIC SUB Listen(Port AS Integer, MaxConn AS Integer)
IF NOT Http THEN
Http = NEW ServerSocket AS "Http"
ELSE
IF Http.Status <> 0 THEN Http.Close()
END IF
TRY Http.Port = Port
IF ERROR THEN
Error.Raise("Invalid TCP port")
RETURN
END IF
IF MaxConn > 0 THEN
TRY Http.Listen(MaxConn)
ELSE
TRY Http.Listen()
END IF
IF ERROR THEN Error.Raise("Unable to listen at port " & Port)
IF Http.Status < 0 THEN Error.Raise("Unable to listen at port " & Port)
END
PUBLIC SUB _New()
hSocket = NEW Object[]
hBuffer = NEW String[]
hProt = NEW Byte[]
hLen = NEW Integer[]
hConn = NEW Integer[]
hType = NEW Byte[]
END
PUBLIC SUB _Free()
ME.Close()
END