Class CommandExecutionManager
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classOutput lines inside the schema caps, plus whether the caps forced a cut.(package private) static final classThe feedback of one v2 panel command, handed to Paper's capturing sender.static classDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the protocol 2.0.0 command path reports through CommandResultSink.static interfaceReceives one v2 command outcome, independently of the transport framing. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final StringTheRemoteActionLog.Entry.getAction()literal for every entry this class records.Blocklist of dangerous commands that should not be executed remotely.private static final StringThe literal config pathloadConfiguration()reads and every configurable refusal this class produces names —ultipanel.commands.blocklist.(package private) static final StringThecommand_resulttext for a command the server console accepted.private final com.google.gson.Gson(package private) static final longHow long after a v2 command's result was taken its late output is still written to the server console — seeCommandExecutionManager.CapturedFeedback.private static final longcommand.result.data.executionMsmaximum in the frozen contract.private static final intcommand.result.data.outputitems maxLength, counted in Unicode code points as the schema andPayloadReadercount it.private static final intcommand.result.data.outputmaxItems in the frozen 2.0.0 contract.private static final intcommand.resultpayload message maxLength, counted in Unicode code points.(package private) static final StringThe v1command_resulttext for a command the server console did not accept (Bukkit.dispatchCommandreturnedfalse: an unknown command, or a command whose executor reported a usage error).(package private) static final StringThe v2command_resulttext for a command the server did not accept while printing nothing the capturing sender could read — an unknown command whose refusal goes elsewhere, or an executor that reports a usage error silently.private static final ThreadLocal<Boolean> Panel origin exists only inside synchronous main-thread dispatch, including nested commands.private UltiPanelWebSocketClient -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static StringbaseCommandName(String commandLine) Extracts a lower-case, namespace-free name from an already canonical command line.private static CommandExecutionManager.BoundedLinesboundLines(List<String> lines) private AccessDecisioncanonicalCommandDecision(String command) Checks text already normalized at the request boundary; dispatch receives that same text.private static StringNormalizes a panel request once, before both policy and dispatch.private static StringDescribes why a raw blocklist value is not a list of strings, naming only its shape.private static StringdescribeShape(Object value) Names a raw configuration value's type for a diagnostic, without its content.private static booleandispatchPanelCommand(org.bukkit.command.CommandSender sender, String command) Restores the caller's origin even when a nested dispatch throws.voidexecute(String commandLine, String actor, Executor mainThread, CommandExecutionManager.CommandResultSink sink) Executes a v2 request using Paper's console-permission feedback sender.private voidexecuteCaptured(String commandLine, CommandExecutionManager.CommandResultSink sink) voidexecuteCommand(com.google.gson.JsonObject commandData) Deprecated, for removal: This API element is subject to removal in a future version.Use execute(String, String, Executor, CommandResultSink), which the protocol 2.0.0 session calls for a panel command; this v1 handler replies only over the client handed to setWebSocketClient.private voidexecuteCommandInternal(String command, String executor, String commandId, long startTime) Internal command execution logic.isCommandAllowed(String command) Whether a command is allowed for remote execution.static booleanWhether a synchronous command is running inside a panel dispatch on this thread.final voidLoadsultipanel.commands.blocklistfrom config.yml, copyingErrorReportCollector.loadConfiguration()'s exact shape: a null-guard onUltiTools.getInstance(), one try/catch whose failure branch keeps the current blocklist and logs a warning naming the key.private voidrecordCommandDecision(String command, String actor, String refusal) private static StringrejectionMessage(List<String> lines) Turns the feedback a refused command printed into the failure message.private static StringresolveActor(String executor) The action-logactorfield — the inboundexecutorfield verbatim, or the literal"panel"when absent.private static StringresultMessage(String message) Fits a result message inside the schema bound and never returns a blank one.private voidsendCommandResult(String commandId, boolean success, String output, long executionTime) Sends the command execution result.voidsetBlockedCommands(Set<String> commands) Set the blocklist of commands that should not be executed remotely.voidDeprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework never calls it, and its own UltiCloud session (protocol 2.0.0) carries the panel requests.(package private) static voidwriteToConsole(String line) Writes one late output line of a panel command to the server console: the server's own logger, atINFO, with the text unchanged.
-
Field Details
-
PANEL_DISPATCH
Panel origin exists only inside synchronous main-thread dispatch, including nested commands. -
webSocketClient
-
pendingCommands
private final ConcurrentHashMap<String,CompletableFuture<CommandExecutionManager.CommandResult>> pendingCommands -
gson
private final com.google.gson.Gson gson -
BLOCKLIST_CONFIG_KEY
The literal config pathloadConfiguration()reads and every configurable refusal this class produces names —ultipanel.commands.blocklist.- See Also:
-
ACTION_EXECUTE_COMMAND
TheRemoteActionLog.Entry.getAction()literal for every entry this class records.- See Also:
-
MAX_OUTPUT_LINES
private static final int MAX_OUTPUT_LINEScommand.result.data.outputmaxItems in the frozen 2.0.0 contract.- See Also:
-
MAX_OUTPUT_LINE_CHARS
private static final int MAX_OUTPUT_LINE_CHARScommand.result.data.outputitems maxLength, counted in Unicode code points as the schema andPayloadReadercount it.- See Also:
-
MAX_RESULT_MESSAGE_CHARS
private static final int MAX_RESULT_MESSAGE_CHARScommand.resultpayload message maxLength, counted in Unicode code points.- See Also:
-
MAX_EXECUTION_MS
private static final long MAX_EXECUTION_MScommand.result.data.executionMsmaximum in the frozen contract.- See Also:
-
LATE_OUTPUT_WINDOW_NANOS
static final long LATE_OUTPUT_WINDOW_NANOSHow long after a v2 command's result was taken its late output is still written to the server console — seeCommandExecutionManager.CapturedFeedback. Long enough for asynchronous command bodies that wait on a database or the network; it bounds how long the framework answers for a capturing sender that a command or another plugin kept. -
DISPATCHED_OUTPUT
Thecommand_resulttext for a command the server console accepted. A panel command is typed into the server console (maintainer decision, 6.3.0): it runs as the console sender, and Paper sends a console command's replies to the console itself, never to a sender the framework could read, so the result cannot carry them. They reach the panel through the log stream, which mirrors the console (ConsoleMirror).- See Also:
-
NOT_ACCEPTED_OUTPUT
The v1command_resulttext for a command the server console did not accept (Bukkit.dispatchCommandreturnedfalse: an unknown command, or a command whose executor reported a usage error). The v1 path still dispatches as the real console, so Paper writes the reply to the console and this sentence stays true there. The v2 path captures that reply instead and sends it — seeexecuteCaptured(java.lang.String, com.ultikits.ultitools.manager.CommandExecutionManager.CommandResultSink)andNOT_ACCEPTED_WITHOUT_REPLY.- See Also:
-
NOT_ACCEPTED_WITHOUT_REPLY
The v2command_resulttext for a command the server did not accept while printing nothing the capturing sender could read — an unknown command whose refusal goes elsewhere, or an executor that reports a usage error silently. When the server did print something,rejectionMessage(java.util.List<java.lang.String>)sends those lines instead of this sentence.- See Also:
-
blockedCommands
Blocklist of dangerous commands that should not be executed remotely. This is the shipped default, kept whenultipanel.commands.blocklistis absent from config.yml or when its present value is rejected as not a list of strings — seeloadConfiguration().
-
-
Constructor Details
-
CommandExecutionManager
public CommandExecutionManager()
-
-
Method Details
-
loadConfiguration
public final void loadConfiguration()Loadsultipanel.commands.blocklistfrom config.yml, copyingErrorReportCollector.loadConfiguration()'s exact shape: a null-guard onUltiTools.getInstance(), one try/catch whose failure branch keeps the current blocklist and logs a warning naming the key.Distinguishes an absent key from an explicit empty list via
isSet(path)— Bukkit'sgetStringListreturns an empty list for both, and collapsing that distinction would make D-03's honest "block nothing" escape value silently impossible (T-06-12). The shipped ten-command default applies only when the key is absent; an explicit empty list is honored as the operator deliberately granting every command.A present value is validated as a whole before anything is installed: it must be a list whose every element is a string. The raw value is read, not Bukkit's converting
getStringList, because that getter turns a non-list into an empty list, drops mapping, nested-list and null elements, and stringifies numbers and booleans — so its output cannot show what the operator wrote, and installing it would silently replace the policy with an empty or partial one. Any other shape (a scalar, a map, or a list with any non-string element) rejects the whole value: one warning naming the key and the rejected shape (the value's type, or the first offending element's position and type) is logged, never the value itself, and the current in-memory blocklist is kept — the shipped default on a newly constructed manager, the previous valid list on a repeated call. A valid list is trimmed and lower-cased withLocale.ROOTinto one new set, installed through a singlesetBlockedCommands(Set)call.This method never writes config.yml or the loaded configuration: it neither saves nor repairs an invalid value, so the operator's file and the in-memory section keep exactly what the operator wrote.
-
describeInvalidBlocklist
Describes why a raw blocklist value is not a list of strings, naming only its shape.- Parameters:
raw- the raw value atultipanel.commands.blocklist- Returns:
nullfor a list whose every element is a string; otherwise the value's type, or the first non-string element's 1-based position and type — never the value itself
-
describeShape
Names a raw configuration value's type for a diagnostic, without its content. -
setBlockedCommands
Set the blocklist of commands that should not be executed remotely. This is nowloadConfiguration()'s config-load target, not just a test-only seam — it has zero production call sites of its own, but every operator-configured blocklist reaches this class through it.- Parameters:
commands- Set of command names to block (case-insensitive)
-
isCommandAllowed
Whether a command is allowed for remote execution.Extracts the base command (the first word) and strips any namespace prefix (e.g.
bukkit:op->op,minecraft:stop->stop) BEFORE checking it against the operator-configured blocklist atultipanel.commands.blocklistinplugins/UltiTools/config.yml— so both the bare command and its namespaced form are blocked; the stripping order is what makes that true. The blocklist is editable in both directions — an operator may remove any of the ten shipped defaults, add to them, or clear the list entirely. Separately, direct forms of UltiCloud's ownloginandlogoutsubcommands are refused withcommand_blocked, regardless of the configured blocklist. Command names come fromCloudLoginCommand'sCmdExecutoraliases, the same metadata used for registration. Indirect synchronous forms (an alias or a command that dispatches another command) are refused by the authentication handler whileisPanelDispatch()is true; that refusal is normal command output, not acommand_blockedresult. The fixed rule does not refuse the root command,status,reconnectorhelp.- Parameters:
command- The command to check (with or without leading slash)- Returns:
- an
AccessDecisionnaming why a refused command was refused
-
canonicalCommandDecision
Checks text already normalized at the request boundary; dispatch receives that same text. -
setWebSocketClient
@Deprecated(since="6.3.1", forRemoval=true) public void setWebSocketClient(UltiPanelWebSocketClient client) Deprecated, for removal: This API element is subject to removal in a future version.There is no replacement: the framework never calls it, and its own UltiCloud session (protocol 2.0.0) carries the panel requests. Removed in 6.4.0.Sets the WebSocket client.- Parameters:
client- the WebSocket client- Scheduled removal version:
- 6.4.0
-
execute
public void execute(String commandLine, String actor, Executor mainThread, CommandExecutionManager.CommandResultSink sink) Executes a v2 request using Paper's console-permission feedback sender. The caller owns acknowledgement, deduplication, lifecycle checks and transport byte limits. The 6.3.0 validation order runs on the calling thread, so a refusal is answered without waiting for the main thread; only the dispatch and its feedback capture are handed to the injected executor, which must enqueue work onto the server main thread in arrival order. Output is cut to thecommand.resultschema bounds — 1000 lines of 4096 code points — and the flag reports that cut. Only feedback printed before the dispatch returns is in the result. A line the command prints later, for example from an asynchronous body, is written to the server console instead, as 6.3.0 printed every panel command's output there; the log stream carries it from there, redacted like every other console line (seeCommandExecutionManager.CapturedFeedback). This method only reads operator configuration and never rewrites it.- Parameters:
commandLine- command text, optionally prefixed with a slashactor- audit actor, or null for the panelmainThread- ordered server-main-thread executorsink- receives the command outcome- Since:
- 6.3.1
-
canonicalCommandLine
Normalizes a panel request once, before both policy and dispatch. Retains the framework's optional leading-slash removal and Paper's trim semantics. Paper 1.21.11 usesStringUtils.normalizeSpace:Character.isWhitespace(char)plus U+00A0, not allisSpaceCharcharacters. Collapse their combined runs to make the result stable when Paper normalizes it again (adjacent NBSPs otherwise become adjacent spaces). -
baseCommandName
Extracts a lower-case, namespace-free name from an already canonical command line. -
isPanelDispatch
public static boolean isPanelDispatch()Whether a synchronous command is running inside a panel dispatch on this thread. Authentication handlers use this at execution, so nested aliases cannot bypass the direct text refusal. Their refusal reaches the panel as normal command output.- Returns:
- true only during a panel dispatch, including synchronous nested dispatches
-
dispatchPanelCommand
private static boolean dispatchPanelCommand(org.bukkit.command.CommandSender sender, String command) Restores the caller's origin even when a nested dispatch throws. Both panel paths use this. -
resultMessage
Fits a result message inside the schema bound and never returns a blank one. -
recordCommandDecision
-
executeCaptured
-
writeToConsole
Writes one late output line of a panel command to the server console: the server's own logger, atINFO, with the text unchanged. The local console and log files keep the text as printed, like every console line; the log stream picks the record up from thejava.util.loggingroot logger and redacts registered sensitive commands (SensitiveCommandRegistry) on its way to UltiCloud, exactly as for any other console line. -
rejectionMessage
Turns the feedback a refused command printed into the failure message.With the capturing sender the server's own reply — Paper's unknown-command line, a Bukkit command's usage text — arrives here instead of reaching the console, so it is the only reply the panel can get and it must not be dropped. The lines are bounded exactly as successful output is (4096 code points per line, at most 1000 lines) and then cut to the message bound, which is the schema slot they travel in.
- Parameters:
lines- feedback captured beforedispatchCommandreturned- Returns:
- the joined reply, or
NOT_ACCEPTED_WITHOUT_REPLYwhen nothing was printed
-
boundLines
-
executeCommand
@Deprecated(since="6.3.1", forRemoval=true) public void executeCommand(com.google.gson.JsonObject commandData) Deprecated, for removal: This API element is subject to removal in a future version.Use execute(String, String, Executor, CommandResultSink), which the protocol 2.0.0 session calls for a panel command; this v1 handler replies only over the client handed to setWebSocketClient. Removed in 6.4.0.Executes a command received from the panel.- Scheduled removal version:
- 6.4.0
-
executeCommandInternal
private void executeCommandInternal(String command, String executor, String commandId, long startTime) Internal command execution logic. -
resolveActor
The action-logactorfield — the inboundexecutorfield verbatim, or the literal"panel"when absent. MirrorsPluginInitiationUtils.resolveActor(JsonObject)exactly: the framework cannot attribute a remote command to an individual panel operator today (seeRemoteActionLog.Entry's javadoc), so this never invents a per-operator identity. -
sendCommandResult
private void sendCommandResult(String commandId, boolean success, String output, long executionTime) Sends the command execution result.
-