mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 00:45:32 +00:00
Fixes #7810. This addresses a regression introduced in #7631, where URL parameters with spaces would be encoded into `+` instead of `%20`. Looking a bit deeper at the Delphi `TNetEncoding.URL.Encode` function I realised that it was entirely inadequate. Some guy named Marc Durdin wrote a blog a good few years ago about the problem, and that's what I ended up using. This encoding issue caused filenames with spaces (by default, project paths in Developer have spaces) to give a 404 when editing a touch layout, which meant that the touch keyboards could not be saved. Also fixes KEYMAN-DEVELOPER-74, where the + encoding caused multiple entries to appear in the filename cache. Amusing to Google this problem, find solid answer on SO, which pointed to my very own blog. Embarrassing that my own code didn't already include my own fix. A secondary issue is also fixed here, where request parameters were double-decoded for formencoded POST requests. The fix for broken URL encodings was only required for GET requests. This also showed up in KEYMAN-DEVELOPER-74, with double-encoded paths being registered as source files.
198 lines
5 KiB
ObjectPascal
198 lines
5 KiB
ObjectPascal
(*
|
|
Name: utilhttp
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 1 Aug 2006
|
|
|
|
Modified Date: 28 Aug 2014
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 01 Aug 2006 - mcdurdin - Refactor util functions into multiple units
|
|
23 Aug 2006 - mcdurdin - Use Unicode strings
|
|
06 Oct 2006 - mcdurdin - Add GetParamsFromURL function
|
|
25 May 2010 - mcdurdin - I1632 - Keyboard Options - form posting support for keyboard option pages
|
|
04 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support
|
|
04 May 2012 - mcdurdin - I3308 - V9.0 - Start to move towards Delphi namespaces
|
|
04 May 2012 - mcdurdin - I3309 - V9.0 - Migrate to Delphi XE2, VS2010, svn 1.7
|
|
28 Aug 2014 - mcdurdin - I4390 - V9.0 - Free vs Pro
|
|
*)
|
|
unit utilhttp; // I3306 // I3308 // I3309
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Classes,
|
|
System.SysUtils,
|
|
System.WideStrUtils,
|
|
System.NetEncoding;
|
|
|
|
function GetParamsFromURL(URL: WideString; var Params: TStringList): Boolean;
|
|
function GetParamsFromURLEx(URL: WideString; var Params: TStringList): Boolean;
|
|
procedure DecodeAndSetParams(const AValue: WideString; Params: TStringList);
|
|
function URLDecode(ASrc: ansistring): string; // I4390
|
|
function URLEncode(Asrc: string): string;
|
|
|
|
function ConvertFileURLToPath(s: WideString): WideString;
|
|
function ConvertPathToFileURL(s: WideString): WideString;
|
|
|
|
implementation
|
|
|
|
// Source:
|
|
// https://marc.durdin.net/2015/08/an-update-for-encodeuricomponent/
|
|
// Stack Overflow:
|
|
// https://stackoverflow.com/questions/776302/standard-url-encode-function
|
|
function URLEncode(ASrc: string): string;
|
|
const
|
|
HexMap: string = '0123456789ABCDEF';
|
|
|
|
function IsSafeChar(ch: Byte): Boolean;
|
|
begin
|
|
if (ch >= 48) and (ch <= 57) then Result := True // 0-9
|
|
else if (ch >= 65) and (ch <= 90) then Result := True // A-Z
|
|
else if (ch >= 97) and (ch <= 122) then Result := True // a-z
|
|
else if (ch = 33) then Result := True // !
|
|
else if (ch >= 39) and (ch <= 42) then Result := True // '()*
|
|
else if (ch >= 45) and (ch <= 46) then Result := True // -.
|
|
else if (ch = 95) then Result := True // _
|
|
else if (ch = 126) then Result := True // ~
|
|
else Result := False;
|
|
end;
|
|
|
|
var
|
|
I, J: Integer;
|
|
Bytes: TBytes;
|
|
begin
|
|
Result := '';
|
|
|
|
Bytes := TEncoding.UTF8.GetBytes(ASrc);
|
|
|
|
I := 0;
|
|
J := Low(Result);
|
|
|
|
SetLength(Result, Length(Bytes) * 3); // space to %xx encode every byte
|
|
|
|
while I < Length(Bytes) do
|
|
begin
|
|
if IsSafeChar(Bytes[I]) then
|
|
begin
|
|
Result[J] := Char(Bytes[I]);
|
|
Inc(J);
|
|
end
|
|
else
|
|
begin
|
|
Result[J] := '%';
|
|
Result[J+1] := HexMap[(Bytes[I] shr 4) + Low(HexMap)];
|
|
Result[J+2] := HexMap[(Bytes[I] and 15) + Low(HexMap)];
|
|
Inc(J,3);
|
|
end;
|
|
Inc(I);
|
|
end;
|
|
|
|
SetLength(Result, J-Low(Result));
|
|
end;
|
|
|
|
function URLDecode(ASrc: AnsiString): string;
|
|
begin
|
|
Result := TNetEncoding.URL.Decode(string(ASrc));
|
|
end;
|
|
|
|
procedure DecodeAndSetParams(const AValue: WideString; Params: TStringList);
|
|
var
|
|
i, j : Integer;
|
|
s: WideString;
|
|
begin
|
|
// Convert special characters
|
|
// ampersand '&' separates values {Do not Localize}
|
|
Params.BeginUpdate;
|
|
try
|
|
Params.Clear;
|
|
i := 1;
|
|
while i <= length(AValue) do
|
|
begin
|
|
j := i;
|
|
while (j <= length(AValue)) and (AValue[j] <> '&') do
|
|
begin
|
|
inc(j);
|
|
end;
|
|
s := copy(AValue, i, j-i);
|
|
// See RFC 1866 section 8.2.1. TP
|
|
s := StringReplace(s, '+', ' ', [rfReplaceAll]); {do not localize}
|
|
Params.Add(URLDecode(AnsiString(s)));
|
|
i := j + 1;
|
|
end;
|
|
finally
|
|
Params.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
function GetParamsFromURL(URL: WideString; var Params: TStringList): Boolean;
|
|
var
|
|
Command: WideString;
|
|
s: WideString;
|
|
n: Integer;
|
|
begin
|
|
s := URL;
|
|
if Copy(s,1,7) = 'keyman:' then
|
|
begin
|
|
params := TStringList.Create;
|
|
Delete(s,1,7);
|
|
n := Pos('?',s);
|
|
if n > 0 then
|
|
begin
|
|
Command := Copy(s,1,n-1);
|
|
DecodeAndSetParams(Copy(s,n+1,Length(s)), params);
|
|
end
|
|
else
|
|
Command := s;
|
|
params.Insert(0, command);
|
|
Result := True;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function GetParamsFromURLEx(URL: WideString; var Params: TStringList): Boolean;
|
|
var
|
|
Command: WideString;
|
|
s: WideString;
|
|
n: Integer;
|
|
begin
|
|
s := URL;
|
|
|
|
params := TStringList.Create;
|
|
Delete(s,1,7);
|
|
n := Pos('?',s);
|
|
if n > 0 then
|
|
begin
|
|
Command := Copy(s,1,n-1);
|
|
DecodeAndSetParams(Copy(s,n+1,Length(s)), params);
|
|
end
|
|
else
|
|
Command := s;
|
|
params.Insert(0, URL);
|
|
params.Insert(0, command);
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function ConvertFileURLToPath(s: WideString): WideString;
|
|
begin
|
|
s := URLDecode(AnsiString(s));
|
|
if Copy(s, 1, 8) = 'file:///'
|
|
then Result := WideReplaceText(Copy(s, 9, MAXINT), '/', '\')
|
|
else Result := s;
|
|
end;
|
|
|
|
function ConvertPathToFileURL(s: WideString): WideString;
|
|
begin
|
|
Result := 'file:///'+UrlEncode(WideReplaceText(s, '\', '/'));
|
|
end;
|
|
|
|
|
|
end.
|