diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 3252d06c..fb32c44c 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -1,5 +1,6 @@ package com.replaymod.core.utils; +import com.google.common.net.PercentEscaper; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -16,11 +17,14 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiInfoPopup; import de.johni0702.minecraft.gui.utils.Colors; +import lombok.SneakyThrows; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.client.resources.DefaultPlayerSkin; import net.minecraft.crash.CrashReport; import net.minecraft.util.ResourceLocation; +import org.apache.commons.io.Charsets; +import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.Logger; import org.lwjgl.input.Keyboard; import org.lwjgl.util.Dimension; @@ -36,6 +40,8 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -137,6 +143,22 @@ public class Utils { return mail.matches("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$"); } + private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false); + + public static String replayNameToFileName(String replayName) { + return REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr"; + } + + @SneakyThrows(UnsupportedEncodingException.class) + public static String fileNameToReplayName(String fileName) { + String baseName = FilenameUtils.getBaseName(fileName); + try { + return URLDecoder.decode(baseName, Charsets.UTF_8.name()); + } catch (IllegalArgumentException e) { + return baseName; + } + } + public static ResourceLocation getResourceLocationForPlayerUUID(UUID uuid) { NetworkPlayerInfo info = getMinecraft().getNetHandler().getPlayerInfo(uuid); ResourceLocation skinLocation; diff --git a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java index 65ea1ed0..05322765 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java +++ b/src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java @@ -36,7 +36,6 @@ import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import net.minecraft.crash.CrashReport; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; import org.lwjgl.util.ReadableDimension; import javax.annotation.Nullable; @@ -125,7 +124,7 @@ public class GuiReplayEditor extends GuiScreen { } public void save(File inputFile, PacketStream.FilterInfo...filters) { - save(FilenameUtils.getBaseName(inputFile.getName()), (outputFile) -> { + save(Utils.fileNameToReplayName(inputFile.getName()), (outputFile) -> { Studio studio = new ReplayStudio(); File tmpDir = null; try { @@ -220,7 +219,7 @@ public class GuiReplayEditor extends GuiScreen { Utils.error(LOGGER, GuiReplayEditor.this, CrashReport.makeCrashReport(e, "Getting replay folder"), null); return; } - File targetFile = new File(replayFolder, name + ".mcpr"); + File targetFile = new File(replayFolder, Utils.replayNameToFileName(name)); if (targetFile.exists()) { LOGGER.trace("Selected file already exists, asking for confirmation"); Futures.addCallback(GuiYesNoPopup.open(GuiReplayEditor.this, diff --git a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java index fda001f1..3bff46f8 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java +++ b/src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java @@ -20,7 +20,6 @@ import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; import net.minecraft.client.resources.I18n; import net.minecraft.crash.CrashReport; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.lwjgl.util.Dimension; @@ -52,7 +51,7 @@ public class GuiTrimPanel extends GuiPanel { public final GuiDropdownMenu inputReplays = new GuiDropdownMenu(this) .setMinSize(new Dimension(200, 20)).onSelection(i -> updateSelectedReplay()) - .setToString(f -> f == NO_REPLAY ? "" : FilenameUtils.getBaseName(f.getName())); + .setToString(f -> f == NO_REPLAY ? "" : Utils.fileNameToReplayName(f.getName())); public final GuiNumberField startHour = newGuiNumberField(); public final GuiNumberField startMin = newGuiNumberField(); diff --git a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java index ec71bd05..252eaf41 100644 --- a/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java +++ b/src/main/java/com/replaymod/online/gui/GuiReplayCenter.java @@ -37,7 +37,6 @@ import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.popup.GuiYesNoPopup; import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Consumer; -import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.core.helpers.Strings; import org.lwjgl.util.Dimension; import org.lwjgl.util.ReadableDimension; @@ -418,7 +417,7 @@ public class GuiReplayCenter extends GuiScreen { this.downloaded = downloaded; ReplayMetaData metaData = fileInfo.getMetadata(); - name.setText(ChatFormatting.UNDERLINE + FilenameUtils.getBaseName(fileInfo.getName())); + name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(fileInfo.getName())); author.setI18nText("replaymod.gui.center.author", "" + ChatFormatting.GRAY + ChatFormatting.ITALIC, fileInfo.getOwner()); if (Strings.isEmpty(metaData.getServerName())) { diff --git a/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java b/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java index 5e7cedfb..27596349 100644 --- a/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java +++ b/src/main/java/com/replaymod/online/gui/GuiSaveModifiedReplay.java @@ -2,6 +2,7 @@ package com.replaymod.online.gui; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.replaymod.core.utils.Utils; import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiScreen; import de.johni0702.minecraft.gui.element.GuiButton; @@ -27,7 +28,7 @@ public class GuiSaveModifiedReplay extends GuiScreen { @Override public void run() { String resultName = name.getText().trim().replace("[^a-zA-Z0-9\\.\\- ]", "_"); - final File resultFile = new File(file.getParentFile(), resultName + ".mcpr"); + final File resultFile = new File(file.getParentFile(), Utils.replayNameToFileName(resultName)); if (resultFile.exists()) { Futures.addCallback(GuiYesNoPopup.open(GuiSaveModifiedReplay.this, new GuiLabel().setI18nText("replaymod.gui.replaymodified.warning1", resultName).setColor(Colors.BLACK), diff --git a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java index 4421df29..3ceb46d6 100644 --- a/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java +++ b/src/main/java/com/replaymod/online/gui/GuiUploadReplay.java @@ -38,7 +38,6 @@ import net.minecraft.client.settings.KeyBinding; import net.minecraft.crash.CrashReport; import net.minecraft.util.ReportedException; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nullable; @@ -135,7 +134,7 @@ public class GuiUploadReplay extends GuiScreen { } // Apply to gui - name.setText(FilenameUtils.getBaseName(file.getName())); + name.setText(Utils.fileNameToReplayName(file.getName())); int secs = metaData.getDuration() / 1000; durationLabel.setI18nText("replaymod.gui.upload.duration", secs / 60, secs % 60); hideServerIP.setEnabled(!metaData.isSingleplayer()); diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index e63d1d13..ae01e5b7 100755 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -2,6 +2,7 @@ package com.replaymod.recording.handler; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.ModCompat; +import com.replaymod.core.utils.Utils; import com.replaymod.recording.Setting; import com.replaymod.recording.gui.GuiRecordingOverlay; import com.replaymod.recording.packet.PacketListener; @@ -74,7 +75,7 @@ public class ConnectionEventHandler { File folder = core.getReplayFolder(); String name = sdf.format(Calendar.getInstance().getTime()); - File currentFile = new File(folder, name + ".mcpr"); + File currentFile = new File(folder, Utils.replayNameToFileName(name)); ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), currentFile); replayFile.writeModInfo(ModCompat.getInstalledNetworkMods()); diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 3de20bd7..e9bd1d4e 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -29,7 +29,6 @@ import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.resources.I18n; import net.minecraft.util.Util; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.apache.logging.log4j.LogManager; @@ -156,7 +155,7 @@ public class GuiReplayViewer extends GuiScreen { @Override public void run() { final File file = list.getSelected().file; - String name = FilenameUtils.getBaseName(file.getName()); + String name = Utils.fileNameToReplayName(file.getName()); final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name); final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this, new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK), @@ -172,16 +171,16 @@ public class GuiReplayViewer extends GuiScreen { } }).onTextChanged(obj -> { popup.getYesButton().setEnabled(!nameField.getText().isEmpty() - && !new File(file.getParentFile(), nameField.getText() + ".mcpr").exists()); + && !new File(file.getParentFile(), Utils.replayNameToFileName(nameField.getText())).exists()); }); Futures.addCallback(popup.getFuture(), new FutureCallback() { @Override public void onSuccess(Boolean delete) { if (delete) { // Sanitize their input - String name = nameField.getText().trim().replace("[^a-zA-Z0-9\\.\\- ]", "_"); + String name = nameField.getText().trim(); // This file is what they want - File targetFile = new File(file.getParentFile(), name + ".mcpr"); + File targetFile = new File(file.getParentFile(), Utils.replayNameToFileName(name)); try { // Finally, try to move it FileUtils.moveFile(file, targetFile); @@ -303,7 +302,7 @@ public class GuiReplayViewer extends GuiScreen { public GuiReplayEntry(File file, ReplayMetaData metaData, BufferedImage thumbImage) { this.file = file; - name.setText(ChatFormatting.UNDERLINE + FilenameUtils.getBaseName(file.getName())); + name.setText(ChatFormatting.UNDERLINE + Utils.fileNameToReplayName(file.getName())); if (Strings.isEmpty(metaData.getServerName())) { server.setI18nText("replaymod.gui.iphidden").setColor(Colors.DARK_RED); } else {