0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.j2me.payment;
0028:
0029: import java.util.Date;
0030: import java.util.Vector;
0031:
0032: import java.io.Writer;
0033: import java.io.InputStream;
0034: import java.io.ByteArrayInputStream;
0035: import java.io.IOException;
0036: import java.io.UnsupportedEncodingException;
0037:
0038: import com.sun.midp.util.Properties;
0039: import com.sun.midp.io.HttpUrl;
0040: import com.sun.midp.io.Util;
0041: import com.sun.midp.io.Base64;
0042:
0043: import com.sun.midp.crypto.*;
0044: import com.sun.midp.pki.*;
0045: import com.sun.midp.security.*;
0046: import com.sun.midp.publickeystore.*;
0047:
0048: import javax.microedition.pki.CertificateException;
0049:
0050: /**
0051: * This class represents the payment information read from the application
0052: * Manifest file or obtained from the associated update URL.
0053: *
0054: * @version 1.11
0055: */
0056: public final class PaymentInfo {
0057:
0058: /** A value indicating that the auto request mode is disabled. */
0059: public static final int AUTO_REQUEST_OFF = 0;
0060: /** A value indicating that the auto request mode is set to accept. */
0061: public static final int AUTO_REQUEST_ACCEPT = 1;
0062: /** A value indicating that the auto request mode is set to reject. */
0063: public static final int AUTO_REQUEST_REJECT = 2;
0064:
0065: /** The version number of the JAR-Manifest fields. */
0066: private static final String CURRENT_VERSION = System
0067: .getProperty("microedition.payment.version");
0068: /** Pay version attribute name. */
0069: private static final String PAY_VERSION = "Pay-Version";
0070: /** Pay-adapters attribute name. */
0071: private static final String PAY_ADAPTERS = "Pay-Adapters";
0072: /** Pay-Debug-DemoMode attribute name. */
0073: private static final String PAY_DBG_DEMOMODE = "Pay-Debug-DemoMode";
0074: /** Pay-Debug-FailInitialize attribute name. */
0075: private static final String PAY_DBG_FAILINITIALIZE = "Pay-Debug-FailInitialize";
0076: /** Pay-Debug-FailIO attribute name. */
0077: private static final String PAY_DBG_FAILIO = "Pay-Debug-FailIO";
0078: /** Pay-Debug-MissedTransactions attribute name. */
0079: private static final String PAY_DBG_MISSEDTRANSACTIONS = "Pay-Debug-MissedTransactions";
0080: /** Pay-Debug-RandomTests attribute name. */
0081: private static final String PAY_DBG_RANDOMTESTS = "Pay-Debug-RandomTests";
0082: /** Pay-Debug-AutoRequestMode attribute name. */
0083: private static final String PAY_DBG_AUTOREQUESTMODE = "Pay-Debug-AutoRequestMode";
0084: /** Pay-Debug-NoAdapter attribute name. */
0085: private static final String PAY_DBG_NOADAPTER = "Pay-Debug-NoAdapter";
0086: /** Pay-Update-Date attribute name. */
0087: private static final String PAY_UPDATE_DATE = "Pay-Update-Date";
0088: /** Pay-Update-Stamp attribute name. */
0089: private static final String PAY_UPDATE_STAMP = "Pay-Update-Stamp";
0090: /** Pay-Update-URL attribute name. */
0091: private static final String PAY_UPDATE_URL = "Pay-Update-URL";
0092: /** Pay-Cache attribute name. */
0093: private static final String PAY_CACHE = "Pay-Cache";
0094: /** Pay-Providers attribute name. */
0095: private static final String PAY_PROVIDERS = "Pay-Providers";
0096: /** Prefix for constructing provider specific attribute name. */
0097: private static final String PAY_PREFIX = "Pay-";
0098: /** Prefix for constructing feature description attribute name. */
0099: private static final String PAY_FEATURE_PREFIX = "Pay-Feature-";
0100: /** Suffix for constructing provider info attribute name. */
0101: private static final String INFO_SUFFIX = "-Info";
0102: /**
0103: * Suffix for constructing price and payment specific information
0104: * attribute name.
0105: */
0106: private static final String TAG = "-Tag-";
0107: /** Pay-Certificate-(n)-(m) attribute name prefix. */
0108: private static final String PAY_CERTIFICATE_PREFIX = "Pay-Certificate-";
0109: /** Pay-Signature-XXX-XXX attribute name prefix. */
0110: private static final String PAY_SIGNATURE_PREFIX = "Pay-Signature-";
0111: /** Pay-Signature-RSA-SHA1 attribute name. */
0112: private static final String PAY_SIGNATURE_RSA_SHA1 = "Pay-Signature-RSA-SHA1";
0113: /** PKI prefixes are used for property strip. */
0114: private static final char[][] PKI_PREFIXES = {
0115: PAY_CERTIFICATE_PREFIX.toCharArray(),
0116: PAY_SIGNATURE_PREFIX.toCharArray() };
0117: /** List of supported adapters. */
0118: private static final String[] VALID_ADAPTER_NAMES = { "PPSMS" };
0119: /** Pointer to "yes" string. */
0120: private static final String YES_VALUE = "yes";
0121: /** Pointer to "no" string. */
0122: private static final String NO_VALUE = "no";
0123: /** Array of options could only exist in payment attributes. */
0124: private static final String[] YES_NO_OPTIONS = { YES_VALUE,
0125: NO_VALUE };
0126: /** Array of options can only exist in payment attributes. */
0127: private static final String[] ACCEPT_REJECT_OPTIONS = { "accept",
0128: "reject" };
0129: /** Instance of Utils class */
0130: private static final Utils utilities = PaymentModule.getInstance()
0131: .getUtilities();
0132:
0133: /** List of MIDlet requested adapters. */
0134: private String[] adapters;
0135: /** Pay-Debug-DemoMode attribute value. */
0136: private boolean dbgDemoMode;
0137: /** Pay-Debug-FailInitialize attribute value. */
0138: private boolean dbgFailInitialize;
0139: /** Pay-Debug-FailIO attribute value. */
0140: private boolean dbgFailIO;
0141: /** Pay-Debug-MissedTransactions attribute value. */
0142: private int dbgMissedTransactions;
0143: /** Pay-Debug-RandomTests attribute value. */
0144: private boolean dbgRandomTests;
0145: /** Pay-Debug-AutoRequestMode attribute value. */
0146: private int dbgAutoRequestMode;
0147: /** Pay-Update-Date attribute value. */
0148: private Date updateDate;
0149: /** Pay-Update-Stamp attibute value. */
0150: private Date updateStamp;
0151: /** Pay-Update-URL attribute value. */
0152: private String updateURL;
0153: /** Pay-Cache attribute value. */
0154: private boolean cache;
0155: /** Payment info expiration date. */
0156: private Date expirationDate;
0157: /** Array of features price tags. */
0158: private int[] featureToTag;
0159: /** List of MIDlet supported payment providers. */
0160: private ProviderInfo[] providers;
0161:
0162: /** Default constructor. */
0163: private PaymentInfo() {
0164: }
0165:
0166: /**
0167: * Creates an instance of the <code>PaymentInfo</code> class. It reads
0168: * information from the provided JAD and Manifest properties.
0169: *
0170: * @param jadProperties the JAD properties
0171: * @param jarProperties the Manifest properties
0172: * @return the instance of the <code>PaymentInfo</code> class
0173: * @throws PaymentException if some of the properties are incorrect,
0174: * incomplete, unsupported, etc.
0175: */
0176: public static PaymentInfo createFromProperties(
0177: Properties jadProperties, Properties jarProperties)
0178: throws PaymentException {
0179: PaymentInfo paymentInfo = new PaymentInfo();
0180:
0181: paymentInfo.loadFromJadProperties(jadProperties);
0182: paymentInfo.loadFromJarProperties(jarProperties);
0183:
0184: return paymentInfo;
0185: }
0186:
0187: /**
0188: * Validates JAD properties.
0189: *
0190: * @param jadProperties the JAD properties
0191: * @throws PaymentException if some of the properties are incorrect,
0192: * incomplete, unsupported, etc.
0193: */
0194: public static void validateJadProperties(Properties jadProperties)
0195: throws PaymentException {
0196: PaymentInfo paymentInfo = new PaymentInfo();
0197: paymentInfo.loadFromJadProperties(jadProperties);
0198: }
0199:
0200: /**
0201: * Validates the given payment update and if correct it updates the internal
0202: * state of the object accordingly.
0203: *
0204: * @param data a byte array which contains the payment update
0205: * @param charset the character set of the payment update
0206: * @throws PaymentException if the payment update is incorrect
0207: */
0208: public void updatePaymentInfo(byte[] data, String charset)
0209: throws PaymentException {
0210: Properties props;
0211:
0212: InputStream bis = new ByteArrayInputStream(data);
0213: try {
0214: try {
0215: props = utilities.loadProperties(bis, charset);
0216: } finally {
0217: bis.close();
0218: }
0219: } catch (UnsupportedEncodingException e) {
0220: throw new PaymentException(
0221: PaymentException.UNSUPPORTED_UPDATE_CHARSET,
0222: charset, null);
0223: } catch (IOException e) {
0224: throw new PaymentException(
0225: PaymentException.INVALID_PROPERTIES_FORMAT, e
0226: .getMessage());
0227: }
0228:
0229: // find a trusted provider certificate in one of the certification
0230: // chains of the payment update
0231: X509Certificate trustedCertificate = findTrustedCertificate(props);
0232:
0233: // get the public key for the trusted certificate
0234: PublicKey publicKey;
0235: try {
0236: publicKey = trustedCertificate.getPublicKey();
0237: } catch (CertificateException e) {
0238: throw new PaymentException(
0239: PaymentException.INVALID_PROVIDER_CERT,
0240: trustedCertificate.getSubject(), null);
0241: }
0242:
0243: // get the encoded signature
0244: String encodedSignature = props
0245: .getProperty(PAY_SIGNATURE_RSA_SHA1);
0246: if (encodedSignature == null) {
0247: throw new PaymentException(
0248: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
0249: PAY_SIGNATURE_RSA_SHA1, null);
0250: }
0251:
0252: byte[] signature;
0253: try {
0254: signature = Base64.decode(encodedSignature);
0255: } catch (IOException e) {
0256: throw new PaymentException(
0257: PaymentException.INVALID_ATTRIBUTE_VALUE,
0258: PAY_SIGNATURE_RSA_SHA1,
0259: "invalid or unsupported signature");
0260: }
0261:
0262: // get the data for verification
0263: String propString;
0264: byte[] testData;
0265:
0266: try {
0267: propString = new String(data, charset);
0268: testData = removePKIProperties(propString)
0269: .getBytes(charset);
0270: } catch (UnsupportedEncodingException e) {
0271: throw new PaymentException(
0272: PaymentException.UNSUPPORTED_UPDATE_CHARSET,
0273: charset, null);
0274: }
0275:
0276: // verify the signature
0277: try {
0278: Signature sigVerifier = Signature
0279: .getInstance("SHA1withRSA");
0280:
0281: sigVerifier.initVerify(publicKey);
0282:
0283: sigVerifier.update(testData, 0, testData.length);
0284: if (!sigVerifier.verify(signature)) {
0285: throw new PaymentException(
0286: PaymentException.SIGNATURE_VERIFICATION_FAILED);
0287: }
0288: } catch (GeneralSecurityException e) {
0289: throw new PaymentException(
0290: PaymentException.SIGNATURE_VERIFICATION_FAILED);
0291: }
0292:
0293: // validate and accept new values
0294: loadFromJppProperties(props);
0295: updateDate = new Date();
0296: }
0297:
0298: /**
0299: * Exports the payment information into the given character output stream.
0300: *
0301: * @param os the output stream
0302: * @throws IOException indicates an output error
0303: */
0304: public void export(Writer os) throws IOException {
0305: StringBuffer buffer = new StringBuffer();
0306:
0307: // Pay-Version: 1.0
0308: buffer.append(PAY_VERSION);
0309: buffer.append(": ");
0310: buffer.append(CURRENT_VERSION);
0311: buffer.append("\n");
0312:
0313: // Pay-Update-Date: <Date>
0314: if (updateDate != null) {
0315: buffer.append(PAY_UPDATE_DATE);
0316: buffer.append(": ");
0317: buffer
0318: .append(utilities.formatISODate(updateDate
0319: .getTime()));
0320: buffer.append("\n");
0321: }
0322:
0323: // Pay-Update-Stamp: <Date>
0324: buffer.append(PAY_UPDATE_STAMP);
0325: buffer.append(": ");
0326: buffer.append(utilities.formatISODate(updateStamp.getTime()));
0327: buffer.append("\n");
0328:
0329: // Pay-Update-URL: <UpdateURL>
0330: buffer.append(PAY_UPDATE_URL);
0331: buffer.append(": ");
0332: buffer.append(updateURL);
0333: buffer.append("\n");
0334:
0335: // Pay-Cache: [yes|no|<Expiration-Date>]
0336: buffer.append(PAY_CACHE);
0337: buffer.append(": ");
0338: if (expirationDate != null) {
0339: buffer.append(utilities.formatISODate(expirationDate
0340: .getTime()));
0341: } else {
0342: buffer.append(cache ? YES_VALUE : NO_VALUE);
0343: }
0344: buffer.append("\n");
0345:
0346: // Pay-Feature-<n>: <m>
0347: for (int i = 0; i < featureToTag.length; ++i) {
0348: buffer.append(PAY_FEATURE_PREFIX);
0349: buffer.append(i);
0350: buffer.append(": ");
0351: buffer.append(featureToTag[i]);
0352: buffer.append("\n");
0353: }
0354:
0355: os.write(buffer.toString());
0356: buffer.setLength(0);
0357:
0358: // Pay-Providers: <ProviderTitles>
0359: buffer.append(PAY_PROVIDERS);
0360: buffer.append(": ");
0361: buffer.append(providers[0].getName());
0362: for (int i = 1; i < providers.length; ++i) {
0363: buffer.append(", ");
0364: buffer.append(providers[i].getName());
0365: }
0366: buffer.append("\n");
0367:
0368: for (int i = 0; i < providers.length; ++i) {
0369: exportProvider(buffer, providers[i]);
0370: }
0371:
0372: os.write(buffer.toString());
0373: }
0374:
0375: /**
0376: * Test if the payment information can be used for payment as is or it
0377: * needs to be updated first from the update URL.
0378: *
0379: * @return <code>true</code> if the payment information needs to be updated
0380: */
0381: public boolean needsUpdate() {
0382: // 1. no cache => update
0383: if (!cache) {
0384: return true;
0385: }
0386:
0387: // 2. expired cache => update
0388: if (expirationDate != null) {
0389: long currentTime = System.currentTimeMillis();
0390: if (currentTime > expirationDate.getTime()) {
0391: return true;
0392: }
0393: }
0394:
0395: // 3. missing tags => update
0396: for (int i = 0; i < providers.length; ++i) {
0397: if (providers[i].getNumPriceTags() == 0) {
0398: return true;
0399: }
0400: }
0401:
0402: return false;
0403: }
0404:
0405: /**
0406: * Returns <code>true</code> if the payment information should be stored
0407: * for the next time.
0408: *
0409: * @return <code>true</code> if the payment information should be cached
0410: */
0411: public boolean cache() {
0412: return cache;
0413: }
0414:
0415: /**
0416: * Test for the system debug mode.
0417: *
0418: * @return <code>true</code> if the is running in the system debug mode
0419: */
0420: private native boolean isDebugMode();
0421:
0422: /**
0423: * Test for the debug demo mode.
0424: *
0425: * @return <code>true</code> if the debug demo mode should be activated
0426: */
0427: public boolean isDemoMode() {
0428: return dbgDemoMode && isDebugMode();
0429: }
0430:
0431: /**
0432: * Test for the debug fail initialize mode.
0433: *
0434: * @return <code>true</code> if the debug fail initialize mode should be
0435: * activated
0436: */
0437: public boolean getDbgFailInitialize() {
0438: return dbgFailInitialize;
0439: }
0440:
0441: /**
0442: * Test for the debug fail IO mode.
0443: *
0444: * @return <code>true</code> if the debug fail IO mode should be activated
0445: */
0446: public boolean getDbgFailIO() {
0447: return dbgFailIO;
0448: }
0449:
0450: /**
0451: * Returns the number of fake missed transactions that should be generated
0452: * when the application starts.
0453: *
0454: * @return the number of missed transactions to generate or <code>-1</code>
0455: * if this debug mode is disabled
0456: */
0457: public int getDbgMissedTransactions() {
0458: return dbgMissedTransactions;
0459: }
0460:
0461: /**
0462: * Test for the debug random tests mode.
0463: *
0464: * @return <code>true</code> if the debug random tests mode should be
0465: * activated
0466: */
0467: public boolean getDbgRandomTests() {
0468: return dbgRandomTests;
0469: }
0470:
0471: /**
0472: * Returns the debug auto request mode setting.
0473: *
0474: * @return <code>AUTO_REQUEST_OFF</code> if the auto request mode is
0475: * disabled, <code>AUTO_REQUEST_ACCEPT</code> if the auto request mode
0476: * is set to accept and <code>AUTO_REQUEST_REJECT</code> if it is set
0477: * to reject
0478: * @see #AUTO_REQUEST_OFF
0479: * @see #AUTO_REQUEST_ACCEPT
0480: * @see #AUTO_REQUEST_REJECT
0481: */
0482: public int getDbgAutoRequestMode() {
0483: return dbgAutoRequestMode;
0484: }
0485:
0486: /**
0487: * Returns the URL of the payment update.
0488: *
0489: * @return the update URL
0490: */
0491: public String getUpdateURL() {
0492: return updateURL;
0493: }
0494:
0495: /**
0496: * Returns the date of the last update or <code>null</code> if the payment
0497: * information has been never updated.
0498: *
0499: * @return the last update date or <code>null</code>
0500: */
0501: public Date getUpdateDate() {
0502: return updateDate;
0503: }
0504:
0505: /**
0506: * Gets the time stamp of last update.
0507: *
0508: * @return the time stamp
0509: */
0510: public Date getUpdateStamp() {
0511: return updateStamp;
0512: }
0513:
0514: /**
0515: * Gets the number of features the application can request the user to pay
0516: * for.
0517: *
0518: * @return the number of paid features
0519: */
0520: public int getNumFeatures() {
0521: return featureToTag.length;
0522: }
0523:
0524: /**
0525: * Returns the price tag for the given feature id.
0526: *
0527: * @param index the feature id
0528: * @return the price tag
0529: */
0530: public int getPriceTagForFeature(int index) {
0531: return featureToTag[index];
0532: }
0533:
0534: /**
0535: * Returns the number of providers which can be used to pay for the
0536: * application features.
0537: *
0538: * @return the number of providers
0539: */
0540: public int getNumProviders() {
0541: return providers.length;
0542: }
0543:
0544: /**
0545: * Return the provider information for the given provider id.
0546: *
0547: * @param index the provider id
0548: * @return the provider information
0549: */
0550: public ProviderInfo getProvider(int index) {
0551: return providers[index];
0552: }
0553:
0554: /**
0555: * Returns <code>true</code> if the given vector contains duplicate values.
0556: *
0557: * @param vector the vector of strings
0558: * @return <code>true</code> if the vector contains duplicate values
0559: */
0560: private boolean hasDuplicates(Vector vector) {
0561: int lastIndex = vector.size() - 1;
0562: for (int i = 0; i < lastIndex; ++i) {
0563: if (vector.indexOf(vector.elementAt(i), i + 1) != -1) {
0564: return true;
0565: }
0566: }
0567:
0568: return false;
0569: }
0570:
0571: /**
0572: * Constructs an string array from the given vector of strings. The
0573: * resulting array will contain the same strings as the vector and in the
0574: * same order as appeared in the vector.
0575: *
0576: * @param vector the vector of strings
0577: * @return the array of strings
0578: */
0579: private String[] toStringArray(Vector vector) {
0580: String[] strings = new String[vector.size()];
0581: vector.copyInto(strings);
0582:
0583: return strings;
0584: }
0585:
0586: /**
0587: * Returns <code>true</code> if the given name is a valid adapter name.
0588: *
0589: * @param name the name to test
0590: * @return <code>true</code> if the name is a valid adapter name
0591: */
0592: private boolean validateAdapterName(String name) {
0593: if (name.startsWith("X-")) {
0594: return name.length() > 2;
0595: }
0596:
0597: for (int i = 0; i < VALID_ADAPTER_NAMES.length; ++i) {
0598: if (VALID_ADAPTER_NAMES[i].equals(name)) {
0599: return true;
0600: }
0601: }
0602:
0603: return false;
0604: }
0605:
0606: /**
0607: * Returns <code>true</code> if the given string value represents a valid
0608: * currency code.
0609: *
0610: * @param name the string to test
0611: * @return <code>true</code> if the string is a valid currency code
0612: */
0613: private boolean validateCurrencyCode(String name) {
0614: if (name.length() != 3) {
0615: return false;
0616: }
0617:
0618: for (int i = 0; i < 3; ++i) {
0619: if ((name.charAt(i) < 'A') || (name.charAt(i) > 'Z')) {
0620: return false;
0621: }
0622: }
0623:
0624: return true;
0625: }
0626:
0627: /**
0628: * Parses an attribute which can have only one of the given predefined
0629: * values. It returns the index of the attribute's value or the
0630: * <code>defValue</code> if the attribute is not defined.
0631: *
0632: * @param props the properties to read the attribute from
0633: * @param attribute the name of the attribute
0634: * @param options the predefined values
0635: * @param defValue a value to return when the attribute is not defined
0636: * @return the index of a string from <code>options</code> which equals to
0637: * the attribute's value or <code>defValue</code>
0638: * @throws PaymentException if the attribute's value doesn't match any of
0639: * the predefined values
0640: */
0641: private int readOptionalSelection(Properties props,
0642: String attribute, String[] options, int defValue)
0643: throws PaymentException {
0644: String value = props.getProperty(attribute);
0645: if (value == null) {
0646: return defValue;
0647: }
0648:
0649: for (int i = 0; i < options.length; ++i) {
0650: if (options[i].equals(value)) {
0651: return i;
0652: }
0653: }
0654:
0655: StringBuffer buffer = new StringBuffer();
0656:
0657: buffer.append("expecting ");
0658: buffer.append(options[0]);
0659: int i;
0660: for (i = 1; i < (options.length - 1); ++i) {
0661: buffer.append(", ");
0662: buffer.append(options[i]);
0663: }
0664: buffer.append(" or ");
0665: buffer.append(options[i]);
0666:
0667: throw new PaymentException(
0668: PaymentException.INVALID_ATTRIBUTE_VALUE, attribute,
0669: buffer.toString());
0670: }
0671:
0672: /**
0673: * Parse and check the version number of the JAR-Manifest
0674: * or JAD fields.
0675: *
0676: * @param payVersion version string to check
0677: * @throws PaymentException if the parameter contains wrong
0678: * value or its value is greater than
0679: * the version of the Payment API
0680: * implemented in the device.
0681: */
0682: private void checkPayVersion(String payVersion)
0683: throws PaymentException {
0684: double curVer;
0685: double appVer;
0686: payVersion = payVersion.trim();
0687:
0688: // The format must be <major>.<minor>.
0689: // First accepted version is 1.0
0690: if ('1' > payVersion.charAt(0) || -1 == payVersion.indexOf('.')
0691: || 2 > payVersion.length() - payVersion.indexOf('.')) {
0692: // unsupported payment version
0693: throw new PaymentException(
0694: PaymentException.INVALID_ATTRIBUTE_VALUE,
0695: PAY_VERSION, null);
0696: }
0697:
0698: try {
0699: curVer = Float.parseFloat(CURRENT_VERSION);
0700: appVer = Float.parseFloat(payVersion);
0701: } catch (NumberFormatException nfe) {
0702: // unsupported payment version
0703: throw new PaymentException(
0704: PaymentException.INVALID_ATTRIBUTE_VALUE,
0705: PAY_VERSION, null);
0706: }
0707:
0708: if (curVer < appVer) {
0709: // unsupported payment version
0710: throw new PaymentException(
0711: PaymentException.UNSUPPORTED_PAYMENT_INFO,
0712: PAY_VERSION, null);
0713: }
0714: }
0715:
0716: /**
0717: * Updates the payment information from the given JAD file properties.
0718: * If an exception is thrown during the update the original object state
0719: * remains intact.
0720: *
0721: * @param props the JAD file properties
0722: * @throws PaymentException if the data read are incorrect or incomplete
0723: */
0724: private void loadFromJadProperties(Properties props)
0725: throws PaymentException {
0726: String payVersion = props.getProperty(PAY_VERSION);
0727: String payAdapters = props.getProperty(PAY_ADAPTERS);
0728:
0729: if (payVersion != null) {
0730: checkPayVersion(payVersion);
0731:
0732: if (payAdapters == null) {
0733: // missing PAY_ADAPTERS attribute
0734: throw new PaymentException(
0735: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
0736: PAY_ADAPTERS, null);
0737: }
0738: } else {
0739: if (payAdapters != null) {
0740: // missing PAY_VERSION attribute
0741: throw new PaymentException(
0742: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
0743: PAY_VERSION, null);
0744: }
0745: }
0746:
0747: String[] adapters = null;
0748: // read & validate adapters
0749: if (payVersion != null) {
0750: Vector names = Util.getCommaSeparatedValues(payAdapters);
0751:
0752: if (names.size() == 0) {
0753: throw new PaymentException(
0754: PaymentException.INVALID_ATTRIBUTE_VALUE,
0755: PAY_ADAPTERS, "the value is empty");
0756: }
0757:
0758: // avoid duplicate names
0759: if (hasDuplicates(names)) {
0760: throw new PaymentException(
0761: PaymentException.INVALID_ATTRIBUTE_VALUE,
0762: PAY_ADAPTERS, "duplicate fields in the value");
0763: }
0764:
0765: adapters = toStringArray(names);
0766:
0767: // validate adapter names
0768: for (int i = 0; i < adapters.length; ++i) {
0769: if (!validateAdapterName(adapters[i])) {
0770: throw new PaymentException(
0771: PaymentException.INVALID_ATTRIBUTE_VALUE,
0772: PAY_ADAPTERS, adapters[i]
0773: + " is not a valid "
0774: + "adapter name");
0775: }
0776: }
0777:
0778: // validate supported adapters
0779: PaymentModule paymentModule = PaymentModule.getInstance();
0780: int j;
0781:
0782: for (j = 0; j < adapters.length; ++j) {
0783: if (paymentModule.isSupportedAdapter(adapters[j])) {
0784: break;
0785: }
0786: }
0787:
0788: if (j == adapters.length) {
0789: throw new PaymentException(
0790: PaymentException.UNSUPPORTED_ADAPTERS,
0791: PAY_ADAPTERS, null);
0792: }
0793: }
0794:
0795: // read and validate Pay-Debug-* attributes
0796: boolean dbgDemoMode = readOptionalSelection(props,
0797: PAY_DBG_DEMOMODE, YES_NO_OPTIONS, 1) != 1;
0798: boolean dbgFailInitialize = readOptionalSelection(props,
0799: PAY_DBG_FAILINITIALIZE, YES_NO_OPTIONS, 1) != 1;
0800: boolean dbgFailIO = readOptionalSelection(props,
0801: PAY_DBG_FAILIO, YES_NO_OPTIONS, 1) != 1;
0802: int dbgMissedTransactions = -1;
0803: boolean dbgRandomTests = readOptionalSelection(props,
0804: PAY_DBG_RANDOMTESTS, YES_NO_OPTIONS, 1) != 1;
0805: int dbgAutoRequestMode = readOptionalSelection(props,
0806: PAY_DBG_AUTOREQUESTMODE, ACCEPT_REJECT_OPTIONS, -1) + 1;
0807:
0808: // Peyment spec 1.1
0809: // It is not used yet, but need for TCK passing
0810: boolean dbgNoAdapter = readOptionalSelection(props,
0811: PAY_DBG_NOADAPTER, YES_NO_OPTIONS, 1) != 1;
0812:
0813: String dbgMissedTransactionsStr = props
0814: .getProperty(PAY_DBG_MISSEDTRANSACTIONS);
0815: if (dbgMissedTransactionsStr != null) {
0816: try {
0817: dbgMissedTransactions = Integer
0818: .parseInt(dbgMissedTransactionsStr);
0819: } catch (NumberFormatException e) {
0820: }
0821:
0822: if (dbgMissedTransactions < 0) {
0823: throw new PaymentException(
0824: PaymentException.INVALID_ATTRIBUTE_VALUE,
0825: PAY_DBG_MISSEDTRANSACTIONS,
0826: "expecting a positive number");
0827: }
0828: }
0829:
0830: // everything is correct, let's change the object state
0831: this .adapters = adapters;
0832: this .dbgDemoMode = dbgDemoMode;
0833: this .dbgFailInitialize = dbgFailInitialize;
0834: this .dbgFailIO = dbgFailIO;
0835: this .dbgMissedTransactions = dbgMissedTransactions;
0836: this .dbgRandomTests = dbgRandomTests;
0837: this .dbgAutoRequestMode = dbgAutoRequestMode;
0838: }
0839:
0840: /**
0841: * Parses and returns the provider information for the given provider name
0842: * from the properties.
0843: *
0844: * @param props the properties to get provider from
0845: * @param provider the provider name
0846: * @return the provider information
0847: * @throws PaymentException if the provider information is incorrect or
0848: * incomplete
0849: */
0850: private ProviderInfo loadProvider(Properties props, String provider)
0851: throws PaymentException {
0852: String tempValue;
0853: String tempKey = PAY_PREFIX + provider + INFO_SUFFIX;
0854:
0855: tempValue = props.getProperty(tempKey);
0856:
0857: if (tempValue == null) {
0858: // missing or incorrect provider
0859: throw new PaymentException(
0860: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
0861: tempKey, null);
0862: }
0863:
0864: int offset = 0;
0865: int index;
0866:
0867: index = tempValue.indexOf(',');
0868: if (index == -1) {
0869: // missing currency code
0870: throw new PaymentException(
0871: PaymentException.INVALID_ATTRIBUTE_VALUE, tempKey,
0872: "the currency code is not present");
0873: }
0874:
0875: String adapter = tempValue.substring(offset, index).trim();
0876: // validate adapter name
0877: if (!validateAdapterName(adapter)) {
0878: throw new PaymentException(
0879: PaymentException.INVALID_ATTRIBUTE_VALUE, tempKey,
0880: adapter + " is not a valid adapter name");
0881: }
0882:
0883: offset = index + 1;
0884: index = tempValue.indexOf(',', offset);
0885:
0886: String currency;
0887: if (index == -1) {
0888: currency = tempValue.substring(offset).trim();
0889: } else {
0890: currency = tempValue.substring(offset, index).trim();
0891: }
0892: // validate currency
0893: if (!validateCurrencyCode(currency)) {
0894: throw new PaymentException(
0895: PaymentException.INVALID_ATTRIBUTE_VALUE, tempKey,
0896: "not a valid currency code");
0897: }
0898:
0899: // get configuration
0900: String configuration;
0901: if (index == -1) {
0902: throw new PaymentException(
0903: PaymentException.INVALID_ATTRIBUTE_VALUE, tempKey,
0904: "the payment specific info is not present");
0905: }
0906:
0907: offset = index + 1;
0908: configuration = tempValue.substring(offset).trim();
0909:
0910: tempKey = PAY_PREFIX + provider + TAG;
0911: tempValue = props.getProperty(tempKey + 0);
0912: double[] prices = null;
0913: String[] paySpecificPriceInfo = null;
0914: if (tempValue != null) {
0915: // contains tag attributes
0916: int numTags = 0;
0917: Vector tempVector = new Vector();
0918: do {
0919: tempVector.addElement(tempValue);
0920: tempValue = props.getProperty(tempKey + ++numTags);
0921: } while (tempValue != null);
0922:
0923: // we know the number of tags
0924: prices = new double[numTags];
0925: paySpecificPriceInfo = new String[numTags];
0926:
0927: for (int i = 0; i < numTags; ++i) {
0928: tempValue = (String) tempVector.elementAt(i);
0929:
0930: index = tempValue.indexOf(',');
0931:
0932: // parse and validate the price
0933: try {
0934: String tempPrice;
0935: if (index == -1) {
0936: tempPrice = tempValue.trim();
0937: } else {
0938: tempPrice = tempValue.substring(0, index)
0939: .trim();
0940: }
0941: prices[i] = Double.parseDouble(tempPrice);
0942: } catch (NumberFormatException e) {
0943: throw new PaymentException(
0944: PaymentException.INVALID_ATTRIBUTE_VALUE,
0945: tempKey + i, "invalid price");
0946: }
0947:
0948: // get pay specific price info if present
0949: if (index != -1) {
0950: paySpecificPriceInfo[i] = tempValue.substring(
0951: index + 1).trim();
0952: }
0953: }
0954: }
0955:
0956: // everything is correct, create the object
0957: return new ProviderInfo(provider, adapter, configuration,
0958: currency, prices, paySpecificPriceInfo);
0959: }
0960:
0961: /**
0962: * Loads the payment information from the given Manifest properties or
0963: * update file properties. The <code>strict</code> indicates if the
0964: * additional tests should be executed on the data read from the properties.
0965: * After passing these additional tests the resulting payment information
0966: * can be used for payment without any further update. Should an exception
0967: * be thrown during the loading the object state will remain intact.
0968: *
0969: * @param props the properties
0970: * @param strict if <code>true</code> the requirements on the data read
0971: * from the properties are harder
0972: * @throws PaymentException if the data read are incorrect or incomplete
0973: */
0974: private void loadFromPropertiesAux(Properties props, boolean strict)
0975: throws PaymentException {
0976: String tempValue;
0977:
0978: long currentTime = System.currentTimeMillis();
0979:
0980: tempValue = props.getProperty(PAY_VERSION);
0981:
0982: if (tempValue == null) {
0983: // missing PAY_VERSION attribute
0984: throw new PaymentException(
0985: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
0986: PAY_VERSION, null);
0987: }
0988:
0989: // throws Payment exception
0990: // if app pay version is greater than stack version
0991: checkPayVersion(tempValue);
0992:
0993: tempValue = props.getProperty(PAY_UPDATE_STAMP);
0994: Date updateStamp;
0995:
0996: if (tempValue == null) {
0997: // missing PAY_UPDATE_STAMP attribute
0998: throw new PaymentException(
0999: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
1000: PAY_UPDATE_STAMP, null);
1001: }
1002:
1003: // parse and validate the date
1004: try {
1005: long millis = utilities.parseISODate(tempValue);
1006: if (millis > currentTime) {
1007: throw new PaymentException(
1008: PaymentException.INFORMATION_NOT_YET_VALID);
1009: }
1010: updateStamp = new Date(millis);
1011: } catch (IllegalArgumentException e) {
1012: throw new PaymentException(
1013: PaymentException.INVALID_ATTRIBUTE_VALUE,
1014: PAY_UPDATE_STAMP, e.getMessage());
1015: }
1016:
1017: String updateURL = props.getProperty(PAY_UPDATE_URL);
1018:
1019: if (updateURL == null) {
1020: // missing PAY_UPDATE_URL attribute
1021: throw new PaymentException(
1022: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
1023: PAY_UPDATE_URL, null);
1024: }
1025:
1026: // validate the URL
1027: try {
1028: HttpUrl tempURL = new HttpUrl(updateURL);
1029: if (!"http".equals(tempURL.scheme)
1030: && !"https".equals(tempURL.scheme)) {
1031: throw new PaymentException(
1032: PaymentException.UNSUPPORTED_URL_SCHEME,
1033: tempURL.scheme, null);
1034: }
1035:
1036: } catch (IllegalArgumentException e) {
1037: throw new PaymentException(
1038: PaymentException.INVALID_ATTRIBUTE_VALUE,
1039: PAY_UPDATE_URL, e.getMessage());
1040: }
1041:
1042: tempValue = props.getProperty(PAY_CACHE);
1043: boolean cache = true;
1044: Date expirationDate = null;
1045:
1046: // validate and parse the PAY_CACHE attribute
1047: if (tempValue != null) {
1048: if (YES_VALUE.equals(tempValue)) {
1049: cache = true;
1050: } else if (NO_VALUE.equals(tempValue)) {
1051: cache = false;
1052: } else {
1053: try {
1054: long millis = utilities.parseISODate(tempValue);
1055: if (strict && (millis < currentTime)) {
1056: throw new PaymentException(
1057: PaymentException.INFORMATION_EXPIRED);
1058: }
1059: expirationDate = new Date(millis);
1060: } catch (IllegalArgumentException e) {
1061: throw new PaymentException(
1062: PaymentException.INVALID_ATTRIBUTE_VALUE,
1063: PAY_CACHE,
1064: "expecting yes, no or a valid date");
1065: }
1066: }
1067: }
1068:
1069: Vector tempVector = new Vector();
1070:
1071: tempValue = props.getProperty(PAY_FEATURE_PREFIX + 0);
1072:
1073: if (tempValue == null) {
1074: throw new PaymentException(
1075: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
1076: PAY_FEATURE_PREFIX + 0, null);
1077: }
1078:
1079: // read Pay-Feature-<n>
1080: int index = 0;
1081: tempVector.setSize(0);
1082: do {
1083: tempVector.addElement(tempValue);
1084: tempValue = props.getProperty(PAY_FEATURE_PREFIX + ++index);
1085: } while (tempValue != null);
1086:
1087: int maxTag = 0;
1088: int[] featureToTag = new int[index];
1089: // parse and validate the numbers
1090: for (int i = 0; i < index; ++i) {
1091: int value = -1;
1092: try {
1093: value = Integer.parseInt((String) tempVector
1094: .elementAt(i));
1095: } catch (NumberFormatException e) {
1096: }
1097:
1098: if (value < 0) {
1099: throw new PaymentException(
1100: PaymentException.INVALID_ATTRIBUTE_VALUE,
1101: PAY_FEATURE_PREFIX + i,
1102: "expecting a positive number");
1103: }
1104:
1105: if (maxTag < value) {
1106: maxTag = value;
1107: }
1108:
1109: featureToTag[i] = value;
1110: }
1111:
1112: tempValue = props.getProperty(PAY_PROVIDERS);
1113:
1114: if (tempValue == null) {
1115: // missing PAY_PROVIDERS attribute
1116: throw new PaymentException(
1117: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
1118: PAY_PROVIDERS, null);
1119: }
1120:
1121: Vector names = Util.getCommaSeparatedValues(tempValue);
1122:
1123: if (names.size() == 0) {
1124: throw new PaymentException(
1125: PaymentException.INVALID_ATTRIBUTE_VALUE,
1126: PAY_PROVIDERS, "the value is empty");
1127: }
1128:
1129: // avoid duplicate names
1130: if (hasDuplicates(names)) {
1131: throw new PaymentException(
1132: PaymentException.INVALID_ATTRIBUTE_VALUE,
1133: PAY_PROVIDERS, "duplicate fields in the value");
1134: }
1135:
1136: // IMPL_NOTE: check provider name?
1137:
1138: ProviderInfo[] providers = new ProviderInfo[names.size()];
1139: int numTags = maxTag + 1;
1140: boolean hasSupportedProvider = false;
1141: PaymentModule paymentModule = PaymentModule.getInstance();
1142: // read and validate provider infos
1143: for (int i = 0; i < providers.length; ++i) {
1144: // read provider
1145: ProviderInfo provider = loadProvider(props, (String) names
1146: .elementAt(i));
1147:
1148: if ((strict || (provider.getNumPriceTags() != 0))
1149: && (provider.getNumPriceTags() < numTags)) {
1150: throw new PaymentException(
1151: PaymentException.INCOMPLETE_INFORMATION);
1152: }
1153:
1154: // try to create an adapter for the provider
1155: PaymentAdapter adapter;
1156: try {
1157: adapter = paymentModule.getAdapter(provider
1158: .getAdapter(), provider.getConfiguration());
1159: } catch (PaymentException e) {
1160: e.setParam(PAY_PREFIX + provider.getName()
1161: + INFO_SUFFIX);
1162: throw e;
1163: }
1164:
1165: if (adapter != null) {
1166: // adapter has been created == we support at least one payment
1167: // provider
1168: hasSupportedProvider = true;
1169:
1170: int numTags2 = provider.getNumPriceTags();
1171: for (int j = 0; j < numTags2; ++j) {
1172: try {
1173: adapter.validatePriceInfo(provider.getPrice(j),
1174: provider.getPaySpecificPriceInfo(j));
1175: } catch (PaymentException e) {
1176: e.setParam(PAY_PREFIX + provider.getName()
1177: + TAG + j);
1178: throw e;
1179: }
1180: }
1181: }
1182:
1183: providers[i] = provider;
1184: }
1185:
1186: if (!hasSupportedProvider) {
1187: throw new PaymentException(
1188: PaymentException.UNSUPPORTED_PROVIDERS,
1189: PAY_PROVIDERS, null);
1190: }
1191:
1192: // everything is correct, let's change the object state
1193: this .updateStamp = updateStamp;
1194: this .updateURL = updateURL;
1195: this .cache = cache;
1196: this .expirationDate = expirationDate;
1197: this .featureToTag = featureToTag;
1198: this .providers = providers;
1199: }
1200:
1201: /**
1202: * Updates the payment information from the given Manifest properties. If
1203: * an exception is thrown during the update the original object state
1204: * remains intact.
1205: *
1206: * @param props the Manifest properties
1207: * @throws PaymentException if the data read are incorrect or incomplete
1208: */
1209: private void loadFromJarProperties(Properties props)
1210: throws PaymentException {
1211: loadFromPropertiesAux(props, false);
1212:
1213: // load the update date
1214: String tempValue = props.getProperty(PAY_UPDATE_DATE);
1215: updateDate = null;
1216: if (tempValue != null) {
1217: try {
1218: long millis = utilities.parseISODate(tempValue);
1219: updateDate = new Date(millis);
1220: } catch (IllegalArgumentException e) {
1221: }
1222: }
1223: }
1224:
1225: /**
1226: * Updates the payment information from the given update file properties.
1227: * If an exception is thrown during the update the original object state
1228: * remains intact.
1229: *
1230: * @param props the update file properties
1231: * @throws PaymentException if the data read are incorrect or incomplete
1232: */
1233: private void loadFromJppProperties(Properties props)
1234: throws PaymentException {
1235: loadFromPropertiesAux(props, true);
1236: }
1237:
1238: /**
1239: * Exports the given provider information into the given
1240: * <code>StringBuffer</code>.
1241: *
1242: * @param buffer the <code>StringBuffer</code>
1243: * @param provider the provider information
1244: */
1245: private void exportProvider(StringBuffer buffer,
1246: ProviderInfo provider) {
1247:
1248: // Pay-<ProviderTitle>
1249: String providerPrefix = PAY_PREFIX + provider.getName();
1250:
1251: // Pay-<ProviderTitle>-Info: <RegAdapter>, <ISO4217CurrencyCode>,
1252: // <PaymentSpecificInformation>
1253: buffer.append(providerPrefix);
1254: buffer.append(INFO_SUFFIX);
1255: buffer.append(": ");
1256: buffer.append(provider.getAdapter());
1257: buffer.append(", ");
1258: buffer.append(provider.getCurrency());
1259: buffer.append(", ");
1260: buffer.append(provider.getConfiguration());
1261: buffer.append("\n");
1262:
1263: // Pay-<ProviderTitle>-Tag-<m>: <Price>[,
1264: // <PaymentSpecificPriceInformation>]
1265: int count = provider.getNumPriceTags();
1266: for (int i = 0; i < count; ++i) {
1267: buffer.append(providerPrefix);
1268: buffer.append(TAG);
1269: buffer.append(i);
1270: buffer.append(": ");
1271: buffer.append(provider.getPrice(i));
1272: String value = provider.getPaySpecificPriceInfo(i);
1273: if (value != null) {
1274: buffer.append(", ");
1275: buffer.append(value);
1276: }
1277: buffer.append("\n");
1278: }
1279: }
1280:
1281: /**
1282: * Finds a trusted provider certificate in one of the certification
1283: * chains read from the given properties.
1284: *
1285: * @param props the properties
1286: * @return the trusted provider certificate
1287: * @throws PaymentException if some certification chain is incorrect or
1288: * none of them can be trusted
1289: */
1290: private static X509Certificate findTrustedCertificate(
1291: Properties props) throws PaymentException {
1292: int certPath = 1;
1293: int certIndex = 1;
1294: Vector certificates = new Vector();
1295: String encodedCert = props.getProperty(PAY_CERTIFICATE_PREFIX
1296: + certPath + "-" + certIndex);
1297:
1298: if (encodedCert == null) {
1299: throw new PaymentException(
1300: PaymentException.MISSING_MANDATORY_ATTRIBUTE,
1301: PAY_CERTIFICATE_PREFIX + certPath + "-" + certIndex,
1302: null);
1303: }
1304:
1305: do {
1306: certificates.setSize(0);
1307:
1308: do {
1309: try {
1310: byte[] binaryCert = Base64.decode(encodedCert);
1311: certificates.addElement(X509Certificate
1312: .generateCertificate(binaryCert, 0,
1313: binaryCert.length));
1314: } catch (IOException e) {
1315: throw new PaymentException(
1316: PaymentException.INVALID_ATTRIBUTE_VALUE,
1317: PAY_CERTIFICATE_PREFIX + certPath + "-"
1318: + certIndex,
1319: "invalid or unsupported certificate");
1320: }
1321:
1322: encodedCert = props.getProperty(PAY_CERTIFICATE_PREFIX
1323: + certPath + "-" + ++certIndex);
1324: } while (encodedCert != null);
1325:
1326: try {
1327: String[] authPath = X509Certificate.verifyChain(
1328: certificates,
1329: X509Certificate.DIGITAL_SIG_KEY_USAGE,
1330: X509Certificate.CODE_SIGN_EXT_KEY_USAGE,
1331: WebPublicKeyStore.getTrustedKeyStore());
1332: String domain = Permissions.UNIDENTIFIED_DOMAIN_BINDING;
1333: Vector keys = WebPublicKeyStore.getTrustedKeyStore()
1334: .findKeys(authPath[0]);
1335: if (keys != null) {
1336: domain = ((PublicKeyInfo) keys.elementAt(0))
1337: .getDomain();
1338: }
1339:
1340: if (!Permissions.UNIDENTIFIED_DOMAIN_BINDING
1341: .equals(domain)) {
1342: // we verified the chain
1343: return (X509Certificate) certificates.elementAt(0);
1344: }
1345:
1346: // try next chain
1347:
1348: } catch (CertificateException e) {
1349: switch (e.getReason()) {
1350: case CertificateException.UNRECOGNIZED_ISSUER:
1351: // try next chain
1352: break;
1353: case CertificateException.EXPIRED:
1354: case CertificateException.NOT_YET_VALID:
1355: throw new PaymentException(
1356: PaymentException.EXPIRED_PROVIDER_CERT, e
1357: .getCertificate().getSubject(),
1358: null);
1359: case CertificateException.ROOT_CA_EXPIRED:
1360: throw new PaymentException(
1361: PaymentException.EXPIRED_CA_CERT, e
1362: .getCertificate().getIssuer(), null);
1363: default:
1364: throw new PaymentException(
1365: PaymentException.INVALID_PROVIDER_CERT, e
1366: .getCertificate().getSubject(),
1367: null);
1368: }
1369: }
1370:
1371: certIndex = 1;
1372: encodedCert = props.getProperty(PAY_CERTIFICATE_PREFIX
1373: + ++certPath + "-" + certIndex);
1374: } while (encodedCert != null);
1375:
1376: throw new PaymentException(PaymentException.NO_TRUSTED_CHAIN);
1377: }
1378:
1379: /**
1380: * Strips all empty lines and lines containing any of
1381: * <code>Pay-Certificate-*</code> or <code>Pay-Signature-*</code>
1382: * attributes from the given string.
1383: *
1384: * @param string property containing string
1385: * @return the altered string
1386: */
1387: private static String removePKIProperties(String string) {
1388: char[] data = string.toCharArray();
1389: int length = data.length;
1390: StringBuffer buffer = new StringBuffer();
1391:
1392: int i = 0;
1393: int j, k;
1394: do {
1395: // skip empty lines
1396: for (j = i; (j < length) && (data[j] != '\n')
1397: && (data[j] <= ' '); ++j) {
1398: }
1399: if (j == length) {
1400: break;
1401: }
1402: if (data[j] == '\n') {
1403: i = j + 1;
1404: continue;
1405: }
1406:
1407: // find matching prefix
1408: int prefixIdx;
1409: for (prefixIdx = 0; prefixIdx < PKI_PREFIXES.length; ++prefixIdx) {
1410: char[] prefix = PKI_PREFIXES[prefixIdx];
1411: for (k = 0, j = i; (j < length) && (k < prefix.length)
1412: && (data[j] == prefix[k]); ++j, ++k) {
1413: }
1414: if (k == prefix.length) {
1415: break;
1416: }
1417: }
1418:
1419: // find the end of the line
1420: for (j = i; (j < length) && (data[j] != '\n'); ++j) {
1421: }
1422: if (j < length) {
1423: // skip '\n'
1424: ++j;
1425: }
1426:
1427: // accept the lines that don't start with any of PKI_PREFIXES
1428: if (prefixIdx == PKI_PREFIXES.length) {
1429: buffer.append(data, i, j - i);
1430: }
1431: i = j;
1432: } while (i < length);
1433:
1434: return buffer.toString();
1435: }
1436: }
|