Home > Software design >  Replacing first 16 digits in a string with Regex.Replace
Replacing first 16 digits in a string with Regex.Replace

Time:06-02

I'm trying to replace only the first 16 digits of a string with Regex. I want it replaced with "*". I need to take this string:

"Request=Credit Card.Auth Only&Version=4022&HD.Network_Status_Byte=*&HD.Application_ID=TZAHSK!&HD.Terminal_ID=12991kakajsjas&HD.Device_Tag=000123&07.POS_Entry_Capability=1&07.PIN_Entry_Capability=0&07.CAT_Indicator=0&07.Terminal_Type=4&07.Account_Entry_Mode=1&07.Partial_Auth_Indicator=0&07.Account_Card_Number=4242424242424242&07.Account_Expiry=1024&07.Transaction_Amount=142931&07.Association_Token_Indicator=0&17.CVV=200&17.Street_Address=123 Road SW&17.Postal_Zip_Code=90210&17.Invoice_Number=INV19291"

And replace the credit card number with an asterisk, which is why I say the first 16 digits, as that is how many digits are in a credit card. I am first splitting the string where there is a "." and then checking if it contains "card" and "number". Then if it finds it I want to replace the first 16 numbers with "*"

This is what I've done:

 public void MaskData(string input)
    {
        if (input.Contains("."))
        {
            string[] userInput = input.Split('.');

            

            foreach (string uInput in userInput)
            {
                string lowerCaseInput = uInput.ToLower();

                string containsCard = "card";
                string containsNumber = "number";

                

                if (lowerCaseInput.Contains(containsCard) && lowerCaseInput.Contains(containsNumber))
                {
                    tbStoreInput.Text  = Regex.Replace(lowerCaseInput, @"[0-9]", "*")   Environment.NewLine;

                }
                else
                {
                    tbStoreInput.Text  = lowerCaseInput   Environment.NewLine;
                }
            }
        }
    }

I am aware that the Regex is wrong, but not sure how to only get the first 16, as right now its putting an asterisks in the entire line like seen here:

"account_card_number=****************&**"

I don't want it to show the asterisks after the "&".

CodePudding user response:

Same answer as in the comments but explained.

your regex pattern "[0-9]" is a single digit match, so each individual digit including the digits after & will be a match and so would be replaced.

What you want to do is add a quantifier which restricts the matching to a number of characters ie 16, so your regex changes to "[0-9]{16}" to ensure those are the only characters affected by your replace operation

CodePudding user response:

Disclaimer

My answer is purposely broader than what is asked by OP but I saw it as an opportunity to raise awareness of other tools that are available in C# (which are objects).

String replacement

Regex is not the only tool available to replace a simple string by another. Instead of

Regex.Replace(lowerCaseInput, @"[0-9]{16}", "****************")

it can also be

new StringBuilder()
       .Append(lowerCaseInput.Take(20))
       .Append(new string('*', 16))
       .Append(lowerCaseInput.Skip(36))
       .ToString();

Shifting from procedural to object

Now the real meat comes in the possibility to encapsulate the logic into an object which holds a kind of string representation of a dictionary (entries being separated by '.' while keys and values are separated by '='). The only behavior this object has is to give back a string representation of the initial input but with some value (1 in your case) masked to user (I assume for some security reason).

public sealed class CreditCardRequest
{
    private readonly string _input;

    public CreditCardRequest(string input) => _input = input;

    public static implicit operator string(CreditCardRequest request) => request.ToString();

    public override string ToString()
    {
        var entries = _input.Split(".", StringSplitOptions.RemoveEmptyEntries)
                            .Select(entry => entry.Split("="))
                            .ToDictionary(kv => kv[0].ToLower(), kv =>
                            {
                                if (kv[0] == "Account_Card_Number")
                                {
                                    return new StringBuilder()
                                                .Append(new string('*', 16))
                                                .Append(kv[1].Skip(16))
                                                .ToString();
                                }
                                else
                                {
                                    return kv[1];
                                }
                            });
        var output = new StringBuilder();
        foreach (var kv in entries)
        {
            output.AppendFormat("{0}={1}{2}", kv.Key, kv.Value, Environment.NewLine);
        }
        return output.ToString();
    }
}

Usage becomes as follow:

tbStoreInput.Text = new CreditCardRequest(input);

The concerns of your code are now independant of each other (the rule to parse the input is no more tied to UI component) and the implementation details are hidden. You can even decide to use Regex in CreditCardRequest.ToString() if you wish to, the UI won't ever notice the change.

The class would then becomes:

public override string ToString()
{
    var output = new StringBuilder();
    if (_input.Contains("."))
    {
        foreach (string uInput in _input.Split('.'))
        {
            if (uInput.StartsWith("Account_Card_Number"))
            {
                output.AppendLine(Regex.Replace(uInput.ToLower(), @"[0-9]{16}", "****************");
            }
            else
            {
                output.AppendLine(uInput.ToLower());
            }
        }
    }
    return output.ToString();
}

CodePudding user response:

You can match 16 digits after the account number, and replace with 16 times an asterix:

(?<=\baccount_card_number=)[0-9]{16}\b

Regex demo

Or you can use a capture group and use that group in the replacement like $1****************

\b(account_card_number=)[0-9]{16}\b

Regex demo

  • Related