Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Filepath.getSanitizedFilename, deprecated getFilename #12183

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 30 additions & 1 deletion core/play/src/main/java/play/mvc/Http.java
Expand Up @@ -15,6 +15,9 @@
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.*;
Expand Down Expand Up @@ -1499,7 +1502,33 @@ public String getKey() {
return key;
}

/** @return the file name */
/**
* @return the sanitized version of the file name (i.e. only the filename, no path components)
*/
public String getSanitizedFilename() {
try {
// Will throw InvalidPathException on invalid filepaths
Path name = Paths.get(filename).normalize().getFileName();

// This can occur if the filepath is empty when fully resolved
if (name == null || name.toString().isEmpty() || name.toString().equals("..")) {
throw new InvalidPathException(filename, "");
}
return name.toString();

} catch (InvalidPathException e) {
throw new RuntimeException(
"Unable to sanitize the filename given to MultipartFormData.FilePart: \""
+ e.getInput()
+ "\"");
}
}

/**
* @deprecated Use {@link #getSanitizedFilename()} instead.
* @return the raw file name
*/
@Deprecated
public String getFilename() {
return filename;
}
Expand Down
87 changes: 87 additions & 0 deletions core/play/src/test/java/play/mvc/SanitizedFilenameTest.java
@@ -0,0 +1,87 @@
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/

package play.mvc;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import play.mvc.Http.MultipartFormData;

public class SanitizedFilenameTest {
@Test
public void sanitizeSingleComponent() {
MultipartFormData.FilePart<Object> p =
new MultipartFormData.FilePart<Object>(null, "abc", null, null);
assertEquals("abc", p.getSanitizedFilename());
}

@Test
public void sanitizeMultipleComponents() {
MultipartFormData.FilePart<Object> p =
new MultipartFormData.FilePart<Object>(null, "abc/def/xyz", null, null);
assertEquals("xyz", p.getSanitizedFilename());
}

@Test
public void sanitizeWithTrailingDots() {
MultipartFormData.FilePart<Object> p =
new MultipartFormData.FilePart<Object>(null, "a/b/c/././", null, null);
assertEquals("c", p.getSanitizedFilename());
}

@Test
public void sanitizeWithLeadingDoubleDots() {
MultipartFormData.FilePart<Object> p =
new MultipartFormData.FilePart<Object>(null, "../../../a", null, null);
assertEquals("a", p.getSanitizedFilename());
}

@Test
public void sanitizeWithNameAfterDoubleDots() {
MultipartFormData.FilePart<Object> p =
new MultipartFormData.FilePart<Object>(null, "../../../a/../b", null, null);
assertEquals("b", p.getSanitizedFilename());
}

@Test
public void sanitizeWithTrailingDoubleDots() {
MultipartFormData.FilePart<Object> p =
new MultipartFormData.FilePart<Object>(null, "a/b/c/../..", null, null);
assertEquals("a", p.getSanitizedFilename());
}

@Test
public void sanitizeWithRedundantSlashesAndDots() {
MultipartFormData.FilePart<Object> p =
new MultipartFormData.FilePart<Object>(null, "///a//b/c/.././d/././/", null, null);
assertEquals("d", p.getSanitizedFilename());
}

@Test(expected = RuntimeException.class)
public void sanitizeThrowsOnEmptyPath() {
(new MultipartFormData.FilePart<Object>(null, "", null, null)).getSanitizedFilename();
}

@Test(expected = RuntimeException.class)
public void sanitizeThrowsOnCurrentDirectory() {
(new MultipartFormData.FilePart<Object>(null, ".", null, null)).getSanitizedFilename();
}

@Test(expected = RuntimeException.class)
public void sanitizeThrowsOnDoubleDots() {
(new MultipartFormData.FilePart<Object>(null, "..", null, null)).getSanitizedFilename();
}

@Test(expected = RuntimeException.class)
public void sanitizeThrowsPastRoot() {
(new MultipartFormData.FilePart<Object>(null, "a/b/../../..", null, null))
.getSanitizedFilename();
}

@Test(expected = RuntimeException.class)
public void sanitizeThrowsOnParentAfterResolving() {
(new MultipartFormData.FilePart<Object>(null, "../a/..", null, null)).getSanitizedFilename();
}
}