Prepare fixtures

This commit is contained in:
dkanus 2026-07-14 20:27:09 +07:00
commit 9c94356263
6021 changed files with 722805 additions and 22 deletions

View file

@ -0,0 +1,62 @@
class 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("<form method=post action=submit.html>");
Response.SendText("<input type=edit name=TestEdit>");
Response.SendText("<p><select multiple name=selecter>");
Response.SendText("<option value=\"one\">Number One");
Response.SendText("<option value=\"two\">Number Two");
Response.SendText("<option value=\"three\">Number Three");
Response.SendText("<option value=\"four\">Number Four");
Response.SendText("</select><p>");
Response.SendText("<input type=submit name=Submit value=Submit>");
Response.SendText("</form>");
break;
case "/submit.html":
Response.SendText("Thanks for submitting the form.<br>");
Response.SendText("TestEdit was \""$Request.GetVariable("TestEdit")$"\"<p>");
Response.SendText("You selected these items:<br>");
for(i=Request.GetVariableCount("selecter")-1;i>=0;i--)
Response.SendText("\""$Request.GetVariableNumber("selecter", i)$"\"<br>");
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("<br>Click <a href=\"form.html\">this link</a> to go to a test form");
break;
}
}
defaultproperties
{
}

View file

@ -0,0 +1,36 @@
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 );
}

View file

@ -0,0 +1,4 @@
[Flags]
AllowDownload=False
ClientOptional=False
ServerSideOnly=True

View file

@ -0,0 +1,26 @@
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);

View file

@ -0,0 +1,274 @@
/*=============================================================================
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;
}
defaultproperties
{
MaxValueLength=512 // Maximum size of a variable value
MaxLineLength=4096
}

View file

@ -0,0 +1,147 @@
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;
}
defaultproperties
{
}

View file

@ -0,0 +1,131 @@
/*=============================================================================
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("<TITLE>400 Bad Request</TITLE><H1>400 Bad Request</H1>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("<TITLE>401 Unauthorized</TITLE><H1>401 Unauthorized</H1>");
break;
case 404:
HTTPResponse("HTTP/1.1 404 Object Not Found");
SendText("<TITLE>404 File Not Found</TITLE><H1>404 File Not Found</H1>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");
// if _RO_
HTTPHeader("Server: UnrealEngine UWeb Web Server Build "$Connection.Level.ROVersion);
// else
//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("<head><title>Document Moved</title></head>");
SendText("<body><h1>Object Moved</h1>This document may be found <a HREF=\""$URL$"\">here</a>.");
}
defaultproperties
{
IncludePath="/Web"
CharSet="iso-8859-1"
}

View file

@ -0,0 +1,149 @@
/*=============================================================================
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<WebApplication> 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<WebApplication>(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;
}
defaultproperties
{
Applications(0)="xWebAdmin.UTServerAdmin"
Applications(1)="xWebAdmin.UTImageServer"
ApplicationPaths(0)="/ServerAdmin"
ApplicationPaths(1)="/images"
ListenPort=8075
MaxConnections=30
ExpirationSeconds=86400
AcceptClass=Class'UWeb.WebConnection'
}

View file

@ -0,0 +1,14 @@
<html>
<head><title>Index of /kf_sources/UWeb/Classes/</title></head>
<body>
<h1>Index of /kf_sources/UWeb/Classes/</h1><hr><pre><a href="../">../</a>
<a href="HelloWeb.uc">HelloWeb.uc</a> 06-Oct-2019 10:27 1929
<a href="ImageServer.uc">ImageServer.uc</a> 06-Oct-2019 10:28 763
<a href="UWeb.upkg">UWeb.upkg</a> 06-Oct-2019 10:27 73
<a href="WebApplication.uc">WebApplication.uc</a> 06-Oct-2019 10:27 601
<a href="WebConnection.uc">WebConnection.uc</a> 06-Oct-2019 10:27 5156
<a href="WebRequest.uc">WebRequest.uc</a> 06-Oct-2019 10:27 2746
<a href="WebResponse.uc">WebResponse.uc</a> 06-Oct-2019 10:27 3858
<a href="WebServer.uc">WebServer.uc</a> 06-Oct-2019 10:27 3518
</pre><hr></body>
</html>

View file

@ -0,0 +1,7 @@
<html>
<head><title>Index of /kf_sources/UWeb/</title></head>
<body>
<h1>Index of /kf_sources/UWeb/</h1><hr><pre><a href="../">../</a>
<a href="Classes/">Classes/</a> 29-Jun-2025 19:43 -
</pre><hr></body>
</html>