/****************************************************************************** * 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.Db; import nxt.Nxt; import nxt.NxtException; import nxt.util.JSON; import nxt.util.Logger; import org.json.simple.JSONObject; import org.json.simple.JSONStreamAware; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static nxt.http.JSONResponses.ERROR_INCORRECT_REQUEST; import static nxt.http.JSONResponses.ERROR_NOT_ALLOWED; import static nxt.http.JSONResponses.POST_REQUIRED; import static nxt.http.JSONResponses.REQUIRED_BLOCK_NOT_FOUND; import static nxt.http.JSONResponses.REQUIRED_LAST_BLOCK_NOT_FOUND; public final class APIServlet extends HttpServlet { abstract static class APIRequestHandler { private final List<String> parameters; private final String fileParameter; private final Set<APITag> apiTags; APIRequestHandler(APITag[] apiTags, String... parameters) { this(null, apiTags, parameters); } APIRequestHandler(String fileParameter, APITag[] apiTags, String... origParameters) { List<String> parameters = new ArrayList<>(); Collections.addAll(parameters, origParameters); if ((requirePassword() || parameters.contains("lastIndex")) && ! API.disableAdminPassword) { parameters.add("adminPassword"); } if (allowRequiredBlockParameters()) { parameters.add("requireBlock"); parameters.add("requireLastBlock"); } this.parameters = Collections.unmodifiableList(parameters); this.apiTags = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(apiTags))); this.fileParameter = fileParameter; } final List<String> getParameters() { return parameters; } final Set<APITag> getAPITags() { return apiTags; } final String getFileParameter() { return fileParameter; } abstract JSONStreamAware processRequest(HttpServletRequest request) throws NxtException; JSONStreamAware processRequest(HttpServletRequest request, HttpServletResponse response) throws NxtException { return processRequest(request); } boolean requirePost() { return false; } boolean startDbTransaction() { return false; } boolean requirePassword() { return false; } boolean allowRequiredBlockParameters() { return true; } boolean requireBlockchain() { return true; } } private static final boolean enforcePost = Nxt.getBooleanProperty("nxt.apiServerEnforcePOST"); static final Map<String,APIRequestHandler> apiRequestHandlers; static { Map<String,APIRequestHandler> map = new HashMap<>(); map.put("approveTransaction", ApproveTransaction.instance); map.put("broadcastTransaction", BroadcastTransaction.instance); map.put("calculateFullHash", CalculateFullHash.instance); map.put("cancelAskOrder", CancelAskOrder.instance); map.put("cancelBidOrder", CancelBidOrder.instance); map.put("castVote", CastVote.instance); map.put("createPoll", CreatePoll.instance); map.put("currencyBuy", CurrencyBuy.instance); map.put("currencySell", CurrencySell.instance); map.put("currencyReserveIncrease", CurrencyReserveIncrease.instance); map.put("currencyReserveClaim", CurrencyReserveClaim.instance); map.put("currencyMint", CurrencyMint.instance); map.put("decryptFrom", DecryptFrom.instance); map.put("deleteAssetShares", DeleteAssetShares.instance); map.put("dgsListing", DGSListing.instance); map.put("dgsDelisting", DGSDelisting.instance); map.put("dgsDelivery", DGSDelivery.instance); map.put("dgsFeedback", DGSFeedback.instance); map.put("dgsPriceChange", DGSPriceChange.instance); map.put("dgsPurchase", DGSPurchase.instance); map.put("dgsQuantityChange", DGSQuantityChange.instance); map.put("dgsRefund", DGSRefund.instance); map.put("decodeHallmark", DecodeHallmark.instance); map.put("decodeToken", DecodeToken.instance); map.put("decodeFileToken", DecodeFileToken.instance); map.put("decodeQRCode", DecodeQRCode.instance); map.put("encodeQRCode", EncodeQRCode.instance); map.put("encryptTo", EncryptTo.instance); map.put("eventRegister", EventRegister.instance); map.put("eventWait", EventWait.instance); map.put("generateToken", GenerateToken.instance); map.put("generateFileToken", GenerateFileToken.instance); map.put("getAccount", GetAccount.instance); map.put("getAccountBlockCount", GetAccountBlockCount.instance); map.put("getAccountBlockIds", GetAccountBlockIds.instance); map.put("getAccountBlocks", GetAccountBlocks.instance); map.put("getAccountId", GetAccountId.instance); map.put("getAccountLedger", GetAccountLedger.instance); map.put("getAccountLedgerEntry", GetAccountLedgerEntry.instance); map.put("getVoterPhasedTransactions", GetVoterPhasedTransactions.instance); map.put("getLinkedPhasedTransactions", GetLinkedPhasedTransactions.instance); map.put("getPolls", GetPolls.instance); map.put("getAccountPhasedTransactions", GetAccountPhasedTransactions.instance); map.put("getAccountPhasedTransactionCount", GetAccountPhasedTransactionCount.instance); map.put("getAccountPublicKey", GetAccountPublicKey.instance); map.put("getAccountLessors", GetAccountLessors.instance); map.put("getAccountAssets", GetAccountAssets.instance); map.put("getAccountCurrencies", GetAccountCurrencies.instance); map.put("getAccountCurrencyCount", GetAccountCurrencyCount.instance); map.put("getAccountAssetCount", GetAccountAssetCount.instance); map.put("getAccountProperties", GetAccountProperties.instance); map.put("sellAlias", SellAlias.instance); map.put("buyAlias", BuyAlias.instance); map.put("getAlias", GetAlias.instance); map.put("getAliasCount", GetAliasCount.instance); map.put("getAliases", GetAliases.instance); map.put("getAliasesLike", GetAliasesLike.instance); map.put("getAllAssets", GetAllAssets.instance); map.put("getAllCurrencies", GetAllCurrencies.instance); map.put("getAsset", GetAsset.instance); map.put("getAssets", GetAssets.instance); map.put("getAssetIds", GetAssetIds.instance); map.put("getAssetsByIssuer", GetAssetsByIssuer.instance); map.put("getAssetAccounts", GetAssetAccounts.instance); map.put("getAssetAccountCount", GetAssetAccountCount.instance); map.put("getAssetPhasedTransactions", GetAssetPhasedTransactions.instance); map.put("getBalance", GetBalance.instance); map.put("getBlock", GetBlock.instance); map.put("getBlockId", GetBlockId.instance); map.put("getBlocks", GetBlocks.instance); map.put("getBlockchainStatus", GetBlockchainStatus.instance); map.put("getBlockchainTransactions", GetBlockchainTransactions.instance); map.put("getReferencingTransactions", GetReferencingTransactions.instance); map.put("getConstants", GetConstants.instance); map.put("getCurrency", GetCurrency.instance); map.put("getCurrencies", GetCurrencies.instance); map.put("getCurrencyFounders", GetCurrencyFounders.instance); map.put("getCurrencyIds", GetCurrencyIds.instance); map.put("getCurrenciesByIssuer", GetCurrenciesByIssuer.instance); map.put("getCurrencyAccounts", GetCurrencyAccounts.instance); map.put("getCurrencyAccountCount", GetCurrencyAccountCount.instance); map.put("getCurrencyPhasedTransactions", GetCurrencyPhasedTransactions.instance); map.put("getDGSGoods", GetDGSGoods.instance); map.put("getDGSGoodsCount", GetDGSGoodsCount.instance); map.put("getDGSGood", GetDGSGood.instance); map.put("getDGSGoodsPurchases", GetDGSGoodsPurchases.instance); map.put("getDGSGoodsPurchaseCount", GetDGSGoodsPurchaseCount.instance); map.put("getDGSPurchases", GetDGSPurchases.instance); map.put("getDGSPurchase", GetDGSPurchase.instance); map.put("getDGSPurchaseCount", GetDGSPurchaseCount.instance); map.put("getDGSPendingPurchases", GetDGSPendingPurchases.instance); map.put("getDGSExpiredPurchases", GetDGSExpiredPurchases.instance); map.put("getDGSTags", GetDGSTags.instance); map.put("getDGSTagCount", GetDGSTagCount.instance); map.put("getDGSTagsLike", GetDGSTagsLike.instance); map.put("getGuaranteedBalance", GetGuaranteedBalance.instance); map.put("getECBlock", GetECBlock.instance); map.put("getInboundPeers", GetInboundPeers.instance); map.put("getPlugins", GetPlugins.instance); map.put("getMyInfo", GetMyInfo.instance); //map.put("getNextBlockGenerators", GetNextBlockGenerators.instance); map.put("getPeer", GetPeer.instance); map.put("getPeers", GetPeers.instance); map.put("getPhasingPoll", GetPhasingPoll.instance); map.put("getPhasingPolls", GetPhasingPolls.instance); map.put("getPhasingPollVotes", GetPhasingPollVotes.instance); map.put("getPhasingPollVote", GetPhasingPollVote.instance); map.put("getPoll", GetPoll.instance); map.put("getPollResult", GetPollResult.instance); map.put("getPollVotes", GetPollVotes.instance); map.put("getPollVote", GetPollVote.instance); map.put("getState", GetState.instance); map.put("getTime", GetTime.instance); map.put("getTrades", GetTrades.instance); map.put("getLastTrades", GetLastTrades.instance); map.put("getExchanges", GetExchanges.instance); map.put("getExchangesByExchangeRequest", GetExchangesByExchangeRequest.instance); map.put("getExchangesByOffer", GetExchangesByOffer.instance); map.put("getLastExchanges", GetLastExchanges.instance); map.put("getAllTrades", GetAllTrades.instance); map.put("getAllExchanges", GetAllExchanges.instance); map.put("getAssetTransfers", GetAssetTransfers.instance); map.put("getAssetDeletes", GetAssetDeletes.instance); map.put("getExpectedAssetTransfers", GetExpectedAssetTransfers.instance); map.put("getExpectedAssetDeletes", GetExpectedAssetDeletes.instance); map.put("getCurrencyTransfers", GetCurrencyTransfers.instance); map.put("getExpectedCurrencyTransfers", GetExpectedCurrencyTransfers.instance); map.put("getTransaction", GetTransaction.instance); map.put("getTransactionBytes", GetTransactionBytes.instance); map.put("getUnconfirmedTransactionIds", GetUnconfirmedTransactionIds.instance); map.put("getUnconfirmedTransactions", GetUnconfirmedTransactions.instance); map.put("getExpectedTransactions", GetExpectedTransactions.instance); map.put("getAccountCurrentAskOrderIds", GetAccountCurrentAskOrderIds.instance); map.put("getAccountCurrentBidOrderIds", GetAccountCurrentBidOrderIds.instance); map.put("getAccountCurrentAskOrders", GetAccountCurrentAskOrders.instance); map.put("getAccountCurrentBidOrders", GetAccountCurrentBidOrders.instance); map.put("getAllOpenAskOrders", GetAllOpenAskOrders.instance); map.put("getAllOpenBidOrders", GetAllOpenBidOrders.instance); map.put("getBuyOffers", GetBuyOffers.instance); map.put("getExpectedBuyOffers", GetExpectedBuyOffers.instance); map.put("getSellOffers", GetSellOffers.instance); map.put("getExpectedSellOffers", GetExpectedSellOffers.instance); map.put("getOffer", GetOffer.instance); map.put("getAvailableToBuy", GetAvailableToBuy.instance); map.put("getAvailableToSell", GetAvailableToSell.instance); map.put("getAskOrder", GetAskOrder.instance); map.put("getAskOrderIds", GetAskOrderIds.instance); map.put("getAskOrders", GetAskOrders.instance); map.put("getBidOrder", GetBidOrder.instance); map.put("getBidOrderIds", GetBidOrderIds.instance); map.put("getBidOrders", GetBidOrders.instance); map.put("getExpectedAskOrders", GetExpectedAskOrders.instance); map.put("getExpectedBidOrders", GetExpectedBidOrders.instance); map.put("getExpectedOrderCancellations", GetExpectedOrderCancellations.instance); map.put("getOrderTrades", GetOrderTrades.instance); map.put("getAccountExchangeRequests", GetAccountExchangeRequests.instance); map.put("getExpectedExchangeRequests", GetExpectedExchangeRequests.instance); map.put("getMintingTarget", GetMintingTarget.instance); map.put("getAllShufflings", GetAllShufflings.instance); map.put("getAccountShufflings", GetAccountShufflings.instance); map.put("getAssignedShufflings", GetAssignedShufflings.instance); map.put("getHoldingShufflings", GetHoldingShufflings.instance); map.put("getShuffling", GetShuffling.instance); map.put("getShufflingParticipants", GetShufflingParticipants.instance); map.put("getPrunableMessage", GetPrunableMessage.instance); map.put("getPrunableMessages", GetPrunableMessages.instance); map.put("getAllPrunableMessages", GetAllPrunableMessages.instance); map.put("verifyPrunableMessage", VerifyPrunableMessage.instance); map.put("issueAsset", IssueAsset.instance); map.put("issueCurrency", IssueCurrency.instance); map.put("leaseBalance", LeaseBalance.instance); map.put("longConvert", LongConvert.instance); map.put("hexConvert", HexConvert.instance); map.put("markHost", MarkHost.instance); map.put("parseTransaction", ParseTransaction.instance); map.put("placeAskOrder", PlaceAskOrder.instance); map.put("placeBidOrder", PlaceBidOrder.instance); map.put("publishExchangeOffer", PublishExchangeOffer.instance); map.put("rsConvert", RSConvert.instance); map.put("readMessage", ReadMessage.instance); map.put("sendMessage", SendMessage.instance); map.put("sendMoney", SendMoney.instance); map.put("setAccountInfo", SetAccountInfo.instance); map.put("setAccountProperty", SetAccountProperty.instance); map.put("deleteAccountProperty", DeleteAccountProperty.instance); map.put("setAlias", SetAlias.instance); map.put("shufflingCreate", ShufflingCreate.instance); map.put("shufflingRegister", ShufflingRegister.instance); map.put("shufflingProcess", ShufflingProcess.instance); map.put("shufflingVerify", ShufflingVerify.instance); map.put("shufflingCancel", ShufflingCancel.instance); map.put("startShuffler", StartShuffler.instance); map.put("stopShuffler", StopShuffler.instance); map.put("getShufflers", GetShufflers.instance); map.put("deleteAlias", DeleteAlias.instance); map.put("signTransaction", SignTransaction.instance); map.put("startForging", StartForging.instance); map.put("stopForging", StopForging.instance); map.put("getForging", GetForging.instance); map.put("transferAsset", TransferAsset.instance); map.put("transferCurrency", TransferCurrency.instance); map.put("canDeleteCurrency", CanDeleteCurrency.instance); map.put("deleteCurrency", DeleteCurrency.instance); map.put("dividendPayment", DividendPayment.instance); map.put("searchDGSGoods", SearchDGSGoods.instance); map.put("searchAssets", SearchAssets.instance); map.put("searchCurrencies", SearchCurrencies.instance); map.put("searchPolls", SearchPolls.instance); map.put("searchAccounts", SearchAccounts.instance); map.put("searchTaggedData", SearchTaggedData.instance); map.put("uploadTaggedData", UploadTaggedData.instance); map.put("extendTaggedData", ExtendTaggedData.instance); map.put("getAccountTaggedData", GetAccountTaggedData.instance); map.put("getAllTaggedData", GetAllTaggedData.instance); map.put("getChannelTaggedData", GetChannelTaggedData.instance); map.put("getTaggedData", GetTaggedData.instance); map.put("downloadTaggedData", DownloadTaggedData.instance); map.put("getDataTags", GetDataTags.instance); map.put("getDataTagCount", GetDataTagCount.instance); map.put("getDataTagsLike", GetDataTagsLike.instance); map.put("verifyTaggedData", VerifyTaggedData.instance); map.put("getTaggedDataExtendTransactions", GetTaggedDataExtendTransactions.instance); map.put("clearUnconfirmedTransactions", ClearUnconfirmedTransactions.instance); map.put("requeueUnconfirmedTransactions", RequeueUnconfirmedTransactions.instance); map.put("rebroadcastUnconfirmedTransactions", RebroadcastUnconfirmedTransactions.instance); map.put("getAllWaitingTransactions", GetAllWaitingTransactions.instance); map.put("getAllBroadcastedTransactions", GetAllBroadcastedTransactions.instance); map.put("fullReset", FullReset.instance); map.put("popOff", PopOff.instance); map.put("scan", Scan.instance); map.put("luceneReindex", LuceneReindex.instance); map.put("addPeer", AddPeer.instance); map.put("blacklistPeer", BlacklistPeer.instance); map.put("dumpPeers", DumpPeers.instance); map.put("getLog", GetLog.instance); map.put("getStackTraces", GetStackTraces.instance); map.put("retrievePrunedData", RetrievePrunedData.instance); map.put("retrievePrunedTransaction", RetrievePrunedTransaction.instance); map.put("setLogging", SetLogging.instance); map.put("shutdown", Shutdown.instance); map.put("trimDerivedTables", TrimDerivedTables.instance); map.put("hash", Hash.instance); map.put("fullHashToId", FullHashToId.instance); map.put("setPhasingOnlyControl", SetPhasingOnlyControl.instance); map.put("getPhasingOnlyControl", GetPhasingOnlyControl.instance); map.put("getAllPhasingOnlyControls", GetAllPhasingOnlyControls.instance); map.put("detectMimeType", DetectMimeType.instance); apiRequestHandlers = Collections.unmodifiableMap(map); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } private void process(HttpServletRequest req, HttpServletResponse resp) throws IOException { // Set response values now in case we create an asynchronous context resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate, private"); resp.setHeader("Pragma", "no-cache"); resp.setDateHeader("Expires", 0); resp.setContentType("text/plain; charset=UTF-8"); JSONStreamAware response = JSON.emptyJSON; try { long startTime = System.currentTimeMillis(); if (! API.isAllowed(req.getRemoteHost())) { response = ERROR_NOT_ALLOWED; return; } String requestType = req.getParameter("requestType"); if (requestType == null) { response = ERROR_INCORRECT_REQUEST; return; } APIRequestHandler apiRequestHandler = apiRequestHandlers.get(requestType); if (apiRequestHandler == null) { response = ERROR_INCORRECT_REQUEST; return; } if (enforcePost && apiRequestHandler.requirePost() && ! "POST".equals(req.getMethod())) { response = POST_REQUIRED; return; } try { if (apiRequestHandler.requirePassword()) { API.verifyPassword(req); } final long requireBlockId = apiRequestHandler.allowRequiredBlockParameters() ? ParameterParser.getUnsignedLong(req, "requireBlock", false) : 0; final long requireLastBlockId = apiRequestHandler.allowRequiredBlockParameters() ? ParameterParser.getUnsignedLong(req, "requireLastBlock", false) : 0; if (requireBlockId != 0 || requireLastBlockId != 0) { Nxt.getBlockchain().readLock(); } try { try { if (apiRequestHandler.startDbTransaction()) { Db.db.beginTransaction(); } if (requireBlockId != 0 && !Nxt.getBlockchain().hasBlock(requireBlockId)) { response = REQUIRED_BLOCK_NOT_FOUND; return; } if (requireLastBlockId != 0 && requireLastBlockId != Nxt.getBlockchain().getLastBlock().getId()) { response = REQUIRED_LAST_BLOCK_NOT_FOUND; return; } response = apiRequestHandler.processRequest(req, resp); if (requireLastBlockId == 0 && requireBlockId != 0 && response instanceof JSONObject) { ((JSONObject) response).put("lastBlock", Nxt.getBlockchain().getLastBlock().getStringId()); } } finally { if (apiRequestHandler.startDbTransaction()) { Db.db.endTransaction(); } } } finally { if (requireBlockId != 0 || requireLastBlockId != 0) { Nxt.getBlockchain().readUnlock(); } } } catch (ParameterException e) { response = e.getErrorResponse(); } catch (NxtException |RuntimeException e) { Logger.logDebugMessage("Error processing API request", e); JSONObject json = new JSONObject(); JSONData.putException(json, e); response = JSON.prepare(json); } catch (ExceptionInInitializerError err) { Logger.logErrorMessage("Initialization Error", err.getCause()); response = ERROR_INCORRECT_REQUEST; } if (response != null && (response instanceof JSONObject)) { ((JSONObject)response).put("requestProcessingTime", System.currentTimeMillis() - startTime); } } catch (Exception e) { Logger.logErrorMessage("Error processing request", e); response = ERROR_INCORRECT_REQUEST; } finally { // The response will be null if we created an asynchronous context if (response != null) { try (Writer writer = resp.getWriter()) { JSON.writeJSONString(response, writer); } } } } }