﻿namespace dOAuthBase;

interface

uses
  System.Text,
  System.Security.Cryptography,
  System.Web,
  System.Collections.Generic;

type
  TSignatureTypes = public (HMACSHA1, PLAINTEXT, RSASHA1);

  TOAuthQueryParameter = public class(System.Object)
    private
       FName: string;
       FValue: string;
    public
       constructor Create(Name: string; Value: string);
       property Name: string read FName write FName;
       property Value: string read FValue write FValue;
  end;

  TQueryParameterComparer = public class(System.Object, IComparer<TOAuthQueryParameter>)
    public
      function Compare(x: TOAuthQueryParameter; y: TOAuthQueryParameter): integer;
  end;


  TOAuthBase = public class(System.Object)
    const
      // Generic Consts
      OAuthVersion = '1.0';
      OAuthParameterPrefix = 'oauth_';
      // List of Known Keys
		  OAuthConsumerKeyKey = 'oauth_consumer_key';
		  OAuthCallbackKey = 'oauth_callback';
		  OAuthVersionKey = 'oauth_version';
		  OAuthSignatureMethodKey = 'oauth_signature_method';
		  OAuthSignatureKey = 'oauth_signature';
		  OAuthTimestampKey = 'oauth_timestamp';
		  OAuthNonceKey = 'oauth_nonce';
		  OAuthTokenKey = 'oauth_token';
		  OAuthTokenSecretKey = 'oauth_token_secret';
      // Signature Consts
      HMACSHA1SignatureType = 'HMAC-SHA1';
      PlainTextSignatureType = 'PLAINTEXT';
      RSASHA1SignatureType = 'RSA-SHA1';
      // Chars which do not have special meanings
      URLUnreservedChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~';
    private
      function ComputeHash(HashAType: HashAlgorithm; data: string): string;
      function GetQueryParameters(parameters: string): System.Collections.Generic.List<TOAuthQueryParameter>;
    public
      function UrlEncode(value: string): string;
      function NormaliseRequestParameters(parameters: System.Collections.Generic.List<TOAuthQueryParameter>): string;
      function GenerateSignatureBase(url: Uri; consumerKey: string; token: string; tokenSecret: string; httpMethod: string; timeStamp: string; nonce: string; signatureType: string): string;
      function GenerateSignatureUsingHash(signatureBase: string; hash: HashAlgorithm): string;
      function GenerateSignature(url: Uri; consumerKey: string; consumerSecret: string; token: string; tokenSecret: string; httpMethod: string; timeStamp: string; nonce: string; signatureType: TSignatureTypes): string; 
      function GenerateSignature(url: Uri; consumerKey: string; consumerSecret: string; token: string; tokenSecret: string; httpMethod: string; timeStamp: string; nonce: string): string;
      function GenerateTimeStamp: string;
      function GenerateNonce: string;
  end;

implementation

{ TOAuth_QueryParameter }

constructor TOAuthQueryParameter.Create(Name, Value: string);
begin
  inherited Create;
  Self.FName := Name;
  Self.FValue := Value;
end;

{ TQueryParameterComparer }

function TQueryParameterComparer.Compare(x, y: TOAuthQueryParameter): integer;
begin
  if (x.Name = y.Name) then
   begin
     Result := System.String.Compare(x.Value, y.Value);
   end
  else
   begin
     Result := System.String.Compare(x.Name, y.Name);
   end;
end;

{ TOAuthBase }

{ Sample Usage in C#

OAuthBase oauth = new OAuthBase();

Uri url = new Uri(”http://photos.example.net/photos?file=vacation.jpg&size=original”);

string signature = oauth.GenerateSignature(url, “dpf43f3p2l4k3l03″, “kd94hf93k423kf44″, “nnch734d00sl2jdk”, “pfkkdhi9sl3r4s00″, “GET”, oauth.GenerateTimeStamp(), oauth.GenerateNonce(), OAuthBase.SignatureTypes.HMACSHA1);

}

function TOAuthBase.GenerateNonce: string;
begin
  Result := System.Random.Create.Next(123400, 9999999).ToString;
end;

function TOAuthBase.GenerateTimeStamp: string;
var
  ts: TimeSpan;
begin
  ts := DateTime.UtcNow - DateTime.Create(1970, 1, 1, 0, 0, 0, 0);
  Result := ts.TotalSeconds.ToString;
end;

function TOAuthBase.GenerateSignature(url: Uri; consumerKey: string; consumerSecret: string; token: string; tokenSecret: string; httpMethod: string; timeStamp: string; nonce: string): string;
begin
  Result := Self.GenerateSignature(url, consumerKey, consumerSecret, token, tokenSecret, httpMethod, timeStamp, nonce, TSignatureTypes.HMACSHA1);
end;

function TOAuthBase.GenerateSignature(url: Uri; consumerKey: string; consumerSecret: string; token: string; tokenSecret: string; httpMethod: string; timeStamp: string; nonce: string; signatureType: TSignatureTypes): string;
var
  signatureBase: string;
  hmacshaHash: System.Security.Cryptography.HMACSHA1;
begin
  if (signatureType = TSignatureTypes.PLAINTEXT) then
   begin
     Result := Self.UrlEncode(System.String.Format('{0}&{1}', consumerSecret, tokenSecret));
   end;
  if (signatureType = TSignatureTypes.HMACSHA1) then
   begin
     signatureBase := Self.GenerateSignatureBase(url, consumerKey, token, tokenSecret, httpMethod, timeStamp, nonce, HMACSHA1SignatureType);
     hmacshaHash := System.Security.Cryptography.HMACSHA1.Create;
     hmacshaHash.Key := Encoding.ASCII.GetBytes(System.String.Format('{0}&{1}', Self.UrlEncode(consumerSecret), Self.UrlEncode(tokenSecret)));
     Result := Self.GenerateSignatureUsingHash(signatureBase, hmacshaHash);
   end;
  if (signatureType = TSignatureTypes.RSASHA1) then
   begin
     raise NotImplementedException.Create('Not done yet!');
   end;
end;

function TOAuthBase.GenerateSignatureUsingHash(signatureBase: string; hash: HashAlgorithm): string;
begin
  Result := Self.ComputeHash(hash, signatureBase);
end;

function TOAuthBase.GenerateSignatureBase(url: Uri; consumerKey: string; token: string; tokenSecret: string; httpMethod: string; timeStamp: string; nonce: string; signatureType: string): string;
var
  parameters: System.Collections.Generic.List<TOAuthQueryParameter>;
  normalisedParameters: string;
  signatureBase: StringBuilder;
begin
  if (token = nil) then
    begin
      token := '';
    end;
  if (tokenSecret = nil) then
    begin
      tokenSecret := '';
    end;
  if (System.String.IsNullOrEmpty(consumerKey)) then
    begin
      raise ArgumentNullException.Create('consumerKey');
    end;
  if (System.String.IsNullOrEmpty(httpMethod)) then
    begin
      raise ArgumentNullException.Create('httpMethod');
    end;
  if (System.String.IsNullOrEmpty(signatureType)) then
    begin
      raise ArgumentNullException.Create('signatureType');
    end;
   parameters := Self.GetQueryParameters(url.Query);
   parameters.Add(TOAuthQueryParameter.Create(Self.OAuthVersionKey, Self.OAuthVersion));
   parameters.Add(TOAuthQueryParameter.Create(Self.OAuthNonceKey, nonce));
   parameters.Add(TOAuthQueryParameter.Create(Self.OAuthTimestampKey, timeStamp));
   parameters.Add(TOAuthQueryParameter.Create(Self.OAuthSignatureMethodKey, signatureType));
   parameters.Add(TOAuthQueryParameter.Create(Self.OAuthConsumerKeyKey, consumerKey));

   if (not System.String.IsNullOrEmpty(token)) then
    begin
      parameters.Add(TOAuthQueryParameter.Create(Self.OAuthTokenKey, token));
    end;

   parameters.Sort;

   normalisedParameters := Self.NormaliseRequestParameters(parameters);
   signatureBase := System.Text.StringBuilder.Create;
   signatureBase.AppendFormat('{0}&', httpMethod.ToUpper);
   signatureBase.AppendFormat('{0}&', Self.UrlEncode(System.String.Format('{0}://{1}{2}', url.Scheme, url.Host, url.AbsolutePath)));
   signatureBase.AppendFormat('{0}', Self.UrlEncode(normalisedParameters));

   Result := signatureBase.ToString;

end;

function TOAuthBase.NormaliseRequestParameters(parameters: System.Collections.Generic.List<TOAuthQueryParameter>): string;
var
  sb: System.Text.StringBuilder;
  p: TOAuthQueryParameter;
begin
  sb := StringBuilder.Create;
  for p in parameters do
   begin
     sb.AppendFormat('{0}={1}', p.Name, p.Value);
   end;
  sb.Append('&');
  Result := sb.ToString;
end;

function TOAuthBase.UrlEncode(value: string): string;
var
  symbol: char;
begin
  Result := '';
  for symbol in value do
   begin
     if (URLUnreservedChars.IndexOf(symbol) <> -1) then
       begin
         Result := Result + symbol;
       end
     else
       begin
         Result := '%' + Result.Format('{0:X2}', symbol);
       end;
   end;
end;

function TOAuthBase.GetQueryParameters(parameters: string): List<TOAuthQueryParameter>;
var
  p, temp: array of string;
  s: string;
begin
  if (parameters.StartsWith('?')) then
  begin
    parameters := parameters.Remove(0,1);
  end;

  Result := System.Collections.Generic.List<TOAuthQueryParameter>.Create;
  if not (System.String.IsNullOrEmpty(parameters)) then
    begin
      p := parameters.Split(['&']);
      for s in p do
       begin
         if ((not System.String.IsNullOrEmpty(s)) and (not s.StartsWith(Self.OAuthParameterPrefix))) then
           begin
             if (s.IndexOf('=') > -1) then
               begin
                 temp := s.Split(['=']);
                 Result.Add(TOAuthQueryParameter.Create(temp[0], temp[1]));
               end
             else
               begin
                 Result.Add(TOAuthQueryParameter.Create(s, ''));
               end;
           end;
       end;
    end;
end;

function TOAuthBase.ComputeHash(hashAType: HashAlgorithm; data: string): string;
var
  dataBuffer: array of Byte;
  hashBytes: array of Byte;
begin
  if (hashAType = nil) then
    begin
      raise ArgumentNullException.Create('hashAType');
    end;
  if (data = nil) then
    begin
      raise ArgumentNullException.Create('data');
    end;
  dataBuffer := System.Text.Encoding.ASCII.GetBytes(data);
  hashBytes := hashAType.ComputeHash(dataBuffer);
  Result := Convert.ToBase64String(hashBytes);
end;

end.

