2016-06-22 17:49:16 +02:00
// ***************************************************************************
//
// Delphi MVC Framework
//
2023-01-17 08:52:26 +01:00
// Copyright (c) 2010-2023 Daniele Teti and the DMVCFramework Team
2016-06-22 17:49:16 +02:00
//
// https://github.com/danieleteti/delphimvcframework
//
// ***************************************************************************
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// *************************************************************************** }
2015-12-22 12:38:17 +01:00
2014-10-03 11:40:57 +02:00
unit MVCFramework. Middleware. CORS;
2017-03-23 18:51:25 +01:00
{$I dmvcframework.inc}
2014-10-03 11:40:57 +02:00
interface
uses
2017-03-20 19:08:01 +01:00
System. StrUtils,
MVCFramework,
MVCFramework. Commons;
2014-10-03 11:40:57 +02:00
type
2019-01-08 12:48:27 +01:00
TMVCCORSDefaults = class sealed
public
const
ALLOWS_ORIGIN_URL = '*' ;
ALLOWS_CREDENTIALS = True ;
/// <summary>
/// The Access-Control-Expose-Headers response header indicates which headers can be exposed as part of the response by listing their names.
/// By default, only the 6 simple response headers are exposed: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma
/// If you want clients to be able to access other headers, you have to list them using the Access-Control-Expose-Headers header.
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
/// </summary>
EXPOSE_HEADERS = '' ;
/// <summary>
/// The Access-Control-Allow-Headers response header is used in response to a preflight request which includes the Access-Control-Request-Headers to indicate which HTTP headers can be used during the actual request.
/// The simple headers, Accept, Accept-Language, Content-Language, Content-Type (but only with a MIME type of its parsed value (ignoring parameters) of either application/x-www-form-urlencoded, multipart/form-data, or text/plain), are always available and don't need to be listed by this header.
/// This header is required if the request has an Access-Control-Request-Headers header.
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
/// </summary>
ALLOWS_HEADERS = 'Content-Type, Accept, jwtusername, jwtpassword, authentication, authorization' ;
/// <summary>
/// The Access-Control-Allow-Methods response header specifies the method or methods allowed when accessing the resource in response to a preflight request.
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
/// </summary>
ALLOWS_METHODS = 'POST,GET,OPTIONS,PUT,DELETE' ;
2023-08-16 11:40:36 +02:00
/// <summary>
/// Indicates the number of seconds (60 by default) the information provided by
/// the `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers` headers can be cached.
/// </summary>
ACCESS_CONTROL_MAX_AGE = 6 0 ;
2019-01-08 12:48:27 +01:00
end ;
2017-03-20 19:08:01 +01:00
2017-03-23 18:51:25 +01:00
TMVCCORSMiddleware = class( TInterfacedObject, IMVCMiddleware)
2023-08-15 21:25:46 +02:00
strict protected
2016-10-12 13:10:41 +02:00
FAllowedOriginURL: string ;
2023-08-15 21:25:46 +02:00
FAllowedOriginURLs: TArray< String > ;
2023-08-15 18:24:17 +02:00
FAllowsCredentials: Boolean ;
2019-01-08 12:48:27 +01:00
FAllowsMethods: string ;
FExposeHeaders: string ;
FAllowsHeaders: string ;
2023-08-16 11:40:36 +02:00
FAccessControlMaxAge: string ;
2017-03-20 19:08:01 +01:00
protected
2023-08-15 21:25:46 +02:00
function GetAllowedOriginURL( AContext: TWebContext) : String ; virtual ;
2023-08-16 11:40:36 +02:00
procedure FillCommonHeaders( AContext: TWebContext; const AAllowOrigin: String ) ; virtual ;
procedure HandlePreflightRequest( AContext: TWebContext; var AHandled: Boolean ) ; virtual ;
procedure HandleRequest( AContext: TWebContext; var AHandled: Boolean ) ; virtual ;
2023-08-15 21:25:46 +02:00
2017-03-23 18:51:25 +01:00
procedure OnBeforeRouting(
AContext: TWebContext;
var AHandled: Boolean
2023-08-15 18:24:17 +02:00
) ; virtual ;
2017-03-20 19:08:01 +01:00
2017-03-23 18:51:25 +01:00
procedure OnBeforeControllerAction(
AContext: TWebContext;
const AControllerQualifiedClassName: string ;
const AActionName: string ;
var AHandled: Boolean
2023-08-15 18:24:17 +02:00
) ; virtual ;
2017-03-23 18:51:25 +01:00
procedure OnAfterControllerAction(
AContext: TWebContext;
2022-08-13 00:21:00 +02:00
const AControllerQualifiedClassName: string ; const AActionName: string ;
2023-08-15 18:24:17 +02:00
const AHandled: Boolean ) ; virtual ;
2020-04-29 01:57:29 +02:00
procedure OnAfterRouting(
AContext: TWebContext;
const AHandled: Boolean
2023-08-15 18:24:17 +02:00
) ; virtual ;
2020-04-29 01:57:29 +02:00
2017-03-23 18:51:25 +01:00
public
2019-01-08 12:48:27 +01:00
constructor Create(
2023-08-15 21:25:46 +02:00
const AAllowedOriginURLs: string = TMVCCORSDefaults. ALLOWS_ORIGIN_URL;
2019-01-08 12:48:27 +01:00
const AAllowsCredentials: Boolean = TMVCCORSDefaults. ALLOWS_CREDENTIALS;
const AExposeHeaders: String = TMVCCORSDefaults. EXPOSE_HEADERS;
const AAllowsHeaders: String = TMVCCORSDefaults. ALLOWS_HEADERS;
2023-08-16 11:40:36 +02:00
const AAllowsMethods: string = TMVCCORSDefaults. ALLOWS_METHODS;
const AAccessControlMaxAge: Integer = TMVCCORSDefaults. ACCESS_CONTROL_MAX_AGE
2019-01-08 12:48:27 +01:00
) ; virtual ;
2016-06-22 17:49:16 +02:00
end ;
2014-10-03 11:40:57 +02:00
2017-03-23 18:51:25 +01:00
TCORSMiddleware = TMVCCORSMiddleware;
2014-10-03 11:40:57 +02:00
2017-03-23 18:51:25 +01:00
implementation
2016-10-12 13:10:41 +02:00
2023-08-15 21:25:46 +02:00
uses
2023-08-16 11:40:36 +02:00
System. SysUtils, System. Classes;
2023-08-15 21:25:46 +02:00
2017-03-23 18:51:25 +01:00
{ TMVCCORSMiddleware }
2014-10-03 11:40:57 +02:00
2019-01-08 12:48:27 +01:00
constructor TMVCCORSMiddleware. Create(
2023-08-15 21:25:46 +02:00
const AAllowedOriginURLs: string ;
2019-01-08 12:48:27 +01:00
const AAllowsCredentials: Boolean ;
const AExposeHeaders: String ;
const AAllowsHeaders: String ;
2023-08-16 11:40:36 +02:00
const AAllowsMethods: string ;
const AAccessControlMaxAge: Integer
2019-01-08 12:48:27 +01:00
) ;
2016-10-12 13:10:41 +02:00
begin
inherited Create;
2023-08-16 11:40:36 +02:00
FAllowedOriginURLs : = AAllowedOriginURLs. Split( [ ',' ] ) ;
2023-08-15 18:24:17 +02:00
FAllowsCredentials : = AAllowsCredentials;
2019-01-08 12:48:27 +01:00
FExposeHeaders : = AExposeHeaders;
FAllowsHeaders : = AAllowsHeaders;
FAllowsMethods : = AAllowsMethods;
2023-08-16 11:40:36 +02:00
FAccessControlMaxAge : = IntToStr( AAccessControlMaxAge) ;
end ;
procedure TMVCCORSMiddleware. FillCommonHeaders( AContext: TWebContext; const AAllowOrigin: String ) ;
var
lCustomHeaders: TStrings;
begin
lCustomHeaders : = AContext. Response. RawWebResponse. CustomHeaders;
lCustomHeaders. Values[ 'Access-Control-Allow-Origin' ] : = AAllowOrigin;
lCustomHeaders. Values[ 'Access-Control-Allow-Methods' ] : = FAllowsMethods;
lCustomHeaders. Values[ 'Access-Control-Allow-Headers' ] : = FAllowsHeaders;
lCustomHeaders. Values[ 'Access-Control-Max-Age' ] : = FAccessControlMaxAge;
2023-08-30 11:58:50 +02:00
if FAllowsCredentials then
begin
// Omit Access-Control-Allow-Credentials if <> true
// https://github.com/danieleteti/delphimvcframework/issues/679#issuecomment-1676535853
lCustomHeaders. Values[ 'Access-Control-Allow-Credentials' ] : = 'true' ;
end ;
2016-10-12 13:10:41 +02:00
end ;
2023-08-15 21:25:46 +02:00
function TMVCCORSMiddleware. GetAllowedOriginURL( AContext: TWebContext) : String ;
var
lRequestOrigin: string ;
lAllowed: String ;
begin
Result : = '' ;
lRequestOrigin : = AContext. Request. Headers[ 'Origin' ] ;
if lRequestOrigin < > '' then
begin
for var I : = Low( FAllowedOriginURLs) to High( FAllowedOriginURLs) do
begin
lAllowed : = FAllowedOriginURLs[ I] . Trim;
if SameText( lRequestOrigin, lAllowed) or ( lAllowed = '*' ) then
begin
Exit( lAllowed) ;
end ;
end ;
end ;
end ;
2023-08-16 11:40:36 +02:00
procedure TMVCCORSMiddleware. HandlePreflightRequest( AContext: TWebContext;
var AHandled: Boolean ) ;
var
lAllowOrigin: String ;
begin
// https://fetch.spec.whatwg.org/#cors-preflight-request
lAllowOrigin : = GetAllowedOriginURL( AContext) ;
AContext. Response. StatusCode : = HTTP_STATUS. NoContent;
if not lAllowOrigin. IsEmpty then
begin
FillCommonHeaders( AContext, lAllowOrigin) ;
end ;
AHandled : = True ;
end ;
procedure TMVCCORSMiddleware. HandleRequest( AContext: TWebContext;
var AHandled: Boolean ) ;
var
lAllowOrigin: String ;
lCustomHeaders: TStrings;
begin
// https://fetch.spec.whatwg.org/#http-responses
lAllowOrigin : = GetAllowedOriginURL( AContext) ;
if not lAllowOrigin. IsEmpty then
begin
FillCommonHeaders( AContext, lAllowOrigin) ;
lCustomHeaders : = AContext. Response. RawWebResponse. CustomHeaders;
lCustomHeaders. Values[ 'Access-Control-Expose-Headers' ] : = FExposeHeaders; {only for not preflight requests}
end ;
AHandled : = False ;
end ;
2022-08-13 00:21:00 +02:00
procedure TMVCCORSMiddleware. OnAfterControllerAction(
AContext: TWebContext;
const AControllerQualifiedClassName: string ; const AActionName: string ;
const AHandled: Boolean ) ;
2015-11-15 18:31:08 +01:00
begin
2017-03-23 18:51:25 +01:00
// Implement as needed
2015-11-15 18:31:08 +01:00
end ;
2020-04-29 01:57:29 +02:00
procedure TMVCCORSMiddleware. OnAfterRouting( AContext: TWebContext; const AHandled: Boolean ) ;
begin
// Implement as needed
end ;
2017-03-23 18:51:25 +01:00
procedure TMVCCORSMiddleware. OnBeforeControllerAction(
AContext: TWebContext; const AControllerQualifiedClassName,
AActionName: string ; var AHandled: Boolean ) ;
2014-10-03 11:40:57 +02:00
begin
2017-03-23 18:51:25 +01:00
// Implement as needed
2014-10-03 11:40:57 +02:00
end ;
2017-03-23 18:51:25 +01:00
procedure TMVCCORSMiddleware. OnBeforeRouting( AContext: TWebContext; var AHandled: Boolean ) ;
2014-10-03 11:40:57 +02:00
begin
2023-08-16 11:40:36 +02:00
if AContext. Request. HTTPMethod < > httpOPTIONS then
2023-08-15 18:24:17 +02:00
begin
2023-08-16 11:40:36 +02:00
//normal request, no preflight request
HandleRequest( AContext, AHandled) ;
end
else
2016-06-22 17:49:16 +02:00
begin
2023-08-16 11:40:36 +02:00
//preflight
HandlePreflightRequest( AContext, AHandled) ;
2016-06-22 17:49:16 +02:00
end ;
2014-10-03 11:40:57 +02:00
end ;
end .