Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to redirect http to https in TIdCustomHTTPServer
#1
Hello Guys,

I am trying to do a service that works under ssl, but that I redirect the http connections to https and I have not succeeded. Is it possible to do that?
I need to use port 9001 so I also can not bind on two different ports, so I need only redirect https:\\example.com to https:\\example.com.

My implementation:
Code:
 sslHandler := TIdServerIOHandlerSSLOpenSSL.Create(nil);
 sslHandler.SSLOptions.KeyFile := GetCurrentDir + '\certificados\private.key';
 sslHandler.SSLOptions.CertFile := GetCurrentDir + '\certificados\certificate.crt';
 sslHandler.SSLOptions.RootCertFile := GetCurrentDir + '\certificados\ca_bundle.crt';
 sslHandler.SSLOptions.Mode := sslmServer;
 sslHandler.SSLOptions.SSLVersions  := [sslvTLSv1, sslvSSLv2, sslvSSLv23, sslvSSLv3, sslvTLSv1_1, sslvTLSv1_2];
 sslHandler.SSLOptions.VerifyDepth  := 0;
 sslHandler.SSLOptions.VerifyMode   := [];
 sslHandler.OnGetPassword := IdServerIOHandlerSSLOpenSSLGetPassword;
 
 Server.OnConnect := onConect;
 Server.IOHandler := sslHandler;

and 

Code:
if (AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase) then
   TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough:= False;


Thank you!
Reply
#2
(06-08-2018, 02:56 PM)ermesoml Wrote: I am trying to do a service that works under ssl, but that I redirect the http connections to https and I have not succeeded. Is it possible to do that?

Of course. In your OnCommand... event handler(s), if you detect a request for an HTTP url, you can use the AResponseInfo.Redirect() method to redirect to an HTTPS url.

This would be very easy to do if you were using separate ports for HTTP and HTTPS, eg:

Code:
const
  cHTTPSPort: TIdPort = ...; // HTTPS port

// TIdHTTPServer.OnQuerySSLPort event handler
procedure TMyForm.onQuerySslPort(APort: TIdPort; var VUseSSL: Boolean);
begin
  VUseSSL := (Port = cHTTPSPort);
end;

// TIdHTTPServer.OnCommandGet event handler
procedure TMyForm.onCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
  Uri: TIdURI;
begin
  if AContext.Binding.Port <> cHTTPSPort then
  begin
    Uri := TIdURI.Create;
    try
      Uri.Protocol := 'https';
      Uri.Host := iif(ARequestInfo.Host <> '', ARequestInfo.Host, 'myserver');
      Uri.Port := cHTTPSPort;
      Uri.Document := TIdURI.PathEncode(ARequestInfo.Document);
      Uri.Params := ARequestInfo.QueryParams;
      AResponseInfo.Redirect(Uri.URI);
    finally
      Uri.Free;
    end;
    Exit;
  end;
  // process request normally ...
end;

(06-08-2018, 02:56 PM)ermesoml Wrote: I need to use port 9001

Why? HTTP is normally on port 80, and HTTP is normally on port 443. Those are standardized.

(06-08-2018, 02:56 PM)ermesoml Wrote: I also can not bind on two different ports, so I need only redirect https:\\example.com to https:\\example.com.

It is not advisable (but not impossible) to run both HTTP and HTTPS on a single port.

That being said, the ONLY way to make this work is to peek (not read!) the first few bytes from a new connection, and if those bytes are an SSL/TLS client handshake then you can activate SSL/TLS. For example:

Code:
uses
  ..., IdStackConsts, IdStackBSDBase;

// unfortunately, Indy does not currently provide a *public* interface for calling
// the underlying platform's socket recv() function with custom flags, see
// https://github.com/IndySockets/Indy/issues/214.  But it does have a *protected*
// interface for it, but only on platforms that use BSD-style sockets APIs
// (Windows, POSIX, etc)...
type
  TIdStackBSDBaseAccess = class(TIdStackBSDBase)
  end;

// adapted from http://cboard.cprogramming.com/networking-device-communication/166336-detecting-ssl-tls-client-handshake.html
function SslTlsHandshakeDetected(ASocket: TIdStackSocketHandle): Boolean;
var
  Buffer: array[0..5] of Byte;
  NumBytes, Len: Integer;
  MsgType: Byte;
begin
  Result := False;

  // sniff the first few bytes from the socket directly, do not remove them from
  // its inbound buffer, in case the SSLIOHandler needs to read them again later ...
  NumBytes := GStack.CheckForSocketError(TIdStackBSDBaseAccess(GBSDStack).WSRecv(ASocket, Buffer, SizeOf(Buffer), MSG_PEEK));

  // client disconnected?
  if NumBytes = 0 then Exit;

  if NumBytes < 3 then Exit;

  // SSL v3 or TLS v1.x ?

  if (Buffer[0] = $16) and // type (22 = handshake)
    (Buffer[1] = $3) then // protocol major version (3.0 = SSL v3, 3.x = TLS v1.x)
  begin
    Result := True;
    Exit;
  end;

  // SSL v2 ?

  if (Buffer[0] and $80) <> 0 then
  begin
    // no padding, 2-byte header
    Len := (Integer(buffer[0] and $7F) shl 8) + buffer[1];
    MsgType := buffer[2];
  end else
  begin
    // padding, 3-byte header
    Len := (Integer(buffer[0] and $3F) shl 8) + buffer[1];
    MsgType := Buffer[3];
  end;

  Result := (Len > 9) and
               (MsgType = $1); // msg type (1 = client hello)
end;

// TIdHTTPServer.OnQuerySSLPort event handler
procedure TMyForm.onQuerySslPort(APort: TIdPort; var VUseSSL: Boolean);
begin
  // do not activate SSL/TLS automatically based on port, need to sniff the socket data...
  VUseSSL := False;
end;

// TIdHTTPServer.OnConnect event handler
procedure TMyForm.onConnect(AContext: TIdContext);
begin
  if not (AContext.Connection.IOHandler is TIdSSLIOHandlerSocketBase) then
    raise Exception.Create('SSL IOHandler not assigned!');

  TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := not SslTlsHandshakeDetected(AContext.Binding.Handle);
end;

// TIdHTTPServer.OnCommandGet event handler
procedure TMyForm.onCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  if TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough then
  begin
    // not using SSL/TLS, redirect to HTTPS...
    Exit;
  end;
  // process request normally ...
end;

Reply
#3
Thank you rlebeau, only god knows how much time i spend searching this!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)