Esempio n. 1
0
/** @author Andreas Schildbach */
public abstract class InputParser {
  private static final Logger log = LoggerFactory.getLogger(InputParser.class);

  public abstract static class StringInputParser extends InputParser {
    private final String input;

    public StringInputParser(@Nonnull final String input) {
      this.input = input;
    }

    @Override
    public void parse() {
      if (input.startsWith("MINTCOIN:-")) {
        try {
          final byte[] serializedPaymentRequest = Qr.decodeBinary(input.substring(9));

          parseAndHandlePaymentRequest(serializedPaymentRequest);
        } catch (final IOException x) {
          log.info("i/o error while fetching payment request", x);

          error(R.string.input_parser_io_error, x.getMessage());
        } catch (final PaymentProtocolException.PkiVerificationException x) {
          log.info("got unverifyable payment request", x);

          error(R.string.input_parser_unverifyable_paymentrequest, x.getMessage());
        } catch (final PaymentProtocolException x) {
          log.info("got invalid payment request", x);

          error(R.string.input_parser_invalid_paymentrequest, x.getMessage());
        }
      } else if (input.startsWith("mintcoin:")) {
        try {
          final BitcoinURI bitcoinUri = new BitcoinURI(null, input);
          final Address address = bitcoinUri.getAddress();
          if (address != null && !Constants.NETWORK_PARAMETERS.equals(address.getParameters()))
            throw new BitcoinURIParseException("mismatched network");

          handlePaymentIntent(PaymentIntent.fromBitcoinUri(bitcoinUri));
        } catch (final BitcoinURIParseException x) {
          log.info("got invalid bitcoin uri: '" + input + "'", x);

          error(R.string.input_parser_invalid_bitcoin_uri, input);
        }
      } else if (PATTERN_BITCOIN_ADDRESS.matcher(input).matches()) {
        try {
          final Address address = new Address(Constants.NETWORK_PARAMETERS, input);

          handlePaymentIntent(PaymentIntent.fromAddress(address, null));
        } catch (final AddressFormatException x) {
          log.info("got invalid address", x);

          error(R.string.input_parser_invalid_address);
        }
      } else if (PATTERN_DUMPED_PRIVATE_KEY_UNCOMPRESSED.matcher(input).matches()
          || PATTERN_DUMPED_PRIVATE_KEY_COMPRESSED.matcher(input).matches()) {
        try {
          final VersionedChecksummedBytes key =
              new DumpedPrivateKey(Constants.NETWORK_PARAMETERS, input);

          handlePrivateKey(key);
        } catch (final AddressFormatException x) {
          log.info("got invalid address", x);

          error(R.string.input_parser_invalid_address);
        }
      } else if (PATTERN_BIP38_PRIVATE_KEY.matcher(input).matches()) {
        try {
          final VersionedChecksummedBytes key =
              new BIP38PrivateKey(Constants.NETWORK_PARAMETERS, input);

          handlePrivateKey(key);
        } catch (final AddressFormatException x) {
          log.info("got invalid address", x);

          error(R.string.input_parser_invalid_address);
        }
      } else if (PATTERN_TRANSACTION.matcher(input).matches()) {
        try {
          final Transaction tx =
              new Transaction(Constants.NETWORK_PARAMETERS, Qr.decodeDecompressBinary(input));

          handleDirectTransaction(tx);
        } catch (final IOException x) {
          log.info("i/o error while fetching transaction", x);

          error(R.string.input_parser_invalid_transaction, x.getMessage());
        } catch (final ProtocolException x) {
          log.info("got invalid transaction", x);

          error(R.string.input_parser_invalid_transaction, x.getMessage());
        }
      } else {
        cannotClassify(input);
      }
    }

    protected void handlePrivateKey(@Nonnull final VersionedChecksummedBytes key) {
      final Address address =
          new Address(
              Constants.NETWORK_PARAMETERS, ((DumpedPrivateKey) key).getKey().getPubKeyHash());

      handlePaymentIntent(PaymentIntent.fromAddress(address, null));
    }
  }

  public abstract static class BinaryInputParser extends InputParser {
    private final String inputType;
    private final byte[] input;

    public BinaryInputParser(@Nonnull final String inputType, @Nonnull final byte[] input) {
      this.inputType = inputType;
      this.input = input;
    }

    @Override
    public void parse() {
      if (Constants.MIMETYPE_TRANSACTION.equals(inputType)) {
        try {
          final Transaction tx = new Transaction(Constants.NETWORK_PARAMETERS, input);

          handleDirectTransaction(tx);
        } catch (final VerificationException x) {
          log.info("got invalid transaction", x);

          error(R.string.input_parser_invalid_transaction, x.getMessage());
        }
      } else if (PaymentProtocol.MIMETYPE_PAYMENTREQUEST.equals(inputType)) {
        try {
          parseAndHandlePaymentRequest(input);
        } catch (final PaymentProtocolException.PkiVerificationException x) {
          log.info("got unverifyable payment request", x);

          error(R.string.input_parser_unverifyable_paymentrequest, x.getMessage());
        } catch (final PaymentProtocolException x) {
          log.info("got invalid payment request", x);

          error(R.string.input_parser_invalid_paymentrequest, x.getMessage());
        }
      } else {
        cannotClassify(inputType);
      }
    }

    @Override
    protected final void handleDirectTransaction(@Nonnull final Transaction transaction)
        throws VerificationException {
      throw new UnsupportedOperationException();
    }
  }

  public abstract static class StreamInputParser extends InputParser {
    private final String inputType;
    private final InputStream is;

    public StreamInputParser(@Nonnull final String inputType, @Nonnull final InputStream is) {
      this.inputType = inputType;
      this.is = is;
    }

    @Override
    public void parse() {
      if (PaymentProtocol.MIMETYPE_PAYMENTREQUEST.equals(inputType)) {
        ByteArrayOutputStream baos = null;

        try {
          baos = new ByteArrayOutputStream();
          Io.copy(is, baos);
          parseAndHandlePaymentRequest(baos.toByteArray());
        } catch (final IOException x) {
          log.info("i/o error while fetching payment request", x);

          error(R.string.input_parser_io_error, x.getMessage());
        } catch (final PaymentProtocolException.PkiVerificationException x) {
          log.info("got unverifyable payment request", x);

          error(R.string.input_parser_unverifyable_paymentrequest, x.getMessage());
        } catch (final PaymentProtocolException x) {
          log.info("got invalid payment request", x);

          error(R.string.input_parser_invalid_paymentrequest, x.getMessage());
        } finally {
          try {
            if (baos != null) baos.close();
          } catch (IOException x) {
            x.printStackTrace();
          }

          try {
            is.close();
          } catch (IOException x) {
            x.printStackTrace();
          }
        }
      } else {
        cannotClassify(inputType);
      }
    }

    @Override
    protected final void handleDirectTransaction(@Nonnull final Transaction transaction)
        throws VerificationException {
      throw new UnsupportedOperationException();
    }
  }

  public abstract void parse();

  protected final void parseAndHandlePaymentRequest(@Nonnull final byte[] serializedPaymentRequest)
      throws PaymentProtocolException {
    final PaymentIntent paymentIntent = parsePaymentRequest(serializedPaymentRequest);

    handlePaymentIntent(paymentIntent);
  }

  public static PaymentIntent parsePaymentRequest(@Nonnull final byte[] serializedPaymentRequest)
      throws PaymentProtocolException {
    try {
      if (serializedPaymentRequest.length > 50000)
        throw new PaymentProtocolException(
            "payment request too big: " + serializedPaymentRequest.length);

      final Protos.PaymentRequest paymentRequest =
          Protos.PaymentRequest.parseFrom(serializedPaymentRequest);

      final String pkiName;
      final String pkiCaName;
      if (!"none".equals(paymentRequest.getPkiType())) {
        final KeyStore keystore = new TrustStoreLoader.DefaultTrustStoreLoader().getKeyStore();
        final PkiVerificationData verificationData =
            PaymentProtocol.verifyPaymentRequestPki(paymentRequest, keystore);
        pkiName = verificationData.displayName;
        pkiCaName = verificationData.rootAuthorityName;
      } else {
        pkiName = null;
        pkiCaName = null;
      }

      final PaymentSession paymentSession = PaymentProtocol.parsePaymentRequest(paymentRequest);

      if (paymentSession.isExpired())
        throw new PaymentProtocolException.Expired(
            "payment details expired: current time "
                + new Date()
                + " after expiry time "
                + paymentSession.getExpires());

      if (!paymentSession.getNetworkParameters().equals(Constants.NETWORK_PARAMETERS))
        throw new PaymentProtocolException.InvalidNetwork(
            "cannot handle payment request network: " + paymentSession.getNetworkParameters());

      final ArrayList<PaymentIntent.Output> outputs = new ArrayList<PaymentIntent.Output>(1);
      for (final PaymentProtocol.Output output : paymentSession.getOutputs())
        outputs.add(PaymentIntent.Output.valueOf(output));

      final String memo = paymentSession.getMemo();

      final String paymentUrl = paymentSession.getPaymentUrl();

      final byte[] merchantData = paymentSession.getMerchantData();

      final byte[] paymentRequestHash =
          Hashing.sha256().hashBytes(serializedPaymentRequest).asBytes();

      final PaymentIntent paymentIntent =
          new PaymentIntent(
              PaymentIntent.Standard.BIP70,
              pkiName,
              pkiCaName,
              outputs.toArray(new PaymentIntent.Output[0]),
              memo,
              paymentUrl,
              merchantData,
              null,
              paymentRequestHash);

      if (paymentIntent.hasPaymentUrl() && !paymentIntent.isSupportedPaymentUrl())
        throw new PaymentProtocolException.InvalidPaymentURL(
            "cannot handle payment url: " + paymentIntent.paymentUrl);

      return paymentIntent;
    } catch (final InvalidProtocolBufferException x) {
      throw new PaymentProtocolException(x);
    } catch (final UninitializedMessageException x) {
      throw new PaymentProtocolException(x);
    } catch (final FileNotFoundException x) {
      throw new RuntimeException(x);
    } catch (final KeyStoreException x) {
      throw new RuntimeException(x);
    }
  }

  protected abstract void handlePaymentIntent(@Nonnull PaymentIntent paymentIntent);

  protected abstract void handleDirectTransaction(@Nonnull Transaction transaction)
      throws VerificationException;

  protected abstract void error(int messageResId, Object... messageArgs);

  protected void cannotClassify(@Nonnull final String input) {
    error(R.string.input_parser_cannot_classify, input);
  }

  protected void dialog(
      final Context context,
      @Nullable final OnClickListener dismissListener,
      final int titleResId,
      final int messageResId,
      final Object... messageArgs) {
    final DialogBuilder dialog = new DialogBuilder(context);
    if (titleResId != 0) dialog.setTitle(titleResId);
    dialog.setMessage(context.getString(messageResId, messageArgs));
    dialog.singleDismissButton(dismissListener);
    dialog.show();
  }

  private static final Pattern PATTERN_BITCOIN_ADDRESS =
      Pattern.compile("[" + new String(Base58.ALPHABET) + "]{20,40}");
  private static final Pattern PATTERN_DUMPED_PRIVATE_KEY_UNCOMPRESSED =
      Pattern.compile(
          (Constants.NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET) ? "5" : "9")
              + "["
              + new String(Base58.ALPHABET)
              + "]{50}");
  private static final Pattern PATTERN_DUMPED_PRIVATE_KEY_COMPRESSED =
      Pattern.compile(
          (Constants.NETWORK_PARAMETERS.getId().equals(NetworkParameters.ID_MAINNET) ? "[KL]" : "c")
              + "["
              + new String(Base58.ALPHABET)
              + "]{51}");
  private static final Pattern PATTERN_BIP38_PRIVATE_KEY =
      Pattern.compile("6P" + "[" + new String(Base58.ALPHABET) + "]{56}");
  private static final Pattern PATTERN_TRANSACTION =
      Pattern.compile("[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$\\*\\+\\-\\.\\/\\:]{100,}");
}