mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 09:25:33 +00:00
907 lines
24 KiB
ObjectPascal
907 lines
24 KiB
ObjectPascal
unit Keyman.Developer.System.HttpServer.Debugger;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Classes,
|
|
System.Generics.Collections,
|
|
System.SyncObjs,
|
|
Winapi.Windows,
|
|
|
|
IdContext,
|
|
IdCustomHTTPServer,
|
|
IdHTTPServer,
|
|
|
|
Keyman.System.HttpServer.Base,
|
|
|
|
KeyboardFonts;
|
|
|
|
type
|
|
TWebDebugKeyboardInfo = class // I4063
|
|
strict private
|
|
FID: string;
|
|
FPath: string;
|
|
FVersion: string; // I4260
|
|
FFontName: TKeyboardFontArray; // I4409
|
|
FFontData: array[TKeyboardFont] of TStream; // I4409
|
|
procedure LoadFontData(const AFontName: string; Data: TStream);
|
|
function GetStoredFilename: string;
|
|
function GetJSONFilename: string; // I4409
|
|
function GetFontData(Index: TKeyboardFont): TStream; // I4409
|
|
function GetFontName(Index: TKeyboardFont): string;
|
|
function GetWebFilename: string;
|
|
function GetName: string;
|
|
public
|
|
constructor Create(const AFilename, AVersion: string; AFonts: TKeyboardFontArray); // I4409
|
|
destructor Destroy; override;
|
|
property ID: string read FID;
|
|
property Name: string read GetName;
|
|
property StoredFilename: string read GetStoredFilename;
|
|
property WebFilename: string read GetWebFilename;
|
|
property Version: string read FVersion;
|
|
property JSONFilename: string read GetJSONFilename; // I4260
|
|
property FontName[Index: TKeyboardFont]: string read GetFontName; // I4409
|
|
property FontData[Index: TKeyboardFont]: TStream read GetFontData; // I4409
|
|
end;
|
|
|
|
TWebDebugPackageInfo = class
|
|
strict private
|
|
FFilename: string;
|
|
FName: string;
|
|
public
|
|
constructor Create(const AFilename, AName: string);
|
|
property Filename: string read FFilename;
|
|
property Name: string read FName;
|
|
end;
|
|
|
|
TWebDebugModelInfo = class
|
|
strict private
|
|
FFilename: string;
|
|
public
|
|
constructor Create(const AFilename: string);
|
|
property Filename: string read FFilename;
|
|
end;
|
|
|
|
TDebuggerHttpResponder = class(TBaseHttpResponder)
|
|
private
|
|
FModelsCS, FKeyboardsCS, FPackagesCS: TCriticalSection; // I4036
|
|
FModels: TObjectDictionary<string,TWebDebugModelInfo>;
|
|
FKeyboards: TObjectDictionary<string,TWebDebugKeyboardInfo>; // I4063
|
|
FPackages: TObjectDictionary<string,TWebDebugPackageInfo>;
|
|
function GetKeyboardStoredFileName(const WebFilename: string): string;
|
|
function GetPackageStoredFileName(const WebFilename: string): string;
|
|
function GetModelStoredFileName(const WebFilename: string): string;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure RegisterKeyboard(const Filename, Version: string; FontInfo: TKeyboardFontArray); // I4063 // I4409
|
|
procedure UnregisterKeyboard(const Filename: string);
|
|
procedure RegisterPackage(const Filename, Name: string);
|
|
procedure UnregisterPackage(const Filename: string);
|
|
procedure RegisterModel(const Filename: string);
|
|
procedure UnregisterModel(const Filename: string);
|
|
|
|
procedure ProcessRequest(AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.DateUtils,
|
|
System.JSON,
|
|
System.StrUtils,
|
|
System.SysUtils,
|
|
System.TimeSpan,
|
|
System.TypInfo,
|
|
Vcl.Graphics,
|
|
|
|
RegExpr,
|
|
IdGlobalProtocols,
|
|
|
|
JsonUtil,
|
|
KeymanDeveloperOptions,
|
|
RedistFiles,
|
|
Upload_Settings;
|
|
|
|
const
|
|
TestFontName: array[TKeyboardFont] of string = ( // I4409
|
|
'code', 'char', 'osk', 'touch-phone', 'touch-tablet', 'touch-desktop'
|
|
);
|
|
|
|
function IsStandardFont(const FontName: string): Boolean; // I4448
|
|
const
|
|
StandardFontNames: array[0..9] of string = (
|
|
'Arial', 'Calibri', 'Consolas', 'Courier New', 'Lucida Console', 'Lucida Sans Unicode', 'Segoe UI', 'Tahoma', 'Times New Roman', 'Verdana'
|
|
);
|
|
begin
|
|
Result := AnsiIndexText(FontName, StandardFontNames) >= 0;
|
|
end;
|
|
|
|
{ TDebuggerHttpServer }
|
|
|
|
constructor TDebuggerHttpResponder.Create;
|
|
begin
|
|
FKeyboardsCS := TCriticalSection.Create; // I4036
|
|
FKeyboards := TObjectDictionary<string,TWebDebugKeyboardInfo>.Create; // I4063
|
|
|
|
FPackagesCS := TCriticalSection.Create;
|
|
FPackages := TObjectDictionary<string,TWebDebugPackageInfo>.Create; // I4063
|
|
|
|
FModelsCS := TCriticalSection.Create;
|
|
FModels := TObjectDictionary<string,TWebDebugModelInfo>.Create; // I4063
|
|
end;
|
|
|
|
destructor TDebuggerHttpResponder.Destroy;
|
|
begin
|
|
FreeAndNil(FKeyboards);
|
|
FreeAndNil(FKeyboardsCS); // I4036
|
|
|
|
FreeAndNil(FPackages);
|
|
FreeAndNil(FPackagesCS); // I4036
|
|
|
|
FreeAndNil(FModels);
|
|
FreeAndNil(FModelsCS); // I4036
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TDebuggerHttpResponder.GetKeyboardStoredFileName(
|
|
const WebFilename: string): string;
|
|
begin
|
|
FKeyboardsCS.Enter; // I4036
|
|
try
|
|
if FKeyboards.ContainsKey(WebFilename) then Result := FKeyboards[WebFilename].StoredFilename
|
|
else Result := '';
|
|
finally
|
|
FKeyboardsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
function TDebuggerHttpResponder.GetModelStoredFileName(
|
|
const WebFilename: string): string;
|
|
begin
|
|
FModelsCS.Enter; // I4036
|
|
try
|
|
if FModels.ContainsKey(WebFilename) then Result := FModels[WebFilename].Filename
|
|
else Result := '';
|
|
finally
|
|
FModelsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
function TDebuggerHttpResponder.GetPackageStoredFileName(
|
|
const WebFilename: string): string;
|
|
begin
|
|
FPackagesCS.Enter; // I4036
|
|
try
|
|
if FPackages.ContainsKey(WebFilename) then Result := FPackages[WebFilename].Filename
|
|
else Result := '';
|
|
finally
|
|
FPackagesCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebuggerHttpResponder.ProcessRequest(AContext: TIdContext;
|
|
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
|
|
|
|
procedure Respond404;
|
|
begin
|
|
AResponseInfo.ResponseNo := 404;
|
|
AResponseInfo.ResponseText := 'File not found';
|
|
end;
|
|
|
|
procedure RespondProject;
|
|
begin
|
|
AResponseInfo.ContentText := 'ooh';
|
|
end;
|
|
|
|
procedure RespondKeyboardsCSS; // I4063
|
|
var
|
|
key, name: string;
|
|
value: TWebDebugKeyboardInfo;
|
|
response: string;
|
|
i: TKeyboardFont; // I4409
|
|
begin
|
|
// list dynamic fonts
|
|
response := '/* Dynamic fonts */';
|
|
|
|
FKeyboardsCS.Enter;
|
|
try
|
|
for key in FKeyboards.Keys do
|
|
begin
|
|
name := ChangeFileExt(key, '');
|
|
value := FKeyboards[key];
|
|
|
|
for i := Low(TKeyboardFont) to High(TKeyboardFont) do // I4409
|
|
if (value.FontName[i] <> '') and not IsStandardFont(value.FontName[i]) then // I4448
|
|
response := response +
|
|
'@font-face { '#13#10+
|
|
' font-family: "'+value.FontName[i]+'";'#13#10+
|
|
' src: local("'+value.FontName[i]+'"),'#13#10+
|
|
' url("/font/'+name+'-'+TestFontName[i]+'.ttf") format("truetype");'#13#10+
|
|
' font-weight: normal;'#13#10+
|
|
' font-style: normal;'#13#10+
|
|
'}'#13#10#13#10;
|
|
|
|
end;
|
|
finally
|
|
FKeyboardsCS.Leave;
|
|
end;
|
|
|
|
AResponseInfo.ContentType := 'text/css';
|
|
AResponseInfo.CharSet := 'UTF-8'; // I4449
|
|
AResponseInfo.ContentText := response;
|
|
|
|
// Keyboard list always expire immediately
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1);
|
|
AResponseInfo.CacheControl := 'no-cache, no-store';
|
|
AResponseInfo.LastModified := Now;
|
|
end;
|
|
|
|
procedure RespondKeyboardsJS;
|
|
var
|
|
name: string;
|
|
key: string;
|
|
response: string;
|
|
value: TWebDebugKeyboardInfo;
|
|
id, src: string;
|
|
n: Integer;
|
|
srcVersion: string;
|
|
model: string;
|
|
begin
|
|
// Get dynamic keyboard registration
|
|
|
|
response :=
|
|
'var debugKeyboards = [];'#13#10+
|
|
'(function() {'#13#10+
|
|
' var kmw=KeymanWeb;'#13#10;
|
|
|
|
FKeyboardsCS.Enter; // I4036
|
|
try
|
|
for key in FKeyboards.Keys do
|
|
begin
|
|
src := ChangeFileExt(key, ''); // I4140
|
|
n := Pos('-', src);
|
|
if n > 0 then
|
|
begin
|
|
name := Copy(src, 1, n-1);
|
|
srcVersion := Copy(src, n+1, MaxInt);
|
|
end
|
|
else
|
|
begin
|
|
name := src;
|
|
srcVersion := '1.0';
|
|
end;
|
|
|
|
value := FKeyboards[key];
|
|
response := response + ' kmw.KRS({'+#13#10+
|
|
' KN:"'+name+'",'#13#10+
|
|
' KI:"Keyboard_'+name+'",'#13#10+
|
|
' KL:"'+name+'",'#13#10+
|
|
' KLC:"en",'#13#10+
|
|
' KR:"Europe",'#13#10+
|
|
' KRC:"eu",'#13#10+
|
|
' KFont:{family:"'+value.FontName[kfontChar]+'"},'#13#10+ // I4063 // I4409
|
|
' KOskFont:{family:"'+value.FontName[kfontOSK]+'"},'#13#10+ // I4063 // I4409
|
|
' KF:"'+src+'.js"'#13#10+ // I4140
|
|
' });'#13#10+
|
|
' debugKeyboards["'+name+'"] = {'+#13#10+ // I4260
|
|
' id:"'+name+'",'#13#10+
|
|
' version:"'+srcVersion+'"'#13#10+
|
|
' };';
|
|
end;
|
|
finally
|
|
FKeyboardsCS.Leave;
|
|
end;
|
|
|
|
FModelsCS.Enter;
|
|
try
|
|
for model in FModels.Keys do
|
|
begin
|
|
id := ChangeFileExt(model, '');
|
|
response := response + Format('registerModel("%s", "%s");', [id, model]);
|
|
end;
|
|
finally
|
|
FModelsCS.Leave;
|
|
end;
|
|
|
|
response := response + '})();';
|
|
|
|
AResponseInfo.CharSet := 'UTF-8';
|
|
AResponseInfo.ContentType := 'application/javascript';
|
|
AResponseInfo.ContentText := response;
|
|
|
|
// Keyboard list always expire immediately
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1); // I4037
|
|
AResponseInfo.CacheControl := 'no-cache, no-store'; // I4037
|
|
AResponseInfo.LastModified := Now; // I4037
|
|
end;
|
|
|
|
procedure RespondPackagesJSON;
|
|
var
|
|
json: TJSONObject;
|
|
jsonArray: TJSONArray;
|
|
jsonPackage: TJSONObject;
|
|
key: string;
|
|
jsonUrls: TJSONObject;
|
|
begin
|
|
// Get dynamic keyboard registration
|
|
FPackagesCS.Enter; // I4036
|
|
try
|
|
|
|
json := TJSONObject.Create;
|
|
try
|
|
try
|
|
jsonArray := TJSONArray.Create;
|
|
|
|
for key in FPackages.Keys do
|
|
begin
|
|
jsonPackage := TJSONObject.Create;
|
|
jsonPackage.AddPair('id', key);
|
|
jsonPackage.AddPair('filename', FPackages[key].Filename);
|
|
jsonPackage.AddPair('name', FPackages[key].Name);
|
|
jsonArray.Add(jsonPackage);
|
|
end;
|
|
|
|
json.AddPair('packages', jsonArray);
|
|
|
|
jsonUrls := TJSONObject.Create;
|
|
jsonUrls.AddPair('installLinkAndroid', MakeKeymanURL(URLPath_KeymanDeveloper_KeymanForAndroidDownload));
|
|
jsonUrls.AddPair('installLinkIos', MakeKeymanURL(URLPath_KeymanDeveloper_KeymanForIosDownload));
|
|
json.AddPair('urls', jsonUrls);
|
|
|
|
AResponseInfo.CharSet := 'UTF-8';
|
|
AResponseInfo.ContentType := 'application/json';
|
|
AResponseInfo.ContentText := JSONToString(json, True);
|
|
|
|
// Keyboard JSON always expire immediately
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1);
|
|
AResponseInfo.CacheControl := 'no-cache, no-store';
|
|
AResponseInfo.LastModified := Now;
|
|
finally
|
|
json.Free;
|
|
end;
|
|
except
|
|
on E:Exception do
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
end;
|
|
finally
|
|
FPackagesCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure RespondFont(const src: string); // I4063
|
|
var
|
|
name, key: string;
|
|
value: TWebDebugKeyboardInfo;
|
|
FontDataStream: TStream;
|
|
Found: Boolean;
|
|
I: TKeyboardFont;
|
|
begin
|
|
FontDataStream := nil;
|
|
FKeyboardsCS.Enter;
|
|
try
|
|
for key in FKeyboards.Keys do
|
|
begin
|
|
name := ChangeFileExt(key, '');
|
|
value := FKeyboards[key];
|
|
Found := False;
|
|
for I := Low(TKeyboardFont) to High(TKeyboardFont) do // I4409
|
|
if name+'-'+TestFontName[I]+'.ttf' = src then
|
|
begin
|
|
FontDataStream := value.FontData[I];
|
|
Found := True;
|
|
Break;
|
|
end;
|
|
if Found then
|
|
Break;
|
|
end;
|
|
|
|
if Assigned(FontDataStream) then
|
|
begin
|
|
AResponseInfo.ContentType := 'application/octet-stream';
|
|
AResponseInfo.ContentLength := FontDataStream.Size;
|
|
//AResponseInfo.LastModified := GetFileDate(doc);
|
|
AResponseInfo.WriteHeader;
|
|
|
|
AContext.Connection.IOHandler.Write(FontDataStream);
|
|
end
|
|
else
|
|
Respond404;
|
|
|
|
finally
|
|
FKeyboardsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
//
|
|
// Modified from System.DateUtils.DateToISO8601
|
|
// This does ISO8601 Date+Time+TZ without msec, which
|
|
// is what Swift 4.0 demands...
|
|
//
|
|
function FormatFinnickyISO8601Date(const ADate: TDateTime; AInputIsUTC: Boolean = true): string;
|
|
const
|
|
SDateFormat: string = 'yyyy''-''mm''-''dd''T''hh'':''nn'':''ss''Z'''; { Do not localize }
|
|
SOffsetFormat: string = '%s%s%.02d:%.02d'; { Do not localize }
|
|
Neg: array[Boolean] of string = ('+', '-'); { Do not localize }
|
|
var
|
|
Bias: Integer;
|
|
TimeZone: TTimeZone;
|
|
begin
|
|
Result := FormatDateTime(SDateFormat, ADate);
|
|
if not AInputIsUTC then
|
|
begin
|
|
TimeZone := TTimeZone.Local;
|
|
Bias := Trunc(TimeZone.GetUTCOffset(ADate).Negate.TotalMinutes);
|
|
if Bias <> 0 then
|
|
begin
|
|
// Remove the Z, in order to add the UTC_Offset to the string.
|
|
SetLength(Result, Result.Length - 1);
|
|
Result := Format(SOffsetFormat, [Result, Neg[Bias > 0], Abs(Bias) div MinsPerHour,
|
|
Abs(Bias) mod MinsPerHour]);
|
|
end
|
|
end;
|
|
end;
|
|
|
|
procedure RespondKeyboardJson(filename: string); // I4260
|
|
var
|
|
JSON: TJSONObject;
|
|
jsonOptions: TJSONObject;
|
|
value: TWebDebugKeyboardInfo;
|
|
FKeyboardBaseUri: string;
|
|
FFontBaseUri: string;
|
|
jsonKeyboard: TJSONObject;
|
|
jsonLanguages: TJSONArray;
|
|
jsonLanguage, jsonFont, jsonOskFont: TJSONObject;
|
|
begin
|
|
if SameText(Copy(filename, Length(filename)-4, 5), '.json') then // I4437
|
|
begin
|
|
Delete(filename, Length(filename)-1, 2); // look for .js
|
|
end;
|
|
|
|
FKeyboardsCS.Enter;
|
|
try
|
|
if not FKeyboards.ContainsKey(filename) then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
|
|
value := FKeyboards[filename];
|
|
|
|
// Build the JSON response
|
|
|
|
json := TJSONObject.Create;
|
|
try
|
|
jsonOptions := TJSONObject.Create;
|
|
jsonKeyboard := TJSONObject.Create;
|
|
|
|
json.AddPair('options', jsonOptions)
|
|
.AddPair('keyboard', jsonKeyboard);
|
|
|
|
FKeyboardBaseUri := Format('http://%s/keyboard/',[ARequestInfo.Host]);
|
|
FFontBaseUri := Format('http://%s/font/',[ARequestInfo.Host]);
|
|
|
|
jsonOptions.AddPair('device', 'any');
|
|
jsonOptions.AddPair('keyboardBaseUri', FKeyboardBaseUri);
|
|
jsonOptions.AddPair('fontBaseUri', FFontBaseUri);
|
|
|
|
jsonFont := TJSONObject.Create;
|
|
jsonFont.AddPair('family', value.FontName[kfontChar]); // I4409
|
|
jsonFont.AddPair('source', value.ID+'-'+value.Version+'-'+TestFontName[kfontChar]+'.ttf'); // I4409
|
|
jsonKeyboard.AddPair('font', jsonFont);
|
|
|
|
jsonOskFont := TJSONObject.Create;
|
|
jsonOskFont.AddPair('family', value.FontName[kfontOSK]); // I4409
|
|
jsonOskFont.AddPair('source', value.ID+'-'+value.Version+'-'+TestFontName[kfontOSK]+'.ttf'); // I4409
|
|
jsonKeyboard.AddPair('oskFont', jsonOskFont);
|
|
|
|
jsonKeyboard.AddPair('id', value.ID);
|
|
jsonKeyboard.AddPair('name', value.Name);
|
|
jsonKeyboard.AddPair('filename', value.WebFilename);
|
|
jsonKeyboard.AddPair('version', value.Version);
|
|
jsonKeyboard.AddPair('lastModified', FormatFinnickyISO8601Date(Now, False));
|
|
|
|
jsonLanguages := TJSONArray.Create;
|
|
jsonKeyboard.AddPair('languages', jsonLanguages);
|
|
|
|
jsonLanguage := TJSONObject.Create;
|
|
jsonLanguage.AddPair('id', 'en');
|
|
jsonLanguage.AddPair('name', 'English');
|
|
jsonLanguages.Add(jsonLanguage);
|
|
|
|
try
|
|
AResponseInfo.CharSet := 'UTF-8';
|
|
AResponseInfo.ContentType := 'application/javascript';
|
|
AResponseInfo.ContentText := JSONToString(json, True);
|
|
|
|
// Keyboard JSON always expire immediately
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1);
|
|
AResponseInfo.CacheControl := 'no-cache, no-store';
|
|
AResponseInfo.LastModified := Now;
|
|
finally
|
|
json.Free;
|
|
end;
|
|
except
|
|
on E:Exception do
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
end;
|
|
finally
|
|
FKeyboardsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
doc: string;
|
|
FFileRegExp: TRegExpr;
|
|
FResourceFileRegExp: TRegExpr;
|
|
begin
|
|
// /keyboard/###.js -> looks up the list of currently testing keyboards
|
|
// everything else retrieved from xml/kmw/
|
|
doc := ARequestInfo.Document;
|
|
Delete(doc, 1, 1);
|
|
|
|
if doc = '' then
|
|
doc := 'index.html';
|
|
|
|
if doc = 'inc/keyboards.js' then
|
|
begin
|
|
RespondKeyboardsJS;
|
|
Exit;
|
|
end;
|
|
|
|
if doc = 'inc/keyboards.css' then // I4063
|
|
begin
|
|
RespondKeyboardsCSS;
|
|
Exit;
|
|
end;
|
|
|
|
if doc = 'inc/packages.json' then
|
|
begin
|
|
RespondPackagesJSON;
|
|
Exit;
|
|
end;
|
|
|
|
FFileRegExp := TRegExpr.Create; // I4036
|
|
FResourceFileRegExp := TRegExpr.Create; // I4036
|
|
try
|
|
FFileRegExp.Expression := '^[a-z0-9_.-]+$';
|
|
FFileRegExp.ModifierI := True;
|
|
|
|
FResourceFileRegExp.Expression := '^([a-z]+\/)*[a-z0-9_.-]+$';
|
|
FResourceFileRegExp.ModifierI := True;
|
|
|
|
if Copy(doc, 1, 9) = 'resource/' then
|
|
begin
|
|
Delete(doc, 1, 9);
|
|
|
|
if not FResourceFileRegExp.Exec(doc) then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
|
|
doc := GetXMLTemplatePath + 'kmw\resource\' + ReplaceText(doc, '/', '\');
|
|
end
|
|
else if Copy(doc, 1, 5) = 'font/' then // I4063
|
|
begin
|
|
Delete(doc, 1, 5);
|
|
RespondFont(doc);
|
|
end
|
|
else if Copy(doc, 1, 10) = 'kbinstall/' then // I4260
|
|
begin
|
|
Delete(doc, 1, 10);
|
|
|
|
// Keyboards always expire immediately
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1); // I4037
|
|
AResponseInfo.CacheControl := 'no-cache, no-store'; // I4037
|
|
AResponseInfo.LastModified := Now; // I4037
|
|
if not FFileRegExp.Exec(doc) then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
RespondKeyboardJson(doc);
|
|
Exit;
|
|
end
|
|
else if Copy(doc, 1, 8) = 'package/' then
|
|
begin
|
|
Delete(doc, 1, 8);
|
|
|
|
// Packages always expire immediately
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1); // I4037
|
|
AResponseInfo.CacheControl := 'no-cache, no-store'; // I4037
|
|
AResponseInfo.LastModified := Now; // I4037
|
|
|
|
if not FFileRegExp.Exec(doc) then
|
|
begin
|
|
// We don't allow files with invalid names to be sent to
|
|
// Android and iOS. TODO: We should probably consider giving
|
|
// a friendly error :)
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
doc := GetPackageStoredFileName(doc);
|
|
if doc = '' then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
end
|
|
else if Copy(doc, 1, 9) = 'keyboard/' then
|
|
begin
|
|
Delete(doc, 1, 9);
|
|
|
|
// Keyboards always expire immediately
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1); // I4037
|
|
AResponseInfo.CacheControl := 'no-cache, no-store'; // I4037
|
|
AResponseInfo.LastModified := Now; // I4037
|
|
if not FFileRegExp.Exec(doc) then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
doc := GetKeyboardStoredFileName(doc);
|
|
if doc = '' then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
end
|
|
else if Copy(doc, 1, 6) = 'model/' then
|
|
begin
|
|
Delete(doc, 1, 6);
|
|
|
|
// Models always expire immediately
|
|
AResponseInfo.ContentType := 'application/javascript';
|
|
AResponseInfo.Expires := EncodeDate(1990, 1, 1); // I4037
|
|
AResponseInfo.CacheControl := 'no-cache, no-store'; // I4037
|
|
AResponseInfo.LastModified := Now; // I4037
|
|
if not FFileRegExp.Exec(doc) then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
doc := GetModelStoredFileName(doc);
|
|
if doc = '' then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
if not FFileRegExp.Exec(doc) then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
|
|
doc := GetXMLTemplatePath + 'kmw\' + doc;
|
|
end;
|
|
|
|
if not FileExists(doc) then
|
|
begin
|
|
Respond404;
|
|
Exit;
|
|
end;
|
|
|
|
// Serve the file
|
|
|
|
if AResponseInfo.ContentType = '' then
|
|
begin
|
|
AResponseInfo.HTTPServer.MIMETable.LoadTypesFromOS := False;
|
|
AResponseInfo.ContentType := AResponseInfo.HTTPServer.MIMETable.GetFileMIMEType(doc);
|
|
end;
|
|
AResponseInfo.CharSet := 'UTF-8';
|
|
AResponseInfo.ContentLength := FileSizeByName(doc);
|
|
//AResponseInfo.LastModified := GetFileDate(doc);
|
|
AResponseInfo.WriteHeader;
|
|
|
|
AContext.Connection.IOHandler.WriteFile(doc);
|
|
finally
|
|
FreeAndNil(FFileRegExp); // I4036
|
|
FreeAndNil(FResourceFileRegExp); // I4036
|
|
end;
|
|
end;
|
|
|
|
procedure TDebuggerHttpResponder.RegisterKeyboard(const Filename, Version: string; FontInfo: TKeyboardFontArray); // I4063 // I4409
|
|
var
|
|
k: TWebDebugKeyboardInfo;
|
|
begin
|
|
k := TWebDebugKeyboardInfo.Create(Filename, Version, FontInfo);
|
|
FKeyboardsCS.Enter; // I4036
|
|
try
|
|
FKeyboards.AddOrSetValue(k.WebFilename, k); // I4063
|
|
finally
|
|
FKeyboardsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebuggerHttpResponder.RegisterModel(const Filename: string);
|
|
var
|
|
m: TWebDebugModelInfo;
|
|
begin
|
|
m := TWebDebugModelInfo.Create(Filename);
|
|
FModelsCS.Enter;
|
|
try
|
|
FModels.AddOrSetValue(ExtractFileName(Filename), m);
|
|
finally
|
|
FModelsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebuggerHttpResponder.RegisterPackage(const Filename, Name: string);
|
|
var
|
|
p: TWebDebugPackageInfo;
|
|
begin
|
|
p := TWebDebugPackageInfo.Create(Filename, Name);
|
|
FPackagesCS.Enter;
|
|
try
|
|
FPackages.AddOrSetValue(ExtractFileName(Filename), p);
|
|
finally
|
|
FPackagesCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebuggerHttpResponder.UnregisterKeyboard(const Filename: string);
|
|
begin
|
|
FKeyboardsCS.Enter; // I4036
|
|
try
|
|
FKeyboards.Remove(ExtractFileName(Filename));
|
|
finally
|
|
FKeyboardsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebuggerHttpResponder.UnregisterModel(const Filename: string);
|
|
begin
|
|
FModelsCS.Enter; // I4036
|
|
try
|
|
FModels.Remove(ExtractFileName(Filename));
|
|
finally
|
|
FModelsCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure TDebuggerHttpResponder.UnregisterPackage(const Filename: string);
|
|
begin
|
|
FPackagesCS.Enter; // I4036
|
|
try
|
|
FPackages.Remove(ExtractFileName(Filename));
|
|
finally
|
|
FPackagesCS.Leave;
|
|
end;
|
|
end;
|
|
|
|
{ TWebDebugKeyboardInfo }
|
|
|
|
constructor TWebDebugKeyboardInfo.Create(const AFilename, AVersion: string; AFonts: TKeyboardFontArray); // I4409
|
|
var
|
|
I: TKeyboardFont;
|
|
begin
|
|
inherited Create;
|
|
FID := ChangeFileExt(ExtractFileName(AFilename), '');
|
|
FVersion := AVersion;
|
|
FPath := ExtractFilePath(AFilename);
|
|
for I := Low(TKeyboardFont) to High(TKeyboardFont) do
|
|
begin
|
|
FFontName[I] := AFonts[I];
|
|
if FFontName[I] <> '' then
|
|
begin
|
|
FFontData[I] := TMemoryStream.Create;
|
|
LoadFontData(FFontName[i], FFontData[i]);
|
|
end
|
|
else
|
|
FFontData[i] := nil;
|
|
end;
|
|
end;
|
|
|
|
destructor TWebDebugKeyboardInfo.Destroy; // I4063
|
|
var
|
|
I: TKeyboardFont;
|
|
begin
|
|
for I := Low(TKeyboardFont) to High(TKeyboardFont) do // I4409
|
|
FreeAndNil(FFontData[I]);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TWebDebugKeyboardInfo.GetStoredFilename: string;
|
|
begin
|
|
Result := FPath + FID + '.js';
|
|
end;
|
|
|
|
function TWebDebugKeyboardInfo.GetWebFilename: string;
|
|
begin
|
|
Result := FID + '-' + FVersion + '.js';
|
|
end;
|
|
|
|
function TWebDebugKeyboardInfo.GetFontData(Index: TKeyboardFont): TStream; // I4409
|
|
begin
|
|
Result := FFontData[Index];
|
|
while not Assigned(Result) do
|
|
begin
|
|
Dec(Index);
|
|
Result := FFontData[Index];
|
|
end;
|
|
end;
|
|
|
|
function TWebDebugKeyboardInfo.GetFontName(Index: TKeyboardFont): string; // I4409
|
|
begin
|
|
Result := FFontName[Index];
|
|
end;
|
|
|
|
function TWebDebugKeyboardInfo.GetJSONFilename: string;
|
|
begin
|
|
Result := FID + '-' + FVersion + '.json';
|
|
end;
|
|
|
|
function TWebDebugKeyboardInfo.GetName: string;
|
|
begin
|
|
Result := FID;
|
|
end;
|
|
|
|
procedure TWebDebugKeyboardInfo.LoadFontData(const AFontName: string;
|
|
Data: TStream); // I4063
|
|
var
|
|
sz: DWord;
|
|
FHDC: THandle;
|
|
Canvas: TCanvas;
|
|
begin
|
|
if AFontName = '' then
|
|
Exit;
|
|
|
|
Canvas := TCanvas.Create;
|
|
try
|
|
FHDC := GetDC(GetDesktopWindow);
|
|
try
|
|
Canvas.Handle := FHDC;
|
|
try
|
|
Canvas.Font.Name := AFontName;
|
|
Canvas.TextExtent('A'); // This forces a csFontValid // I4824
|
|
|
|
sz := Winapi.Windows.GetFontData(Canvas.Handle, 0, 0, nil, 0);
|
|
if sz = GDI_ERROR then Exit;
|
|
|
|
Data.Size := sz;
|
|
Data.Position := 0;
|
|
if Winapi.Windows.GetFontData(Canvas.Handle, 0, 0, (Data as TMemoryStream).Memory, sz) = GDI_ERROR then
|
|
begin
|
|
Data.Size := 0;
|
|
Exit;
|
|
end;
|
|
finally
|
|
Canvas.Handle := 0;
|
|
end;
|
|
finally
|
|
ReleaseDC(GetDesktopWindow, FHDC);
|
|
end;
|
|
finally
|
|
Canvas.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TWebDebugPackageInfo }
|
|
|
|
constructor TWebDebugPackageInfo.Create(const AFilename, AName: string);
|
|
begin
|
|
FFilename := AFilename;
|
|
FName := AName;
|
|
end;
|
|
|
|
{ TWebDebugModelInfo }
|
|
|
|
constructor TWebDebugModelInfo.Create(const AFilename: string);
|
|
begin
|
|
inherited Create;
|
|
FFilename := AFilename;
|
|
end;
|
|
|
|
end.
|