/****************************************************************************** * Copyright © 2013-2016 The Nxt Core Developers. * * * * See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * Nxt software, including this file, may be copied, modified, propagated, * * or distributed except according to the terms contained in the LICENSE.txt * * file. * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ package nxt.http; import nxt.Account; import nxt.Alias; import nxt.Appendix; import nxt.Asset; import nxt.Attachment; import nxt.Constants; import nxt.Currency; import nxt.CurrencyBuyOffer; import nxt.CurrencySellOffer; import nxt.DigitalGoodsStore; import nxt.Nxt; import nxt.NxtException; import nxt.Poll; import nxt.Shuffling; import nxt.Transaction; import nxt.crypto.Crypto; import nxt.crypto.EncryptedData; import nxt.util.Convert; import nxt.util.Logger; import nxt.util.Search; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.Part; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.StringJoiner; import static nxt.http.JSONResponses.*; final class ParameterParser { static byte getByte(HttpServletRequest req, String name, byte min, byte max, boolean isMandatory) throws ParameterException { String paramValue = Convert.emptyToNull(req.getParameter(name)); if (paramValue == null) { if (isMandatory) { throw new ParameterException(missing(name)); } return 0; } try { byte value = Byte.parseByte(paramValue); if (value < min || value > max) { throw new ParameterException(incorrect(name, String.format("value %d not in range [%d-%d]", value, min, max))); } return value; } catch (RuntimeException e) { throw new ParameterException(incorrect(name, String.format("value %s is not numeric", paramValue))); } } static int getInt(HttpServletRequest req, String name, int min, int max, boolean isMandatory) throws ParameterException { String paramValue = Convert.emptyToNull(req.getParameter(name)); if (paramValue == null) { if (isMandatory) { throw new ParameterException(missing(name)); } return 0; } try { int value = Integer.parseInt(paramValue); if (value < min || value > max) { throw new ParameterException(incorrect(name, String.format("value %d not in range [%d-%d]", value, min, max))); } return value; } catch (RuntimeException e) { throw new ParameterException(incorrect(name, String.format("value %s is not numeric", paramValue))); } } static long getLong(HttpServletRequest req, String name, long min, long max, boolean isMandatory) throws ParameterException { String paramValue = Convert.emptyToNull(req.getParameter(name)); if (paramValue == null) { if (isMandatory) { throw new ParameterException(missing(name)); } return 0; } try { long value = Long.parseLong(paramValue); if (value < min || value > max) { throw new ParameterException(incorrect(name, String.format("value %d not in range [%d-%d]", value, min, max))); } return value; } catch (RuntimeException e) { throw new ParameterException(incorrect(name, String.format("value %s is not numeric", paramValue))); } } static long getUnsignedLong(HttpServletRequest req, String name, boolean isMandatory) throws ParameterException { String paramValue = Convert.emptyToNull(req.getParameter(name)); if (paramValue == null) { if (isMandatory) { throw new ParameterException(missing(name)); } return 0; } try { long value = Convert.parseUnsignedLong(paramValue); if (value == 0) { // 0 is not allowed as an id throw new ParameterException(incorrect(name)); } return value; } catch (RuntimeException e) { throw new ParameterException(incorrect(name)); } } static long[] getUnsignedLongs(HttpServletRequest req, String name) throws ParameterException { String[] paramValues = req.getParameterValues(name); if (paramValues == null || paramValues.length == 0) { throw new ParameterException(missing(name)); } long[] values = new long[paramValues.length]; try { for (int i = 0; i < paramValues.length; i++) { if (paramValues[i] == null || paramValues[i].isEmpty()) { throw new ParameterException(incorrect(name)); } values[i] = Long.parseUnsignedLong(paramValues[i]); if (values[i] == 0) { throw new ParameterException(incorrect(name)); } } } catch (RuntimeException e) { throw new ParameterException(incorrect(name)); } return values; } static byte[] getBytes(HttpServletRequest req, String name, boolean isMandatory) throws ParameterException { String paramValue = Convert.emptyToNull(req.getParameter(name)); if (paramValue == null) { if (isMandatory) { throw new ParameterException(missing(name)); } return Convert.EMPTY_BYTE; } return Convert.parseHexString(paramValue); } static long getAccountId(HttpServletRequest req, boolean isMandatory) throws ParameterException { return getAccountId(req, "account", isMandatory); } static long getAccountId(HttpServletRequest req, String name, boolean isMandatory) throws ParameterException { String paramValue = Convert.emptyToNull(req.getParameter(name)); if (paramValue == null) { if (isMandatory) { throw new ParameterException(missing(name)); } return 0; } try { long value = Convert.parseAccountId(paramValue); if (value == 0) { throw new ParameterException(incorrect(name)); } return value; } catch (RuntimeException e) { throw new ParameterException(incorrect(name)); } } static long[] getAccountIds(HttpServletRequest req, boolean isMandatory) throws ParameterException { String[] paramValues = req.getParameterValues("account"); if (paramValues == null || paramValues.length == 0) { if (isMandatory) { throw new ParameterException(MISSING_ACCOUNT); } else { return Convert.EMPTY_LONG; } } long[] values = new long[paramValues.length]; try { for (int i = 0; i < paramValues.length; i++) { if (paramValues[i] == null || paramValues[i].isEmpty()) { throw new ParameterException(INCORRECT_ACCOUNT); } values[i] = Convert.parseAccountId(paramValues[i]); if (values[i] == 0) { throw new ParameterException(INCORRECT_ACCOUNT); } } } catch (RuntimeException e) { throw new ParameterException(INCORRECT_ACCOUNT); } return values; } static Alias getAlias(HttpServletRequest req) throws ParameterException { long aliasId; try { aliasId = Convert.parseUnsignedLong(Convert.emptyToNull(req.getParameter("alias"))); } catch (RuntimeException e) { throw new ParameterException(INCORRECT_ALIAS); } String aliasName = Convert.emptyToNull(req.getParameter("aliasName")); Alias alias; if (aliasId != 0) { alias = Alias.getAlias(aliasId); } else if (aliasName != null) { alias = Alias.getAlias(aliasName); } else { throw new ParameterException(MISSING_ALIAS_OR_ALIAS_NAME); } if (alias == null) { throw new ParameterException(UNKNOWN_ALIAS); } return alias; } static long getAmountNQT(HttpServletRequest req) throws ParameterException { return getLong(req, "amountNQT", 1L, Constants.MAX_BALANCE_NQT, true); } static long getFeeNQT(HttpServletRequest req) throws ParameterException { return getLong(req, "feeNQT", 0L, Constants.MAX_BALANCE_NQT, true); } static long getPriceNQT(HttpServletRequest req) throws ParameterException { return getLong(req, "priceNQT", 1L, Constants.MAX_BALANCE_NQT, true); } static Poll getPoll(HttpServletRequest req) throws ParameterException { Poll poll = Poll.getPoll(getUnsignedLong(req, "poll", true)); if (poll == null) { throw new ParameterException(UNKNOWN_POLL); } return poll; } static Asset getAsset(HttpServletRequest req) throws ParameterException { Asset asset = Asset.getAsset(getUnsignedLong(req, "asset", true)); if (asset == null) { throw new ParameterException(UNKNOWN_ASSET); } return asset; } static Currency getCurrency(HttpServletRequest req) throws ParameterException { return getCurrency(req, true); } static Currency getCurrency(HttpServletRequest req, boolean isMandatory) throws ParameterException { Currency currency = Currency.getCurrency(getUnsignedLong(req, "currency", isMandatory)); if (isMandatory && currency == null) { throw new ParameterException(UNKNOWN_CURRENCY); } return currency; } static CurrencyBuyOffer getBuyOffer(HttpServletRequest req) throws ParameterException { CurrencyBuyOffer offer = CurrencyBuyOffer.getOffer(getUnsignedLong(req, "offer", true)); if (offer == null) { throw new ParameterException(UNKNOWN_OFFER); } return offer; } static CurrencySellOffer getSellOffer(HttpServletRequest req) throws ParameterException { CurrencySellOffer offer = CurrencySellOffer.getOffer(getUnsignedLong(req, "offer", true)); if (offer == null) { throw new ParameterException(UNKNOWN_OFFER); } return offer; } static Shuffling getShuffling(HttpServletRequest req) throws ParameterException { Shuffling shuffling = Shuffling.getShuffling(getUnsignedLong(req, "shuffling", true)); if (shuffling == null) { throw new ParameterException(UNKNOWN_SHUFFLING); } return shuffling; } static long getQuantityQNT(HttpServletRequest req) throws ParameterException { return getLong(req, "quantityQNT", 1L, Constants.MAX_ASSET_QUANTITY_QNT, true); } static long getAmountNQTPerQNT(HttpServletRequest req) throws ParameterException { return getLong(req, "amountNQTPerQNT", 1L, Constants.MAX_BALANCE_NQT, true); } static DigitalGoodsStore.Goods getGoods(HttpServletRequest req) throws ParameterException { DigitalGoodsStore.Goods goods = DigitalGoodsStore.Goods.getGoods(getUnsignedLong(req, "goods", true)); if (goods == null) { throw new ParameterException(UNKNOWN_GOODS); } return goods; } static int getGoodsQuantity(HttpServletRequest req) throws ParameterException { return getInt(req, "quantity", 0, Constants.MAX_DGS_LISTING_QUANTITY, true); } static EncryptedData getEncryptedData(HttpServletRequest req, String messageType) throws ParameterException { String dataString = Convert.emptyToNull(req.getParameter(messageType + "Data")); String nonceString = Convert.emptyToNull(req.getParameter(messageType + "Nonce")); if (dataString == null || nonceString == null) { return null; } byte[] data; byte[] nonce; try { data = Convert.parseHexString(dataString); } catch (RuntimeException e) { throw new ParameterException(JSONResponses.incorrect(messageType + "Data")); } try { nonce = Convert.parseHexString(nonceString); } catch (RuntimeException e) { throw new ParameterException(JSONResponses.incorrect(messageType + "Nonce")); } return new EncryptedData(data, nonce); } static Appendix.EncryptToSelfMessage getEncryptToSelfMessage(HttpServletRequest req) throws ParameterException { boolean isText = !"false".equalsIgnoreCase(req.getParameter("messageToEncryptToSelfIsText")); boolean compress = !"false".equalsIgnoreCase(req.getParameter("compressMessageToEncryptToSelf")); byte[] plainMessageBytes = null; EncryptedData encryptedData = ParameterParser.getEncryptedData(req, "encryptToSelfMessage"); if (encryptedData == null) { String plainMessage = Convert.emptyToNull(req.getParameter("messageToEncryptToSelf")); if (plainMessage == null) { return null; } try { plainMessageBytes = isText ? Convert.toBytes(plainMessage) : Convert.parseHexString(plainMessage); } catch (RuntimeException e) { throw new ParameterException(INCORRECT_MESSAGE_TO_ENCRYPT); } String secretPhrase = getSecretPhrase(req, false); if (secretPhrase != null) { byte[] publicKey = Crypto.getPublicKey(secretPhrase); encryptedData = Account.encryptTo(publicKey, plainMessageBytes, secretPhrase, compress); } } if (encryptedData != null) { return new Appendix.EncryptToSelfMessage(encryptedData, isText, compress); } else { return new Appendix.UnencryptedEncryptToSelfMessage(plainMessageBytes, isText, compress); } } static DigitalGoodsStore.Purchase getPurchase(HttpServletRequest req) throws ParameterException { DigitalGoodsStore.Purchase purchase = DigitalGoodsStore.Purchase.getPurchase(getUnsignedLong(req, "purchase", true)); if (purchase == null) { throw new ParameterException(INCORRECT_PURCHASE); } return purchase; } static String getSecretPhrase(HttpServletRequest req, boolean isMandatory) throws ParameterException { String secretPhrase = Convert.emptyToNull(req.getParameter("secretPhrase")); if (secretPhrase == null && isMandatory) { throw new ParameterException(MISSING_SECRET_PHRASE); } return secretPhrase; } static byte[] getPublicKey(HttpServletRequest req) throws ParameterException { return getPublicKey(req, null); } static byte[] getPublicKey(HttpServletRequest req, String prefix) throws ParameterException { String secretPhraseParam = prefix == null ? "secretPhrase" : (prefix + "SecretPhrase"); String publicKeyParam = prefix == null ? "publicKey" : (prefix + "PublicKey"); String secretPhrase = Convert.emptyToNull(req.getParameter(secretPhraseParam)); if (secretPhrase == null) { try { byte[] publicKey = Convert.parseHexString(Convert.emptyToNull(req.getParameter(publicKeyParam))); if (publicKey == null) { throw new ParameterException(missing(secretPhraseParam, publicKeyParam)); } if (!Crypto.isCanonicalPublicKey(publicKey)) { throw new ParameterException(incorrect(publicKeyParam)); } return publicKey; } catch (RuntimeException e) { throw new ParameterException(incorrect(publicKeyParam)); } } else { return Crypto.getPublicKey(secretPhrase); } } static Account getSenderAccount(HttpServletRequest req) throws ParameterException { byte[] publicKey = getPublicKey(req); Account account = Account.getAccount(publicKey); if (account == null) { throw new ParameterException(UNKNOWN_ACCOUNT); } return account; } static Account getAccount(HttpServletRequest req) throws ParameterException { return getAccount(req, true); } static Account getAccount(HttpServletRequest req, boolean isMandatory) throws ParameterException { long accountId = getAccountId(req, "account", isMandatory); if (accountId == 0 && !isMandatory) { return null; } Account account = Account.getAccount(accountId); if (account == null) { throw new ParameterException(JSONResponses.unknownAccount(accountId)); } return account; } static List<Account> getAccounts(HttpServletRequest req) throws ParameterException { String[] accountValues = req.getParameterValues("account"); if (accountValues == null || accountValues.length == 0) { throw new ParameterException(MISSING_ACCOUNT); } List<Account> result = new ArrayList<>(); for (String accountValue : accountValues) { if (accountValue == null || accountValue.equals("")) { continue; } try { Account account = Account.getAccount(Convert.parseAccountId(accountValue)); if (account == null) { throw new ParameterException(UNKNOWN_ACCOUNT); } result.add(account); } catch (RuntimeException e) { throw new ParameterException(INCORRECT_ACCOUNT); } } return result; } static int getTimestamp(HttpServletRequest req) throws ParameterException { return getInt(req, "timestamp", 0, Integer.MAX_VALUE, false); } static int getFirstIndex(HttpServletRequest req) { try { int firstIndex = Integer.parseInt(req.getParameter("firstIndex")); if (firstIndex < 0) { return 0; } return firstIndex; } catch (NumberFormatException e) { return 0; } } static int getLastIndex(HttpServletRequest req) { int lastIndex = Integer.MAX_VALUE; try { lastIndex = Integer.parseInt(req.getParameter("lastIndex")); if (lastIndex < 0) { lastIndex = Integer.MAX_VALUE; } } catch (NumberFormatException ignored) {} if (!API.checkPassword(req)) { int firstIndex = Math.min(getFirstIndex(req), Integer.MAX_VALUE - API.maxRecords + 1); lastIndex = Math.min(lastIndex, firstIndex + API.maxRecords - 1); } return lastIndex; } static int getNumberOfConfirmations(HttpServletRequest req) throws ParameterException { return getInt(req, "numberOfConfirmations", 0, Nxt.getBlockchain().getHeight(), false); } static int getHeight(HttpServletRequest req) throws ParameterException { String heightValue = Convert.emptyToNull(req.getParameter("height")); if (heightValue != null) { try { int height = Integer.parseInt(heightValue); if (height < 0 || height > Nxt.getBlockchain().getHeight()) { throw new ParameterException(INCORRECT_HEIGHT); } return height; } catch (NumberFormatException e) { throw new ParameterException(INCORRECT_HEIGHT); } } return -1; } static String getSearchQuery(HttpServletRequest req) throws ParameterException { String query = Convert.nullToEmpty(req.getParameter("query")).trim(); String tags = Convert.nullToEmpty(req.getParameter("tag")).trim(); if (query.isEmpty() && tags.isEmpty()) { throw new ParameterException(JSONResponses.missing("query", "tag")); } if (!tags.isEmpty()) { StringJoiner stringJoiner = new StringJoiner(" AND TAGS:", "TAGS:", ""); for (String tag : Search.parseTags(tags, 0, Integer.MAX_VALUE, Integer.MAX_VALUE)) { stringJoiner.add(tag); } query = stringJoiner.toString() + (query.isEmpty() ? "" : (" AND (" + query + ")")); } return query; } static Transaction.Builder parseTransaction(String transactionJSON, String transactionBytes, String prunableAttachmentJSON) throws ParameterException { if (transactionBytes == null && transactionJSON == null) { throw new ParameterException(MISSING_TRANSACTION_BYTES_OR_JSON); } if (transactionBytes != null && transactionJSON != null) { throw new ParameterException(either("transactionBytes", "transactionJSON")); } if (prunableAttachmentJSON != null && transactionBytes == null) { throw new ParameterException(JSONResponses.missing("transactionBytes")); } if (transactionJSON != null) { try { JSONObject json = (JSONObject) JSONValue.parseWithException(transactionJSON); return Nxt.newTransactionBuilder(json); } catch (NxtException.ValidationException | RuntimeException | ParseException e) { Logger.logDebugMessage(e.getMessage(), e); JSONObject response = new JSONObject(); JSONData.putException(response, e, "Incorrect transactionJSON"); throw new ParameterException(response); } } else { try { byte[] bytes = Convert.parseHexString(transactionBytes); JSONObject prunableAttachments = prunableAttachmentJSON == null ? null : (JSONObject)JSONValue.parseWithException(prunableAttachmentJSON); return Nxt.newTransactionBuilder(bytes, prunableAttachments); } catch (NxtException.ValidationException|RuntimeException | ParseException e) { Logger.logDebugMessage(e.getMessage(), e); JSONObject response = new JSONObject(); JSONData.putException(response, e, "Incorrect transactionBytes"); throw new ParameterException(response); } } } static Appendix getPlainMessage(HttpServletRequest req, boolean prunable) throws ParameterException { String messageValue = Convert.emptyToNull(req.getParameter("message")); if (messageValue != null) { boolean messageIsText = !"false".equalsIgnoreCase(req.getParameter("messageIsText")); try { if (prunable) { return new Appendix.PrunablePlainMessage(messageValue, messageIsText); } else { return new Appendix.Message(messageValue, messageIsText); } } catch (RuntimeException e) { throw new ParameterException(INCORRECT_ARBITRARY_MESSAGE); } } return null; } static Appendix getEncryptedMessage(HttpServletRequest req, Account recipient, boolean prunable) throws ParameterException { boolean isText = !"false".equalsIgnoreCase(req.getParameter("messageToEncryptIsText")); boolean compress = !"false".equalsIgnoreCase(req.getParameter("compressMessageToEncrypt")); byte[] plainMessageBytes = null; byte[] recipientPublicKey = null; EncryptedData encryptedData = ParameterParser.getEncryptedData(req, "encryptedMessage"); if (encryptedData == null) { String plainMessage = Convert.emptyToNull(req.getParameter("messageToEncrypt")); if (plainMessage == null) { return null; } if (recipient != null) { recipientPublicKey = recipient.getPublicKey(); } if (recipientPublicKey == null) { recipientPublicKey = Convert.parseHexString(Convert.emptyToNull(req.getParameter("recipientPublicKey"))); } if (recipientPublicKey == null) { throw new ParameterException(MISSING_RECIPIENT_PUBLIC_KEY); } try { plainMessageBytes = isText ? Convert.toBytes(plainMessage) : Convert.parseHexString(plainMessage); } catch (RuntimeException e) { throw new ParameterException(INCORRECT_MESSAGE_TO_ENCRYPT); } String secretPhrase = getSecretPhrase(req, false); if (secretPhrase != null) { encryptedData = Account.encryptTo(recipientPublicKey, plainMessageBytes, secretPhrase, compress); } } if (encryptedData != null) { if (prunable) { return new Appendix.PrunableEncryptedMessage(encryptedData, isText, compress); } else { return new Appendix.EncryptedMessage(encryptedData, isText, compress); } } else { if (prunable) { return new Appendix.UnencryptedPrunableEncryptedMessage(plainMessageBytes, isText, compress, recipientPublicKey); } else { return new Appendix.UnencryptedEncryptedMessage(plainMessageBytes, isText, compress, recipientPublicKey); } } } static Attachment.TaggedDataUpload getTaggedData(HttpServletRequest req) throws ParameterException, NxtException.NotValidException { String name = Convert.emptyToNull(req.getParameter("name")); String description = Convert.nullToEmpty(req.getParameter("description")); String tags = Convert.nullToEmpty(req.getParameter("tags")); String type = Convert.nullToEmpty(req.getParameter("type")).trim(); String channel = Convert.nullToEmpty(req.getParameter("channel")); boolean isText = !"false".equalsIgnoreCase(req.getParameter("isText")); String filename = Convert.nullToEmpty(req.getParameter("filename")).trim(); String dataValue = Convert.emptyToNull(req.getParameter("data")); byte[] data; if (dataValue == null) { try { Part part = req.getPart("file"); if (part == null) { throw new ParameterException(INCORRECT_TAGGED_DATA_FILE); } FileData fileData = new FileData(part).invoke(); data = fileData.getData(); // Depending on how the client submits the form, the filename, can be a regular parameter // or encoded in the multipart form. If its not a parameter we take from the form if (filename.isEmpty() && fileData.getFilename() != null) { filename = fileData.getFilename().trim(); } if (name == null) { name = filename; } } catch (IOException | ServletException e) { Logger.logDebugMessage("error in reading file data", e); throw new ParameterException(INCORRECT_TAGGED_DATA_FILE); } } else { data = isText ? Convert.toBytes(dataValue) : Convert.parseHexString(dataValue); } String detectedMimeType = Search.detectMimeType(data, filename); if (detectedMimeType != null) { isText = detectedMimeType.equals("text/plain"); if (type.isEmpty()) { type = detectedMimeType.substring(0, Math.min(detectedMimeType.length(), Constants.MAX_TAGGED_DATA_TYPE_LENGTH)); } } if (name == null) { throw new ParameterException(MISSING_NAME); } name = name.trim(); if (name.length() > Constants.MAX_TAGGED_DATA_NAME_LENGTH) { throw new ParameterException(INCORRECT_TAGGED_DATA_NAME); } if (description.length() > Constants.MAX_TAGGED_DATA_DESCRIPTION_LENGTH) { throw new ParameterException(INCORRECT_TAGGED_DATA_DESCRIPTION); } if (tags.length() > Constants.MAX_TAGGED_DATA_TAGS_LENGTH) { throw new ParameterException(INCORRECT_TAGGED_DATA_TAGS); } type = type.trim(); if (type.length() > Constants.MAX_TAGGED_DATA_TYPE_LENGTH) { throw new ParameterException(INCORRECT_TAGGED_DATA_TYPE); } channel = channel.trim(); if (channel.length() > Constants.MAX_TAGGED_DATA_CHANNEL_LENGTH) { throw new ParameterException(INCORRECT_TAGGED_DATA_CHANNEL); } if (data.length == 0 || data.length > Constants.MAX_TAGGED_DATA_DATA_LENGTH) { throw new ParameterException(INCORRECT_DATA); } if (filename.length() > Constants.MAX_TAGGED_DATA_FILENAME_LENGTH) { throw new ParameterException(INCORRECT_TAGGED_DATA_FILENAME); } return new Attachment.TaggedDataUpload(name, description, tags, type, channel, isText, filename, data); } private ParameterParser() {} // never static class FileData { private final Part part; private String filename; private byte[] data; public FileData(Part part) { this.part = part; } public String getFilename() { return filename; } public byte[] getData() { return data; } public FileData invoke() throws IOException { try (InputStream is = part.getInputStream()) { int nRead; byte[] bytes = new byte[1024]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); while ((nRead = is.read(bytes, 0, bytes.length)) != -1) { baos.write(bytes, 0, nRead); } data = baos.toByteArray(); filename = part.getSubmittedFileName(); } return this; } } }