Page 1 of 1
Change response headers.
Posted: Sat May 29, 2021 12:49 pm
by michal@dorfin.waw.pl
I'd like to change some values in response headers before the Chrome will acknowledged them. I tried to do this in "OnResourceResponse" with this code:
Code: Select all
newHeaderMap := TCefStringMultimapOwn.Create;
TempHeaderMap := TCefStringMultimapOwn.Create;
aResponse.GetHeaderMap(TempHeaderMap);
i := 0;
j := TempHeaderMap.Size;
while (i < j) do
begin
key:=TempHeaderMap.Key[i];
value:=TempHeaderMap.Value[i];
if lowercase(key)='content-security-policy' then
BEGIN
value:='';
END;
newHeaderMap.Append(key, value);
inc(i);
end;
aResponse.SetHeaderMap(newHeaderMap);
Unfortunatelly the headers remain the same.
Is there any other possibility to change response headers.
Re: Change response headers.
Posted: Sat May 29, 2021 2:28 pm
by salvadordf
The documentation for that event states that the response parameter cannot be modified in that event :
https://magpcss.org/ceforum/apidocs3/projects/(default)/CefResourceRequestHandler.html
The CEF project maintainer suggests using an in-between proxy as the best option :
https://magpcss.org/ceforum/viewtopic.php?f=6&t=14042
Re: Change response headers.
Posted: Sat May 29, 2021 3:40 pm
by michal@dorfin.waw.pl
If found that I should use my own TCefResourceHandler.
So I tried this:
Code: Select all
{$I cef.inc}
interface
uses
System.Classes, WinApi.Windows, System.SysUtils,
uCEFInterfaces, uCEFTypes, uCEFResourceHandler,uCEFUrlrequestClient;
type
TA21CEFResourceHandler = class(TCefResourceHandlerOwn)
private
FOffset: NativeUInt;
FStream: TMemoryStream;
FCallback: ICefCallback;
FResponse: ICefResponse;
protected
function ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean; override;
procedure GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring); override;
function ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean; override;
public
constructor Create(const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest); reintroduce;
destructor Destroy; override;
procedure WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt); virtual;
procedure CompleteRequest(const Request: ICefUrlRequest); virtual;
end;
TA21HttpRequestClient = class(TCefUrlrequestClientOwn)
private
FHandler: TA21CEFResourceHandler;
protected
procedure OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt); override;
procedure OnRequestComplete(const Request: ICefUrlRequest); override;
public
constructor Create(Handler: TA21CEFResourceHandler); reintroduce;
end;
implementation
Uses uCefStringMultimap, uCEFUrlRequest;
constructor TA21CEFResourceHandler.Create(const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest);
begin
inherited Create(Browser, Frame, SchemeName, Request);
FStream := TMemoryStream.Create;
end;
destructor TA21CEFResourceHandler.Destroy;
begin
FStream.Free;
inherited;
end;
function TA21CEFResourceHandler.ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean;
begin
Result := True;
// reset the offset value
FOffset := 0;
// store the callback reference
FCallback := Callback;
// create the URL request that will perform actual data exchange (you can replace
// it with any other; e.g. with MSXML, or an Indy client)
TCefUrlRequestRef.New(Request, TA21HttpRequestClient.Create(Self), Nil);
end;
procedure TA21CEFResourceHandler.GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring);
var
HeaderMap: ICefStringMultimap;
begin
// return the size of the data we have in the response stream
ResponseLength := FStream.Size;
// fill the header fields from the response returned by the URL request
Response.Status := FResponse.Status;
Response.StatusText := FResponse.StatusText;
Response.MimeType := FResponse.MimeType;
// copy the header map from the response returned by the URL request
HeaderMap := TCefStringMultimapOwn.Create;
FResponse.GetHeaderMap(HeaderMap);
if HeaderMap.Size <> 0 then
FResponse.SetHeaderMap(HeaderMap);
end;
function TA21CEFResourceHandler.ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean;
begin
// since this method can be called multiple times (reading in chunks), check if we
// have still something to transfer
if FOffset < FStream.Size then
begin
Result := True;
BytesRead := BytesToRead;
// copy the data from the response stream to the browser buffer
Move(Pointer(NativeUInt(FStream.Memory) + FOffset)^, DataOut^, BytesRead);
// increment the offset by the amount of data we just copied
Inc(FOffset, BytesRead);
end
else
Result := False;
end;
procedure TA21CEFResourceHandler.WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt);
begin
// write the just downloaded data to the intermediate response stream
FStream.Write(Data^, Size);
end;
procedure TA21CEFResourceHandler.CompleteRequest(const Request: ICefUrlRequest);
begin
FStream.Position := 0;
// store the response reference for the GetResponseHeaders method
FResponse := Request.GetResponse;
// this signals the handler that the request has completed and that it can process
// the response headers and pass the content to the browser
if Assigned(FCallback) then
FCallback.Cont;
end;
{ TA21HttpRequestClient }
constructor TA21HttpRequestClient.Create(Handler: TA21CEFResourceHandler);
begin
inherited Create;
FHandler := Handler;
end;
procedure TA21HttpRequestClient.OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt);
begin
inherited;
FHandler.WriteResponse(Request, Data, DataLength);
end;
procedure TA21HttpRequestClient.OnRequestComplete(const Request: ICefUrlRequest);
begin
inherited;
FHandler.CompleteRequest(Request);
end;
And I'm using it like that way:
Code: Select all
procedure TMainForm.CHROMEGetResourceHandler(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const request: ICefRequest; var aResourceHandler: ICefResourceHandler);
begin
aResourceHandler:=TA21CEFResourceHandler.Create(browser, frame, 'TA21CEFResourceHandler', request);
end;
I this code I haven't changed any header yet. Just try to use this handler.
But when I did that I have a strange result when I open any website:
screen.png
Have I missed something in my handler ?
Re: Change response headers.
Posted: Sun May 30, 2021 7:44 am
by salvadordf
I haven't tested your code but it looks like there's an issue copying the data to/from the stream or perhaps a string encoding issue.
Write the stream contents in a local file to compare them with the original and check the "Content-Encoding" and "Content-Type" headers.
Re: Change response headers.
Posted: Sun May 30, 2021 8:15 am
by michal@dorfin.waw.pl
You are right. There was a bug in GetResponseHeaders.
Here is code that works better:
Code: Select all
unit Klasy.A21CEFResourceHandler;
{$I cef.inc}
interface
uses
System.Classes, WinApi.Windows, System.SysUtils,
uCEFInterfaces, uCEFTypes, uCEFResourceHandler,uCEFUrlrequestClient, uCEFConstants;
type
TA21CEFResourceHandler = class(TCefResourceHandlerOwn)
private
FStream: TMemoryStream;
FCallback: ICefCallback;
FResponse: ICefResponse;
protected
function ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean; override;
procedure GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring); override;
function ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean; override;
public
constructor Create(const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest); reintroduce;
destructor Destroy; override;
procedure WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt); virtual;
procedure CompleteRequest(const Request: ICefUrlRequest); virtual;
end;
TA21HttpRequestClient = class(TCefUrlrequestClientOwn)
private
FHandler: TA21CEFResourceHandler;
protected
procedure OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt); override;
procedure OnRequestComplete(const Request: ICefUrlRequest); override;
public
constructor Create(Handler: TA21CEFResourceHandler); reintroduce;
end;
implementation
Uses uCefStringMultimap, uCEFUrlRequest;
constructor TA21CEFResourceHandler.Create(const Browser: ICefBrowser; const Frame: ICefFrame; const SchemeName: ustring; const Request: ICefRequest);
begin
inherited Create(Browser, Frame, SchemeName, Request);
FStream := TMemoryStream.Create;
end;
destructor TA21CEFResourceHandler.Destroy;
begin
FStream.Free;
inherited;
end;
function TA21CEFResourceHandler.ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean;
begin
Result := True;
if (FStream <> nil) then FStream.Seek(0, soFromBeginning);
Request.Flags:=UR_FLAG_ALLOW_STORED_CREDENTIALS;// and UR_FLAG_DISABLE_CACHE and UR_FLAG_SKIP_CACHE;
// store the callback reference
FCallback := Callback;
// create the URL request that will perform actual data exchange (you can replace
// it with any other; e.g. with MSXML, or an Indy client)
TCefUrlRequestRef.New(Request, TA21HttpRequestClient.Create(Self), Nil);
end;
procedure TA21CEFResourceHandler.GetResponseHeaders(const Response: ICefResponse; out ResponseLength: Int64; out RedirectUrl: ustring);
var
HeaderMap: ICefStringMultimap;
begin
// return the size of the data we have in the response stream
ResponseLength := FStream.Size;
// fill the header fields from the response returned by the URL request
Response.Status := FResponse.Status;
Response.StatusText := FResponse.StatusText;
Response.MimeType := FResponse.MimeType;
Response.Charset :=FResponse.Charset;
Response.Error :=FResponse.Error;
Response.URL :=FResponse.URL;
// copy the header map from the response returned by the URL request
HeaderMap := TCefStringMultimapOwn.Create;
FResponse.GetHeaderMap(HeaderMap);
if HeaderMap.Size <> 0 then
Response.SetHeaderMap(HeaderMap);
end;
function TA21CEFResourceHandler.ReadResponse(const DataOut: Pointer; BytesToRead: Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean;
begin
if (FStream <> nil) and (DataOut <> nil) then
begin
BytesRead := FStream.Read(DataOut^, BytesToRead);
Result := (BytesRead > 0);
end
else
Result := False;
end;
procedure TA21CEFResourceHandler.WriteResponse(const Request: ICefUrlRequest; Data: Pointer; Size: NativeUInt);
begin
// write the just downloaded data to the intermediate response stream
FStream.Write(Data^, Size);
end;
procedure TA21CEFResourceHandler.CompleteRequest(const Request: ICefUrlRequest);
begin
FStream.Position := 0;
// store the response reference for the GetResponseHeaders method
FResponse := Request.GetResponse;
// this signals the handler that the request has completed and that it can process
// the response headers and pass the content to the browser
if Assigned(FCallback) then
FCallback.Cont;
end;
{ TA21HttpRequestClient }
constructor TA21HttpRequestClient.Create(Handler: TA21CEFResourceHandler);
begin
inherited Create;
FHandler := Handler;
end;
procedure TA21HttpRequestClient.OnDownloadData(const Request: ICefUrlRequest; Data: Pointer; DataLength: NativeUInt);
begin
inherited;
FHandler.WriteResponse(Request, Data, DataLength);
end;
procedure TA21HttpRequestClient.OnRequestComplete(const Request: ICefUrlRequest);
begin
inherited;
FHandler.CompleteRequest(Request);
end;
end.
I don't know how to handle redirects.
When I use my TA21CEFResourceHandler and navigate to : "facebook.pl" - which redirects to "https://facebook.com" - there is an error on FB site: "Cannot fullfill the request. We are working to solve the problem". When I navigete to this URL with my ResourceHandler disabled - everything works fine.
Re: Change response headers.
Posted: Sun May 30, 2021 3:37 pm
by Student
The request does not pass the security check, try this, add security headers
Code: Select all
function TA21CEFResourceHandler.ProcessRequest(const Request: ICefRequest; const Callback: ICefCallback): Boolean;
var FRequest: ICefRequest;
HeaderMapTemp: ICefStringMultimap;
begin
Result := True;
if (FStream <> nil) then FStream.Seek(0, soFromBeginning);
FRequest := TCefRequestRef.New;
FRequest.Flags:=UR_FLAG_ALLOW_STORED_CREDENTIALS;// and UR_FLAG_DISABLE_CACHE and UR_FLAG_SKIP_CACHE;
// store the callback reference
FCallback := Callback;
// create the URL request that will perform actual data exchange (you can replace
// it with any other; e.g. with MSXML, or an Indy client)
FRequest := TCefRequestRef.New;
HeaderMapTemp := TCefStringMultimapOwn.Create;
Request.GetHeaderMap(HeaderMapTemp);
HeaderMapTemp.Append('Sec-Fetch-Mode', 'navigate');
FRequest.SetReferrer(Request.ReferrerUrl, Request.ReferrerPolicy);
FRequest.Assign(Request.url, Request.Method, Request.PostData, HeaderMapTemp);
TCefUrlRequestRef.New(FRequest, TA21HttpRequestClient.Create(Self), Nil);
end;
Re: Change response headers.
Posted: Mon May 31, 2021 5:06 am
by michal@dorfin.waw.pl
Thank you. It is working for FB. But unfortunatelly doesn't help in other sites when You start from one url but there is redirection for login.
Maybe let's change the level of difficulty. I want only to change response headers. It can be done only in custom resource handler. Is there a way to override the resource handler with only the "GetResponseHeaders" method. So the rest of default resource engine is default ?
Re: Change response headers.
Posted: Mon May 31, 2021 8:45 am
by Student
Yes, for all sites not need set such a header, I showed an example of how you can get around the problem, filter problem sites that are loaded incorrectly by default by setting the desired header, view the headers through the sniffer as the site loads without ResourceHandler and replace or add those that are missing when loading via ResourceHandler.
If I find out another way to solve this problem, I will write.