mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-13 12:19:25 +00:00
251 lines
6.4 KiB
ObjectPascal
251 lines
6.4 KiB
ObjectPascal
unit Keyman.Developer.System.HttpServer.App;
|
|
|
|
interface
|
|
|
|
uses
|
|
IdContext,
|
|
IdCustomHTTPServer,
|
|
IdHTTPServer,
|
|
|
|
Keyman.Developer.System.HttpServer.Base;
|
|
|
|
type
|
|
TAppHttpResponder = class(TBaseHttpResponder)
|
|
private
|
|
FStandardTemplatePath: string;
|
|
procedure RespondProject(doc: string; AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
procedure RespondHelp(doc: string; AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
public
|
|
procedure ProcessRequest(AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.Classes,
|
|
System.SysUtils,
|
|
Xml.XMLDoc,
|
|
Xml.XMLIntf,
|
|
|
|
ProjectFile,
|
|
ProjectSaver,
|
|
RedistFiles;
|
|
|
|
{ TAppHttpServer }
|
|
|
|
procedure TAppHttpResponder.RespondHelp(doc: string; AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
var
|
|
path: string;
|
|
xml, xsl: IXMLDocument;
|
|
s: WideString;
|
|
begin
|
|
|
|
if FStandardTemplatePath = '' then
|
|
begin
|
|
FStandardTemplatePath := ExtractFilePath(ParamStr(0)) + 'locale\' + 'en';
|
|
// I2595
|
|
|
|
if FileExists(FStandardTemplatePath + '\xml\help\contexthelp.xml') then
|
|
FStandardTemplatePath := FStandardTemplatePath + '\xml\help\'
|
|
else
|
|
begin
|
|
FStandardTemplatePath := ExtractFilePath(ParamStr(0)) + 'locale\' + 'en';
|
|
// I2595
|
|
if FileExists(FStandardTemplatePath + '\xml\help\contexthelp.xml') then
|
|
FStandardTemplatePath := FStandardTemplatePath + '\xml\help\'
|
|
else
|
|
FStandardTemplatePath := GetXMLTemplatePath + 'help\';
|
|
end;
|
|
end;
|
|
|
|
if (doc <> 'help/') and (doc <> 'help/index') then
|
|
begin
|
|
Respond404(AContext, ARequestInfo, AResponseInfo);
|
|
Exit;
|
|
end;
|
|
|
|
xml := TXMLDocument.Create(nil);
|
|
xml.ParseOptions := [poResolveExternals];
|
|
// I902 - resolve externals when loading XML files so locale.xml parses
|
|
xml.LoadFromFile(FStandardTemplatePath + 'contexthelp.xml');
|
|
|
|
xsl := TXMLDocument.Create(nil);
|
|
// xml.ParseOptions := [poResolveExternals]; // I902 - resolve externals when loading XML files so locale.xml parses
|
|
|
|
xsl.LoadFromFile(FStandardTemplatePath + 'help.xsl');
|
|
xml.Node.transformNode(xsl.Node, s);
|
|
|
|
AResponseInfo.ContentType := 'text/html; charset=UTF-8';
|
|
AResponseInfo.ContentText := s;
|
|
end;
|
|
|
|
procedure TAppHttpResponder.RespondProject(doc: string; AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
|
|
procedure RespondProjectFile;
|
|
var
|
|
path: string;
|
|
begin
|
|
if ARequestInfo.Params.IndexOfName('path') < 0 then
|
|
begin
|
|
AResponseInfo.ResponseNo := 400;
|
|
AResponseInfo.ResponseText := 'Missing parameter path';
|
|
Exit;
|
|
end;
|
|
|
|
path := ARequestInfo.Params.Values['path'];
|
|
|
|
if not FileExists(path) or not SameText(ExtractFileExt(path), '.kpj') then
|
|
begin
|
|
AResponseInfo.ResponseNo := 400;
|
|
AResponseInfo.ResponseText := 'Project file '+path+' does not exist.';
|
|
Exit;
|
|
end;
|
|
|
|
// Transform the .kpj
|
|
with TProject.Create(path) do
|
|
try
|
|
AResponseInfo.ContentType := 'text/html; charset=UTF-8';
|
|
AResponseInfo.ContentText := Render;
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure RespondIco;
|
|
var
|
|
path: string;
|
|
begin
|
|
if ARequestInfo.Params.IndexOfName('path') < 0 then
|
|
begin
|
|
AResponseInfo.ResponseNo := 400;
|
|
AResponseInfo.ResponseText := 'Missing parameter path';
|
|
Exit;
|
|
end;
|
|
|
|
path := ARequestInfo.Params.Values['path'];
|
|
|
|
if not FileExists(path) or (
|
|
not SameText(ExtractFileExt(path), '.ico') and
|
|
not SameText(ExtractFileExt(path), '.bmp')
|
|
) then
|
|
begin
|
|
AResponseInfo.ResponseNo := 400;
|
|
AResponseInfo.ResponseText := 'Image file '+path+' does not exist.';
|
|
Exit;
|
|
end;
|
|
|
|
RespondFile(path, AContext, ARequestInfo, AResponseInfo);
|
|
end;
|
|
|
|
procedure RespondResource;
|
|
begin
|
|
Delete(doc, 1, 4);
|
|
if (Pos('/', doc) > 0) or (Pos('\', doc) > 0) then
|
|
Respond404(AContext, ARequestInfo, AResponseInfo);
|
|
RespondFile(TProject.StandardTemplatePath + '/' + doc, AContext, ARequestInfo, AResponseInfo);
|
|
end;
|
|
|
|
procedure RespondState;
|
|
var
|
|
displayState, path: string;
|
|
xmldoc: IXMLDocument;
|
|
FProject: TProject;
|
|
begin
|
|
with TStringStream.Create('', TEncoding.UTF8) do
|
|
try
|
|
CopyFrom(ARequestInfo.PostStream, 0);
|
|
displayState := DataString;
|
|
finally
|
|
Free;
|
|
end;
|
|
|
|
try
|
|
xmldoc := LoadXMLData(displayState);
|
|
except
|
|
on E:Exception do
|
|
begin
|
|
Respond404(AContext, ARequestInfo, AResponseInfo);
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
path := xmldoc.DocumentElement.ChildValues['path'];
|
|
|
|
// Saving state
|
|
|
|
if not FileExists(path) or not SameText(ExtractFileExt(path), '.kpj') then
|
|
begin
|
|
AResponseInfo.ResponseNo := 400;
|
|
AResponseInfo.ResponseText := 'Project file '+path+' does not exist.';
|
|
Exit;
|
|
end;
|
|
|
|
FProject := TProject.Create(path, False);
|
|
try
|
|
FProject.DisplayState := displayState;
|
|
//TODO: file in-use conflicts
|
|
with TProjectSaver.Create(FProject, path) do
|
|
try
|
|
SaveUser;
|
|
finally
|
|
Free;
|
|
end;
|
|
finally
|
|
FProject.Free;
|
|
end;
|
|
end;
|
|
begin
|
|
//
|
|
// http://localhost:8008/app/project/(index)?path=<fqp-to-.kpj>
|
|
// http://localhost:8008/app/project/<resource>
|
|
//
|
|
Delete(doc, 1, 8);
|
|
if (doc = '') or (doc = 'index') then
|
|
RespondProjectFile
|
|
else if doc = 'ico' then
|
|
RespondIco
|
|
else if Copy(doc, 1, 4) = 'res/' then
|
|
RespondResource
|
|
else if doc = 'state' then
|
|
RespondState;
|
|
end;
|
|
|
|
procedure TAppHttpResponder.ProcessRequest(AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
var
|
|
doc: string;
|
|
begin
|
|
// We know the request starts with /app/
|
|
doc := ARequestInfo.Document;
|
|
Delete(doc, 1, 5); // /app/
|
|
|
|
// App requests are only accepted from local computer
|
|
// So test that the request comes from localhost
|
|
if (AContext.Binding.PeerIP <> '127.0.0.1') and
|
|
(AContext.Binding.PeerIP <> '0:0:0:0:0:0:0:1') then
|
|
begin
|
|
AResponseInfo.ResponseNo := 403;
|
|
AResponseInfo.ResponseText := 'Access denied';
|
|
Exit;
|
|
end;
|
|
|
|
if Copy(doc, 1, 8) = 'project/' then
|
|
begin
|
|
RespondProject(doc, AContext, ARequestInfo, AResponseInfo);
|
|
Exit;
|
|
end
|
|
else if Copy(doc, 1, 5) = 'help/' then
|
|
begin
|
|
RespondHelp(doc, AContext, ARequestInfo, AResponseInfo);
|
|
Exit;
|
|
end;
|
|
|
|
Respond404(AContext, ARequestInfo, AResponseInfo);
|
|
end;
|
|
|
|
end.
|