*}%|1rHIf`7None SendText HTTPHeaderCleanupCoreSystemUWeb HTTPResponse HTTPErrorSendStandardHeadersQueryDecodeFormData RedirectCheckRawBytesCreateResponseObjectGetApplication PreQuery GetHexDigit PostQueryCloseIpDrv ReceivedLine ProcessHeadInitProcessHeaderString EndOfHeaders SendBinary ProcessGetEngineFailAuthenticationForceKarma Collision Movement LightColor Lighting ProcessPostListenSubstExpirationSecondsDefaultApplicationMaxConnections ClearSubst IncludeUHTMIncludeBinaryFile ListenPortLoadParsedUHTMGetHTTPExpirationSendCachedFile BeginPlayTimer LostChild GainedChild Destroyed Request_GET Request_POST WebServerWebConnection ImageServerWebApplication HelloWeb DecodeBase64 AddVariable WebRequest WebResponseIpAddrToString GetLocalIP BindPort ReceivedText GetVariableGetVariableCountGetVariableNumber AcceptClass IncludePath AcceptedClosed LinkStateTcpLink InternetLink bEnabled Password UsernameHeaderEngineVersion ReceivedDataRequest Response ApplicationRawBytesExpectingLIpAddr ServerNameValueDataText Filename ApplicationsApplicationPathsOwnerURLDynamicLoadObjecti ServerURLApplicationObjectsConnectionCountApplicationClasstURISubURIReplacementMapS Connection bSentTextbSentResponse ReturnValue VariablebClearDCB ScriptText LevelInfoOffsetSecondsbNoCRLFLevel ContentTypeActorRealmPathCountTitle ErrorNumClassbCachePackage ERequestType TextBufferObjectContentLength RequestType VariableMapEnumEncoded Function VariableNameStruct DefaultValue StrPropertyStructPropertyNumberClassPropertyObjectPropertyTokench BoolProperty IntProperty BytePropertyImageNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneCharSetNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNone CleanupApp IsHangingbDelayCleanupNetModeNoneNoneNoneNoneNoneNoneNoneNoneNoneMaxLineLengthMaxValueLengthH1H2NonelimitNoneDump TMultiMapNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNoneNonewo@6 @EF3}F RяF RяFFFFI]/Webh= iso-8859-1`rT?@EFF```Pc;gk@E``'````3@``o``*``@``@``@`}C8> RяKgk3}3}gkgkgkgk! #"`]xWebAdmin.UTServerAdmin`xWebAdmin.UTImageServera] /ServerAdmina /images-"P)"'"QH````( `` ` `%@`Lr9>$FKgkgkgkgkgkgk Rя Rя Rя Rя Rя! #"H"G"``!``C``@````"````@``````?@``x``@``t````@````@``] @@````B@``````@``8``|L&)L)M)N G`-@``:@``z`c`.@``/``g@``A``@``@``@``\@``@``#``\a  G`7@``0@``D@@``|Q+\)Q G`VH> )H)i G`;@`````` `A@``@``$ @@``G@``>EE, )E)G G`B@``i@`` ``FOFn)O G```M@``N@````E@````@``=S,)S G`@``{@``y@``T=)T G`JYG)Y)Z)[ G`v``Z@``[``J@````,@``_````@``b@``@``67`@``V@``@````@``@``k``@``zX/S)X G`njMj HTTP/1.1 400 Bad Request400 Bad Request

400 Bad Request

If you got this error from a standard web browser, please mail jack@epicgames.com and submit a bug report. yHTTP/1.1 401 UnauthorizedppWWW-authenticate: basic realm="k"401 Unauthorized

401 Unauthorized

 HTTP/1.1 404 Object Not Found404 File Not Found

404 File Not Found

The URL you requested was not found.  G`phE`^;-!Can't send headers - already called SendText()pph, ,  G`l4 ub HTTP/1.1 302 Document MovedpLocation: 4Document Movedpp

Object Moved

This document may be found here. G`qf?f-@' G`sd:d G`b*class WebRequest extends Object native noexport; enum ERequestType { Request_GET, Request_POST }; var string URI; var string Username; var string Password; var int ContentLength; var string ContentType; var ERequestType RequestType; var private native const TMultiMap VariableMap; // C++ placeholder. native final function string DecodeBase64(string Encoded); native final function AddVariable(string VariableName, coerce string Value); native final function string GetVariable(string VariableName, optional string DefaultValue); native final function int GetVariableCount(string VariableName); native final function string GetVariableNumber(string VariableName, int Number, optional string DefaultValue); native final function Dump(); // only works in dev mode function ProcessHeaderString(string S) { local int i; if(Left(S, 21) ~= "Authorization: Basic ") { S = DecodeBase64(Mid(S, 21, 256)); i = InStr(S, ":"); if(i != -1) { Username = Left(S, i); Password = Mid(S, i+1); } } else if(Left(S, 16) ~= "Content-Length: ") ContentLength = Int(Mid(S, 16, 64)); else if(Left(S, 14) ~= "Content-Type: ") ContentType = Mid(S, 14, 512); } function DecodeFormData(string Data) { local string Token[2], ch; local int i, H1, H2, limit; local int t; t = 0; for( i = 0; i < Len(Data); i++ ) { if ( limit > class'WebConnection'.default.MaxValueLength || i > class'WebConnection'.default.MaxLineLength ) break; ch = mid(Data, i, 1); switch(ch) { case "+": Token[t] $= " "; limit++; break; case "&": case "?": if(Token[0] != "") AddVariable(Token[0], Token[1]); Token[0] = ""; Token[1] = ""; t = 0; limit=0; break; case "=": if(t == 0) { limit = 0; t = 1; } else { Token[1] $= "="; limit++; } break; case "%": H1 = GetHexDigit(Mid(Data, ++i, 1)); if ( H1 != -1 ) { limit++; H1 *= 16; H2 = GetHexDigit(Mid(Data,++i,1)); if ( H2 != -1 ) Token[t] $= Chr(H1 + H2); } limit++; break; default: Token[t] $= ch; limit++; } } if(Token[0] != "") AddVariable(Token[0], Token[1]); } function int GetHexDigit(string D) { switch(caps(D)) { case "0": return 0; case "1": return 1; case "2": return 2; case "3": return 3; case "4": return 4; case "5": return 5; case "6": return 6; case "7": return 7; case "8": return 8; case "9": return 9; case "A": return 10; case "B": return 11; case "C": return 12; case "D": return 13; case "E": return 14; case "F": return 15; } return -1; } `ta00--! b'-!'=a G`u^+^_ G`y6\-! -!'9-\ 6Ypp6, ,  G`@``S9/*============================================================================= WebResponse is used by WebApplication to handle most aspects of sending http information to the client. It serves as a bridge between WebApplication and WebConnection. =============================================================================*/ class WebResponse extends Object native noexport; var private native const TMultiMap ReplacementMap; // C++ placeholder. var const config string IncludePath; var localized string CharSet; var WebConnection Connection; var bool bSentText; // used to warn headers already sent var bool bSentResponse; native final function Subst(string Variable, coerce string Value, optional bool bClear); native final function ClearSubst(); native final function IncludeUHTM(string Filename); native final function IncludeBinaryFile(string Filename); native final function string LoadParsedUHTM(string Filename); // For templated web items, uses Subst too native final function string GetHTTPExpiration(optional int OffsetSeconds); native final function Dump(); // only works in dev mode event SendText(string Text, optional bool bNoCRLF) { if(!bSentText) { SendStandardHeaders(); bSentText = True; } if(bNoCRLF) Connection.SendText(Text); else Connection.SendText(Text$Chr(13)$Chr(10)); } event SendBinary(int Count, byte B[255]) { Connection.SendBinary(Count, B); } function SendCachedFile(string Filename, optional string ContentType) { if(!bSentText) { SendStandardHeaders(ContentType, true); bSentText = True; } IncludeUHTM(Filename); } function FailAuthentication(string Realm) { HTTPError(401, Realm); } function HTTPResponse(string Header) { HTTPHeader(Header); bSentResponse = True; } function HTTPHeader(string Header) { if(bSentText) Log("Can't send headers - already called SendText()"); Connection.SendText(Header$Chr(13)$Chr(10)); } function HTTPError(int ErrorNum, optional string Data) { switch(ErrorNum) { case 400: HTTPResponse("HTTP/1.1 400 Bad Request"); SendText("400 Bad Request

400 Bad Request

If you got this error from a standard web browser, please mail jack@epicgames.com and submit a bug report."); break; case 401: HTTPResponse("HTTP/1.1 401 Unauthorized"); HTTPHeader("WWW-authenticate: basic realm=\""$Data$"\""); SendText("401 Unauthorized

401 Unauthorized

"); break; case 404: HTTPResponse("HTTP/1.1 404 Object Not Found"); SendText("404 File Not Found

404 File Not Found

The URL you requested was not found."); break; default: break; } } function SendStandardHeaders( optional string ContentType, optional bool bCache ) { if(ContentType == "") ContentType = "text/html"; if(!bSentResponse) HTTPResponse("HTTP/1.1 200 OK"); HTTPHeader("Server: UnrealEngine UWeb Web Server Build "$Connection.Level.EngineVersion); HTTPHeader("Content-Type: "$ContentType); if (bCache) { HTTPHeader("Cache-Control: max-age="$Connection.WebServer.ExpirationSeconds); // Need to compute an Expires: tag .... arrgggghhh HTTPHeader("Expires:"@GetHTTPExpiration(Connection.WebServer.ExpirationSeconds)); } HTTPHeader("Connection: Close"); HTTPHeader(""); } function Redirect(string URL) { HTTPResponse("HTTP/1.1 302 Document Moved"); HTTPHeader("Location: "$URL); SendText("Document Moved"); SendText("

Object Moved

This document may be found here."); } `@``RU.)U G`@``+*$ G`A2/*============================================================================= WebServer is responsible for listening to requests on the selected http port and will guide requests to the correct application. =============================================================================*/ class WebServer extends TcpLink; var config string ServerName; var config string Applications[10]; var config string ApplicationPaths[10]; var config bool bEnabled; var config int ListenPort; var config int MaxConnections; var config int DefaultApplication; var config int ExpirationSeconds; // How long images can be cached .. default is 24 hours var string ServerURL; var WebApplication ApplicationObjects[10]; var int ConnectionCount; // MC: Debug // var int ConnId; function BeginPlay() { local int i; local class ApplicationClass; local IpAddr l; local string s; // Destroy if not a server if (Level.NetMode == NM_StandAlone || Level.NetMode == NM_Client) { Destroy(); return; } if(!bEnabled) { Log("Webserver is not enabled. Set bEnabled to True in Advanced Options."); Destroy(); return; } Super.BeginPlay(); for(i=0;i<10;i++) { if(Applications[i] == "") break; ApplicationClass = class(DynamicLoadObject(Applications[i], class'Class')); if(ApplicationClass != None) { ApplicationObjects[i] = New(None) ApplicationClass; ApplicationObjects[i].Level = Level; ApplicationObjects[i].WebServer = Self; ApplicationObjects[i].Path = ApplicationPaths[i]; ApplicationObjects[i].Init(); } } if(ServerName == "") { GetLocalIP(l); s = IpAddrToString(l); i = InStr(s, ":"); if(i != -1) s = Left(s, i); ServerURL = "http://"$s; } else ServerURL = "http://"$ServerName; if(ListenPort != 80) ServerURL = ServerURL $ ":"$string(ListenPort); BindPort( ListenPort ); Listen(); } event Destroyed() { local int i; for(i = 0; i < ArrayCount(ApplicationObjects); i++) if (ApplicationObjects[i] != None) ApplicationObjects[i].CleanupApp(); Super.Destroyed(); } event GainedChild( Actor C ) { Super.GainedChild(C); ConnectionCount++; // if too many connections, close down listen. if(MaxConnections > 0 && ConnectionCount > MaxConnections && LinkState == STATE_Listening) { Log("WebServer: Too many connections - closing down Listen."); Close(); } } event LostChild( Actor C ) { Super.LostChild(C); ConnectionCount--; // if closed due to too many connections, start listening again. if(ConnectionCount <= MaxConnections && LinkState != STATE_Listening) { Log("WebServer: Listening again - connections have been closed."); Listen(); } } function WebApplication GetApplication(string URI, out string SubURI) { local int i, l; SubURI = ""; for(i=0;i<10;i++) { if(ApplicationPaths[i] != "") { l = Len(ApplicationPaths[i]); if(Left(URI, l) == ApplicationPaths[i] && (Len(URI) == l || Mid(URI, l, 1) == "/")) { SubURI = Mid(URI, l); return ApplicationObjects[i]; } } } return None; } `n@@``J. aA( G```@``D&{S B%, {$"}$z&"$&}&"z&"&/B&"* G`FK3l zK(w(-9:9:$WebServer: Listening again - connections have been closed.% G```HP4_P(-% (-9:9:$WebServer: Too many connections - closing down Listen. G`@K$Aa G`M5VL%C, 9w*z G`G2)e-o( G`@``{ ]|,Authorization: Basic V,%~:%?%;%&|,Content-Length: 09J,,@|,Content-Type: /, G`~N/*============================================================================= WebConnection is the bridge that will handle all communication between the web server and the client's browser. =============================================================================*/ class WebConnection extends TcpLink; var WebServer WebServer; var string ReceivedData; var WebRequest Request; var WebResponse Response; var WebApplication Application; var bool bDelayCleanup; var int RawBytesExpecting; var config int MaxValueLength; var config int MaxLineLength; // MC: Debug // var int ConnId; event Accepted() { WebServer = WebServer(Owner); SetTimer(30, False); // ConnId = WebServer.ConnId++; // Log("Connection"@ConnId@"Accepted"); } event Closed() { // Log("Connection"@ConnId@"Closed"); Destroy(); } event Timer() { bDelayCleanup = False; Cleanup(); } event ReceivedText( string Text ) { local int i; local string S; ReceivedData $= Text; if(RawBytesExpecting > 0) { RawBytesExpecting -= Len(Text); CheckRawBytes(); return; } // remove a LF which arrived in a new packet // and thus didn't get cleaned up by the code below if(Left(ReceivedData, 1) == Chr(10)) ReceivedData = Mid(ReceivedData, 1); i = InStr(ReceivedData, Chr(13)); while(i != -1) { S = Left(ReceivedData, i); i++; // check for any LF following the CR. if(Mid(ReceivedData, i, 1) == Chr(10)) i++; ReceivedData = Mid(ReceivedData, i); ReceivedLine(S); if(LinkState != STATE_Connected) return; if(RawBytesExpecting > 0) { CheckRawBytes(); return; } i = InStr(ReceivedData, Chr(13)); } } function ReceivedLine(string S) { if (S == "") EndOfHeaders(); else { // Log(S,'WebServer'); if(Left(S, 4) ~= "GET ") ProcessGet(S); else if(Left(S, 5) ~= "POST ") ProcessPost(S); else if(Left(S, 5) ~= "HEAD ") ProcessHead(S); else if(Request != None) { Request.ProcessHeaderString(S); } } } function ProcessHead(string S) { Log("Received Header: "$S,'Header'); } function ProcessGet(string S) { local int i; if(Request == None) CreateResponseObject(); Request.RequestType = Request_GET; S = Mid(S, 4); while(Left(S, 1) == " ") S = Mid(S, 1); i = InStr(S, " "); if(i != -1) S = Left(S, i); i = InStr(S, "?"); if(i != -1) { Request.DecodeFormData(Mid(S, i+1)); S = Left(S, i); } Application = WebServer.GetApplication(S, Request.URI); if(Application != None && Request.URI == "") { Response.Redirect(S$"/"); Cleanup(); } else if(Application == None && Webserver.DefaultApplication != -1) { Response.Redirect(Webserver.ApplicationPaths[Webserver.DefaultApplication]$"/"); Cleanup(); } } function ProcessPost(string S) { local int i; if(Request == None) CreateResponseObject(); Request.RequestType = Request_POST; S = Mid(S, 5); while(Left(S, 1) == " ") S = Mid(S, 1); i = InStr(S, " "); if(i != -1) S = Left(S, i); i = InStr(S, "?"); if(i != -1) { Request.DecodeFormData(Mid(S, i+1)); S = Left(S, i); } Application = WebServer.GetApplication(S, Request.URI); if(Application != None && Request.URI == "") { // Response.Redirect(WebServer.ServerURL$S$"/"); Response.Redirect(S$"/"); Cleanup(); } } function CreateResponseObject() { Request = new(None) class'WebRequest'; Response = new(None) class'WebResponse'; Response.Connection = Self; } function EndOfHeaders() { if(Response == None) { CreateResponseObject(); Response.HTTPError(400); // Bad Request Cleanup(); return; } if(Application == None) { Response.HTTPError(404); // FNF Cleanup(); return; } if(Request.ContentLength != 0 && Request.RequestType == Request_POST) { RawBytesExpecting = Request.ContentLength; RawBytesExpecting -= Len(ReceivedData); CheckRawBytes(); } else { if (Application.PreQuery(Request, Response)) { Application.Query(Request, Response); Application.PostQuery(Request, Response); } Cleanup(); } } function CheckRawBytes() { if(RawBytesExpecting <= 0) { if(!(Request.ContentType ~= "application/x-www-form-urlencoded")) { Log("WebConnection: Unknown form data content-type: "$Request.ContentType); Response.HTTPError(400); // Can't deal with this type of form data } else { Request.DecodeFormData(ReceivedData); if (Application.PreQuery(Request, Response)) { Application.Query(Request, Response); Application.PostQuery(Request, Response); } ReceivedData = ""; } Cleanup(); } } function Cleanup() { if (bDelayCleanup) return; if(Request != None) Request = None; if(Response != None) { Response.Connection = None; Response = None; } if (Application != None) Application = None; Close(); } final function bool IsHanging() { return bDelayCleanup; } `< 1 099:9:$9:9:$a -]Webserver is not enabled. Set bEnabled to True in Advanced Options.a  %o , z Do* D ew** * * 2 A ` $  z<B8#A8 ~#: ## phttp://#phttp://<,,Ppp:9S,C,% G`K) 8>%%})Ts x w3)&3 +aB s & ?{%>%&%&%s% =%s%&aB&=s %t)&tst9?,v)&vaBtvs aB3s{%>%& G`I5D2aB 5/%}5  Nz &,   &~ , C z &,   C9:9:$ %  ~ , _ G`X class WebApplication extends Object; // Set by the webserver var LevelInfo Level; var WebServer WebServer; var string Path; function Init(); // This is a dummy function which should never be called // Here for backwards compatibility final function Cleanup(); function CleanupApp() { if (Level != None) Level = None; if (WebServer != None) WebServer = None; } function bool PreQuery(WebRequest Request, WebResponse Response) { return true; } function Query(WebRequest Request, WebResponse Response); function PostQuery(WebRequest Request, WebResponse Response); `UZ -o w**?w***Qw** G`z class ImageServer extends WebApplication; /* Usage: [UWeb.WebServer] Applications[0]="UWeb.ImageServer" ApplicationPaths[0]="/images" bEnabled=True http://server.ip.address/images/test.jpg */ event Query(WebRequest Request, WebResponse Response) { local string Image; Image = Request.URI; if( Right(Caps(Image), 4) == ".JPG" || Right(Caps(Image), 5) == ".JPEG" ) { Response.SendStandardHeaders("image/jpeg", true); } else if( Right(Caps(Image), 4) == ".GIF" ) { Response.SendStandardHeaders("image/gif", true); } else if( Right(Caps(Image), 4) == ".BMP" ) { Response.SendStandardHeaders("image/bmp", true); } else { Response.HTTPError(404); return; } Response.IncludeBinaryFile( Path $ Image ); } Rj:Sgk``Nez e 0% 1& "2, +3, 44, =5, F6, O7, X8, a9, jA, sB, |C, D, E, F,  G`W w %|/application/x-www-form-urlencodedpWebConnection: Unknown form data content-type: /       G```X -r*  Tr*  0%9:.9:$0}    G``t 1* *  G`OZz7|,GET Z|,POST $}|,HEAD w*  G`2 G`Z  G`q]' G`^``_````m $A >r*.$  ,Rz &   &1~   ~ ?  &  ;w*z p / G`b````9dT G`e````9 dO{?test{;testHelloWeb  >/form.html-
'*

4

 h/submit.html+Thanks for submitting the form.
9ppTestEdit was "FTestEdit"

%You selected these items:
'Jselecter&e'%2pp"Wselecter'"
' (/include.html%+variable1This is variable 1%+variable2This is variable 2%+variable3This is variable 3=testinclude.html 9pHello web! The current level is 2H
Click this link to go to a test form G`qclass HelloWeb extends WebApplication; /* Usage: This is a sample web application, to demonstrate how to program for the web server. [UWeb.WebServer] Applications[0]="UWeb.HelloWeb" ApplicationPaths[0]="/hello" bEnabled=True http://server.ip.address/hello */ event Query(WebRequest Request, WebResponse Response) { local int i; if(Request.Username != "test" || Request.Password != "test") { Response.FailAuthentication("HelloWeb"); return; } switch(Request.URI) { case "/form.html": Response.SendText("

"); Response.SendText(""); Response.SendText("

"); Response.SendText(""); Response.SendText("

"); break; case "/submit.html": Response.SendText("Thanks for submitting the form.
"); Response.SendText("TestEdit was \""$Request.GetVariable("TestEdit")$"\"

"); Response.SendText("You selected these items:
"); for(i=Request.GetVariableCount("selecter")-1;i>=0;i--) Response.SendText("\""$Request.GetVariableNumber("selecter", i)$"\"
"); break; case "/include.html": Response.Subst("variable1", "This is variable 1"); Response.Subst("variable2", "This is variable 2"); Response.Subst("variable3", "This is variable 3"); Response.IncludeUHTM("testinclude.html"); break; default: Response.SendText("Hello web! The current level is "$Level.Title); Response.SendText("
Click this link to go to a test form"); break; } } gf<hAIgk3}``Ykj"$pReceived Header: k!R G`9I !Ibz,.JPGz,.JPEG  image/jpeg'z,.GIF  image/gif'z,.BMP  image/bmp'   Rp` G`@``m1 c >z11text/html?-@HTTP/1.1 200 OKpServer: UnrealEngine UWeb Web Server Build pContent-Type: 1-gpCache-Control: max-age=9S7Expires:l7Connection: Close G`i qr*.$  ,Rz &   &1~   ~ ?  &  >w*z p /r*:( p:$/ G`@``````[z0'w2*2*$wA*A* G`Qp{  -o G```v``s``@``w@``lNt G`c``WN G`~`FDSDOD[FDWD~DIFDMDDDZDyDHW|DT\LQO\}O1O5O4O3D\SBWbSSO2DMDNDVQZOdOJOKODO@Pw8?@G<UG>;\V>Vr?VA@XP@W^@en@n|@TJAnXA8TfAjzE gHF8WFnfF9dtFoXMegMnvMeDNeRNe`N fnNe|NUJOkYOXgO]vOeDPVSPpbPYqPnP aMQe\QkjQeyQ hGR]URicR&+sR -^S )lSKzS}KTJYT}gT|vTYFUocTU^bUu^pU 'UYMV #\V (VPMW [[W+%jW>(OXQwXqEY8SYlcYnrY `@ZFPNZE(]ZFRE[>PT[Uc[F&r[vX\+sg\+\v\+tE]JPS]vb]=_q],$]R_c^VNq^z_@_=$O_G,s_lz_`WPn`WU}`WRLau{[a OiatAwatwFb@Ubs_cbs}rbG@cqGcuUcpRdcErcnR@d>\NdmC\dm]kd/%yda^esm [ro/Ms.|sx4n*jt0=X2U^Glrex4_9s`zrR.%`VrE* S x4M2sI@`J2OrAFrP R^ 3FpJrv 4LDK&P 5Qv2+GWrrZ@x4JOZ 1ed EIDHNx4d V\zx4F V:S\Ao gprWDe>iQg!x"Y%{U`Vpr@$kN9Uy9VI"XUzVJ9 qYx4}J<YG@`9 g`nG ^Ulsh_|mr{z=I{)FLoI}`JLG[HiN"xLZN"jmL