public override bool TryParse(string epasaText, out EPasa epasa, out EPasaErrorCode errorCode) { errorCode = EPasaErrorCode.Success; epasa = new EPasa(); var node = TryParseEPasaInternal(TokenType.EPASA, new StringReader(epasaText), ref errorCode); if (errorCode == EPasaErrorCode.Success) { errorCode = TryCompile((EPasaNode)node, epasa); } return(errorCode == EPasaErrorCode.Success); }
public override bool TryParse(string epasaText, out EPasa epasa, out EPasaErrorCode errorCode) { errorCode = EPasaErrorCode.Success; epasa = new EPasa(); if (string.IsNullOrEmpty(epasaText)) { errorCode = EPasaErrorCode.BadFormat; return(false); } var match = _epasaRegex.Match(epasaText); var checksumDelim = match.Groups["ChecksumDelim"].Success ? match.Groups["ChecksumDelim"].Value : null; var accountNumber = match.Groups["AccountNumber"].Success ? match.Groups["AccountNumber"].Value : null; var accountChecksum = match.Groups["Checksum"].Success ? match.Groups["Checksum"].Value : null; var accountName = match.Groups["AccountName"].Success ? match.Groups["AccountName"].Value : null; var payloadStartChar = match.Groups["PayloadStartChar"].Success ? match.Groups["PayloadStartChar"].Value : null; var payloadEndChar = match.Groups["PayloadEndChar"].Success ? match.Groups["PayloadEndChar"].Value : null; var payloadContent = match.Groups["PayloadContent"].Success ? match.Groups["PayloadContent"].Value : null; var payloadPasswordDelim = match.Groups["PayloadPasswordDelim"].Success ? match.Groups["PayloadPasswordDelim"].Value : null; var payloadPassword = match.Groups["PayloadPassword"].Success ? match.Groups["PayloadPassword"].Value : null; var extendedChecksumDelim = match.Groups["ExtendedChecksumDelim"].Success ? match.Groups["ExtendedChecksumDelim"].Value : null; var extendedChecksum = match.Groups["ExtendedChecksum"].Success ? match.Groups["ExtendedChecksum"].Value : null; // Check parsed completely if (epasaText != match.Value) { errorCode = EPasaErrorCode.BadFormat; return(false); } if (accountName != null) { // Account Name if (string.IsNullOrEmpty(accountName)) { errorCode = EPasaErrorCode.BadFormat; return(false); } epasa.PayloadType = epasa.PayloadType | PayloadType.AddressedByName; epasa.AccountName = Pascal64Encoding.Unescape(accountName); epasa.Account = epasa.AccountChecksum = null; } else { // Account Number if (!uint.TryParse(accountNumber, out var accNo)) { errorCode = EPasaErrorCode.InvalidAccountNumber; return(false); } epasa.Account = accNo; var actualAccountChecksum = AccountHelper.CalculateAccountChecksum(accNo); if (checksumDelim != null) { // validate account checksum if (!uint.TryParse(accountChecksum, out var accChecksum)) { errorCode = EPasaErrorCode.AccountChecksumInvalid; return(false); } if (accChecksum != actualAccountChecksum) { errorCode = EPasaErrorCode.BadChecksum; return(false); } } epasa.AccountChecksum = actualAccountChecksum; } // Encryption type switch (payloadStartChar) { case null: break; case "[": if (payloadEndChar != "]") { errorCode = EPasaErrorCode.MismatchedPayloadEncoding; return(false); } epasa.PayloadType = epasa.PayloadType | PayloadType.Public; break; case "(": if (payloadEndChar != ")") { errorCode = EPasaErrorCode.MismatchedPayloadEncoding; return(false); } epasa.PayloadType = epasa.PayloadType | PayloadType.RecipientKeyEncrypted; break; case "<": if (payloadEndChar != ">") { errorCode = EPasaErrorCode.MismatchedPayloadEncoding; return(false); } epasa.PayloadType = epasa.PayloadType | PayloadType.SenderKeyEncrypted; break; case "{": if (payloadEndChar != "}") { errorCode = EPasaErrorCode.MismatchedPayloadEncoding; return(false); } epasa.PayloadType = epasa.PayloadType | PayloadType.PasswordEncrypted; break; default: throw new NotSupportedException($"Unrecognized start character '{payloadStartChar}'"); } // Password if (epasa.PayloadType.HasFlag(PayloadType.PasswordEncrypted)) { if (payloadPasswordDelim == null) { errorCode = EPasaErrorCode.MissingPassword; return(false); } epasa.Password = PascalAsciiEncoding.Unescape(payloadPassword ?? ""); } else if (payloadPasswordDelim != null) { errorCode = EPasaErrorCode.UnusedPassword; return(false); } // Payload if (payloadStartChar != null) { if (payloadContent == null) { epasa.Payload = string.Empty; } else if (payloadContent.StartsWith("\"")) { epasa.PayloadType = epasa.PayloadType | PayloadType.AsciiFormatted; epasa.Payload = PascalAsciiEncoding.Unescape(payloadContent.Trim('"')); } else if (payloadContent.StartsWith("0x")) { epasa.PayloadType = epasa.PayloadType | PayloadType.HexFormatted; epasa.Payload = payloadContent.Substring(2); } else { epasa.PayloadType = epasa.PayloadType | PayloadType.Base58Formatted; epasa.Payload = payloadContent; } } // Payload Lengths if (!EPasaHelper.IsValidPayloadLength(epasa.PayloadType, epasa.Payload)) { errorCode = EPasaErrorCode.PayloadTooLarge; return(false); } // Extended Checksum var actualChecksum = EPasaHelper.ComputeExtendedChecksum(epasa.ToString(true)); if (extendedChecksumDelim != null) { if (extendedChecksum != actualChecksum) { errorCode = EPasaErrorCode.BadExtendedChecksum; return(false); } } epasa.ExtendedChecksum = actualChecksum; return(true); }
public abstract bool TryParse(string epasaText, out EPasa epasa, out EPasaErrorCode errorCode);
private Node TryParseEPasaInternal(TokenType expectedToken, TextReader reader, ref EPasaErrorCode error, string prefix = null, string postFix = null) { Node result = null; if (error != EPasaErrorCode.Success) { return(null); } if (prefix != null) { foreach (var prefixChar in prefix) { if (!reader.MatchChar(prefixChar)) { error = EPasaErrorCode.BadFormat; return(null); } } } switch (expectedToken) { case TokenType.EPASA: result = new EPasaNode { Type = TokenType.EPASA, Account = TryParseEPasaInternal(TokenType.PASA, reader, ref error) as AccountNode, Payload = TryParseEPasaInternal(TokenType.Payload, reader, ref error) as PayloadNode, ExtendedChecksum = TryParseEPasaInternal(TokenType.ExtendedChecksum, reader, ref error) as ValueNode }; if (!reader.MatchChar(null as char?)) { // match end-of-string error = EPasaErrorCode.BadFormat; return(null); } break; case TokenType.PASA: if (IsStartChar(TokenType.AccountNumber, reader.PeekChar())) { result = TryParseEPasaInternal(TokenType.AccountNumber, reader, ref error); } else if (IsStartChar(TokenType.AccountName, reader.PeekChar())) { result = TryParseEPasaInternal(TokenType.AccountName, reader, ref error); } else { error = EPasaErrorCode.InvalidAccountNumber; } break; case TokenType.AccountName: result = new AccountNode { Type = TokenType.AccountName, Name = TryParseEPasaInternal(TokenType.Pascal64String, reader, ref error) as ValueNode }; break; case TokenType.AccountNumber: result = new AccountNode { Type = TokenType.AccountNumber, AccountNo = TryParseEPasaInternal(TokenType.Number, reader, ref error) as ValueNode, Checksum = TryParseEPasaInternal(TokenType.Checksum, reader, ref error) as ValueNode }; break; case TokenType.Checksum: if (IsStartChar(TokenType.Checksum, reader.PeekChar())) { result = TryParseEPasaInternal(TokenType.Number, reader, ref error, prefix: "-") as ValueNode; } break; case TokenType.Payload: if (IsStartChar(TokenType.Payload, reader.PeekChar())) { result = new PayloadNode { Type = TokenType.Payload }; var payloadOpenChar = reader.ReadChar(); ((PayloadNode)result).Content = TryParseEPasaInternal(TokenType.PayloadContent, reader, ref error) as ValueNode; if (reader.PeekChar() == ':') { if (payloadOpenChar != '{') { error = EPasaErrorCode.UnusedPassword; return(result); } ((PayloadNode)result).Password = TryParseEPasaInternal(TokenType.PascalAsciiString, reader, ref error, prefix: ":") as ValueNode; } else if (payloadOpenChar == '{') { error = EPasaErrorCode.MissingPassword; return(null); } var payloadEndChar = reader.PeekChar(); if (!payloadEndChar.IsIn(']', ')', '>', '}')) { error = EPasaErrorCode.BadFormat; return(null); } char?expectedEndChar = null; switch (payloadOpenChar) { case '[': ((PayloadNode)result).Encryption = PayloadType.Public; expectedEndChar = ']'; break; case '(': ((PayloadNode)result).Encryption = PayloadType.RecipientKeyEncrypted; expectedEndChar = ')'; break; case '<': ((PayloadNode)result).Encryption = PayloadType.SenderKeyEncrypted; expectedEndChar = '>'; break; case '{': ((PayloadNode)result).Encryption = PayloadType.PasswordEncrypted; expectedEndChar = '}'; break; default: throw new InternalErrorException("Implementation Error"); } if (reader.PeekChar() != expectedEndChar) { error = EPasaErrorCode.MismatchedPayloadEncoding; return(null); } if (!reader.MatchChar(']', ')', '>', '}')) { error = EPasaErrorCode.BadFormat; return(null); } } break; case TokenType.PayloadContent: var contentStartChar = reader.PeekChar(); if (contentStartChar == '"') { result = TryParseEPasaInternal(TokenType.PascalAsciiString, reader, ref error, prefix: "\"", postFix: "\"") as ValueNode; } else if (IsStartChar(TokenType.HexString, contentStartChar)) { result = TryParseEPasaInternal(TokenType.HexString, reader, ref error, prefix: "0x") as ValueNode; } else if (IsStartChar(TokenType.Base58String, contentStartChar)) { result = TryParseEPasaInternal(TokenType.Base58String, reader, ref error) as ValueNode; } else { // unrecognized content, assume empty } break; case TokenType.ExtendedChecksum: if (reader.PeekChar() == ':') { result = TryParseEPasaInternal(TokenType.HexString, reader, ref error, prefix: ":") as ValueNode; } break; case TokenType.PascalAsciiString: case TokenType.Pascal64String: result = new ValueNode { Type = expectedToken, Value = string.Empty }; if (!IsStartChar(expectedToken, reader.PeekChar())) { // first character is a non-character, assume empty string return(result); } var escapedValue = string.Empty; do { if (reader.PeekChar() == GetEscapeChar(expectedToken)) { escapedValue += reader.ReadChar(); if (!IsEscapedChar(expectedToken, reader.PeekChar())) { error = EPasaErrorCode.BadFormat; // illegal escape sequence return(result); } } else if (IsEscapedChar(expectedToken, reader.PeekChar())) { // encountered a character that needs escaping // assume end of token break; } escapedValue += reader.ReadChar(); } while (IsValidValueChar(expectedToken, reader.PeekChar())); ((ValueNode)result).Value = escapedValue; break; case TokenType.HexString: case TokenType.Base58String: case TokenType.Number: result = new ValueNode { Type = expectedToken, Value = string.Empty }; while (IsValidValueChar(expectedToken, reader.PeekChar())) { ((ValueNode)result).Value = ((ValueNode)result).Value + reader.ReadChar(); } break; default: throw new ArgumentOutOfRangeException(nameof(expectedToken), expectedToken, null); } if (error == EPasaErrorCode.Success && postFix != null) { foreach (var postfixChar in postFix) { if (!reader.MatchChar(postfixChar)) { error = EPasaErrorCode.BadFormat; return(null); } } } return(result); }