Skip to content
This repository was archived by the owner on Sep 27, 2021. It is now read-only.

Commit 2523bdb

Browse files
authored
Allowed special characters on path or filename (#39)
1 parent 3301184 commit 2523bdb

File tree

7 files changed

+49
-44
lines changed

7 files changed

+49
-44
lines changed

build.sbt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ scalafmt: {
2626

2727
// Dependency versions
2828
val akkaVersion = "2.6.0"
29-
val akkaHttpVersion = "10.1.10"
29+
val akkaHttpVersion = "10.1.11"
3030
val apacheCompressVersion = "1.19"
3131
val alpakkaVersion = "1.1.2"
32-
val catsVersion = "2.0.0"
32+
val catsVersion = "2.1.0"
3333
val catsEffectVersion = "2.0.0"
3434
val circeVersion = "0.12.3"
3535
val commonsVersion = "0.20.0"
3636
val iamVersion = "1.2.0+16-382dc073"
37-
val mockitoVersion = "1.7.1"
37+
val mockitoVersion = "1.10.1"
3838
val monixVersion = "3.1.0"
3939
val pureconfigVersion = "0.12.1"
4040
val scalaTestVersion = "3.1.0"

src/main/scala/ch/epfl/bluebrain/nexus/storage/Storages.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ch.epfl.bluebrain.nexus.storage
22

3+
import java.net.URLDecoder
34
import java.nio.file.StandardCopyOption._
45
import java.nio.file.{Files, Path, Paths}
56
import java.security.MessageDigest
@@ -121,13 +122,16 @@ object Storages {
121122
F: Effect[F]
122123
) extends Storages[F, AkkaSource] {
123124

125+
private def decode(path: Uri.Path): String =
126+
Try(URLDecoder.decode(path.toString, "UTF-8")).getOrElse(path.toString())
127+
124128
private def basePath(name: String, protectedDir: Boolean = true): Path = {
125129
val path = config.rootVolume.resolve(name).normalize()
126130
if (protectedDir) path.resolve(config.protectedDirectory).normalize() else path
127131
}
128132

129133
private def filePath(name: String, relativePath: Uri.Path, protectedDir: Boolean = true): Path =
130-
basePath(name, protectedDir).resolve(Paths.get(relativePath.toString())).normalize()
134+
basePath(name, protectedDir).resolve(Paths.get(decode(relativePath))).normalize()
131135

132136
def exists(name: String): BucketExistence = {
133137
val path = basePath(name)
@@ -156,7 +160,7 @@ object Storages {
156160
case (digFuture, ioFuture) =>
157161
digFuture.zipWith(ioFuture) {
158162
case (digest, io) if absFilePath.toFile.exists() =>
159-
Future(FileAttributes(s"file://$absFilePath", io.count, digest, detectMediaType(absFilePath)))
163+
Future(FileAttributes(absFilePath.toAkkaUri, io.count, digest, detectMediaType(absFilePath)))
160164
case _ =>
161165
Future.failed(InternalError(s"I/O error writing file to path '$relativeFilePath'"))
162166
}
@@ -196,7 +200,7 @@ object Storages {
196200
F.fromTry(Try(Files.createDirectories(absDestPath.getParent))) >>
197201
F.fromTry(Try(Files.move(absSourcePath, absDestPath, ATOMIC_MOVE))) >>
198202
F.pure(cache.asyncComputePut(absDestPath, digestConfig.algorithm)) >>
199-
F.pure(Right(FileAttributes(s"file://$absDestPath", computedSize, Digest.empty, mediaType)))
203+
F.pure(Right(FileAttributes(absDestPath.toAkkaUri, computedSize, Digest.empty, mediaType)))
200204
}
201205
}
202206

src/main/scala/ch/epfl/bluebrain/nexus/storage/attributes/AttributesCacheActor.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import java.time.Clock
55

66
import akka.NotUsed
77
import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
8+
import akka.http.scaladsl.model.MediaTypes.`application/octet-stream`
89
import akka.stream.javadsl.Sink
910
import akka.stream.scaladsl.{Flow, Keep, Source}
1011
import akka.stream.{OverflowStrategy, QueueOfferResult}
1112
import cats.effect.Effect
1213
import cats.effect.implicits._
1314
import ch.epfl.bluebrain.nexus.storage.File.{Digest, FileAttributes}
15+
import ch.epfl.bluebrain.nexus.storage._
1416
import ch.epfl.bluebrain.nexus.storage.attributes.AttributesCacheActor.Protocol._
1517
import ch.epfl.bluebrain.nexus.storage.config.AppConfig.DigestConfig
16-
import akka.http.scaladsl.model.MediaTypes.`application/octet-stream`
1718

1819
import scala.collection.mutable
1920
import scala.concurrent.duration._
@@ -114,7 +115,7 @@ class AttributesCacheActor[F[_]: Effect, S](computation: AttributesComputation[F
114115
}
115116

116117
private def emptyAttributes(path: Path) =
117-
FileAttributes(s"file://$path", 0L, Digest.empty, `application/octet-stream`)
118+
FileAttributes(path.toAkkaUri, 0L, Digest.empty, `application/octet-stream`)
118119

119120
private def removeOldest(n: Int) =
120121
map --= map.take(n).keySet

src/main/scala/ch/epfl/bluebrain/nexus/storage/attributes/AttributesComputation.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import java.nio.file.{Files, Path}
44
import java.security.MessageDigest
55

66
import akka.http.scaladsl.model.HttpCharsets.`UTF-8`
7-
import akka.http.scaladsl.model.{ContentType, MediaType, MediaTypes}
87
import akka.http.scaladsl.model.MediaTypes.{`application/octet-stream`, `application/x-tar`}
8+
import akka.http.scaladsl.model.{ContentType, MediaType, MediaTypes}
99
import akka.stream.Materializer
10-
import cats.implicits._
1110
import akka.stream.scaladsl.{Keep, Sink}
1211
import akka.util.ByteString
1312
import cats.effect.Effect
13+
import cats.implicits._
1414
import ch.epfl.bluebrain.nexus.storage.File.{Digest, FileAttributes}
1515
import ch.epfl.bluebrain.nexus.storage.StorageError.InternalError
16-
import ch.epfl.bluebrain.nexus.storage.{fileSource, folderSource, AkkaSource}
16+
import ch.epfl.bluebrain.nexus.storage._
1717
import org.apache.commons.io.FilenameUtils
1818

1919
import scala.concurrent.{ExecutionContext, Future}
@@ -84,7 +84,7 @@ object AttributesComputation {
8484
.alsoToMat(sinkSize)(Keep.right)
8585
.toMat(sinkDigest(msgDigest)) { (bytesF, digestF) =>
8686
(bytesF, digestF).mapN {
87-
case (bytes, digest) => FileAttributes(s"file://$path", bytes, digest, detectMediaType(path, isDir))
87+
case (bytes, digest) => FileAttributes(path.toAkkaUri, bytes, digest, detectMediaType(path, isDir))
8888
}
8989
}
9090
.run()

src/main/scala/ch/epfl/bluebrain/nexus/storage/package.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ package object storage {
6969
*/
7070
def descendantOf(parent: JavaPath): Boolean =
7171
inner(parent, path.getParent)
72+
73+
/**
74+
* Converts a Java Path to an Akka [[Uri]]
75+
*/
76+
def toAkkaUri: Uri = {
77+
val pathString = path.toUri.toString
78+
if (pathString.endsWith("/")) Uri(pathString.dropRight(1)) else Uri(pathString)
79+
}
7280
}
7381

7482
/**

src/test/scala/ch/epfl/bluebrain/nexus/storage/DiskStorageSpec.scala

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class DiskStorageSpec
153153

154154
"fail when call to nexus-fixer fails" in new AbsoluteDirectoryCreated {
155155
val badStorage = new DiskStorage[IO](sConfig.copy(fixerCommand = List("/bin/false")), dConfig, cache)
156-
val file = "some/folder/myfile.txt"
156+
val file = "some/folder/my !file.txt"
157157
val absoluteFile = baseRootPath.resolve(Paths.get(file.toString))
158158
Files.createDirectories(absoluteFile.getParent)
159159
Files.write(absoluteFile, "something".getBytes(StandardCharsets.UTF_8))
@@ -180,22 +180,22 @@ class DiskStorageSpec
180180
}
181181

182182
"fail when destination already exists" in new AbsoluteDirectoryCreated {
183-
val file = "some/folder/myfile.txt"
183+
val file = "some/folder/my !file.txt"
184184
val absoluteFile = baseRootPath.resolve(Paths.get(file.toString))
185185
Files.createDirectories(absoluteFile.getParent)
186186
Files.write(absoluteFile, "something".getBytes(StandardCharsets.UTF_8))
187187

188-
val fileDest = basePath.resolve(Paths.get("myfile.txt"))
188+
val fileDest = basePath.resolve(Paths.get("my !file.txt"))
189189
Files.write(fileDest, "something".getBytes(StandardCharsets.UTF_8))
190190
storage
191-
.moveFile(name, Uri.Path(file), Uri.Path("myfile.txt"))
191+
.moveFile(name, Uri.Path(file), Uri.Path("my !file.txt"))
192192
.rejected[PathAlreadyExists] shouldEqual
193-
PathAlreadyExists(name, Uri.Path("myfile.txt"))
193+
PathAlreadyExists(name, Uri.Path("my !file.txt"))
194194
}
195195

196196
"fail when destination is out of bucket scope" in new AbsoluteDirectoryCreated {
197-
val file = "some/folder/myfile.txt"
198-
val dest = Uri.Path("../some/other.txt")
197+
val file = "some/folder/my !file.txt"
198+
val dest = Uri.Path("../some/other path.txt")
199199
val absoluteFile = baseRootPath.resolve(Paths.get(file.toString))
200200
Files.createDirectories(absoluteFile.getParent)
201201

@@ -208,25 +208,25 @@ class DiskStorageSpec
208208
}
209209

210210
"pass on file" in new AbsoluteDirectoryCreated {
211-
val file = "some/folder/myfile.txt"
211+
val file = "some/folder/my !file.txt"
212212
val absoluteFile = baseRootPath.resolve(Paths.get(file.toString))
213213
Files.createDirectories(absoluteFile.getParent)
214214

215215
val content = "some content"
216216
Files.write(absoluteFile, content.getBytes(StandardCharsets.UTF_8))
217217

218-
storage.moveFile(name, Uri.Path(file), Uri.Path("some/other.txt")).accepted shouldEqual
219-
FileAttributes(s"file://${basePath.resolve("some/other.txt")}", 12L, Digest.empty, `text/plain(UTF-8)`)
218+
storage.moveFile(name, Uri.Path(file), Uri.Path("some/other path.txt")).accepted shouldEqual
219+
FileAttributes(s"file://${basePath.resolve("some/other%20path.txt")}", 12L, Digest.empty, `text/plain(UTF-8)`)
220220
Files.exists(absoluteFile) shouldEqual false
221-
Files.exists(basePath.resolve("some/other.txt")) shouldEqual true
221+
Files.exists(basePath.resolve("some/other path.txt")) shouldEqual true
222222
}
223223

224224
"pass on directory" in new AbsoluteDirectoryCreated {
225225
val dir = "some/folder"
226226
val absoluteDir = baseRootPath.resolve(Paths.get(dir.toString))
227227
Files.createDirectories(absoluteDir)
228228

229-
val absoluteFile = absoluteDir.resolve(Paths.get("myfile.txt"))
229+
val absoluteFile = absoluteDir.resolve(Paths.get("my !file.txt"))
230230
val content = "some content"
231231
Files.write(absoluteFile, content.getBytes(StandardCharsets.UTF_8))
232232

@@ -236,7 +236,7 @@ class DiskStorageSpec
236236
Files.exists(absoluteDir) shouldEqual false
237237
Files.exists(absoluteFile) shouldEqual false
238238
Files.exists(resolvedDir) shouldEqual true
239-
Files.exists(basePath.resolve("some/other/myfile.txt")) shouldEqual true
239+
Files.exists(basePath.resolve("some/other/my !file.txt")) shouldEqual true
240240
}
241241
}
242242

src/test/scala/ch/epfl/bluebrain/nexus/storage/attributes/AttributesCacheSpec.scala

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import akka.actor.ActorSystem
88
import akka.testkit.TestKit
99
import akka.util.Timeout
1010
import ch.epfl.bluebrain.nexus.commons.test.Randomness
11+
import ch.epfl.bluebrain.nexus.storage._
1112
import ch.epfl.bluebrain.nexus.storage.File.{Digest, FileAttributes}
1213
import ch.epfl.bluebrain.nexus.storage.config.AppConfig.DigestConfig
1314
import monix.eval.Task
@@ -47,7 +48,7 @@ class AttributesCacheSpec
4748
val path: Path = Paths.get(genString())
4849
val digest = Digest(config.algorithm, genString())
4950
val attributes = FileAttributes(s"file://$path", genInt().toLong, digest, `image/jpeg`)
50-
def attributesEmpty(p: Path = path) = FileAttributes(s"file://$p", 0L, Digest.empty, `application/octet-stream`)
51+
def attributesEmpty(p: Path = path) = FileAttributes(p.toAkkaUri, 0L, Digest.empty, `application/octet-stream`)
5152
val counter = new AtomicInteger(0)
5253

5354
implicit val clock: Clock = new Clock {
@@ -81,12 +82,9 @@ class AttributesCacheSpec
8182

8283
"verify 2 concurrent computations" in new Ctx {
8384
val list = List.tabulate(10) { i =>
84-
Paths.get(i.toString) -> FileAttributes(
85-
s"file://$i",
86-
i.toLong,
87-
Digest(config.algorithm, i.toString),
88-
`image/jpeg`
89-
)
85+
val path = Paths.get(i.toString)
86+
val digest = Digest(config.algorithm, i.toString)
87+
path -> FileAttributes(path.toAkkaUri, i.toLong, digest, `image/jpeg`)
9088
}
9189
val time = System.currentTimeMillis()
9290

@@ -120,12 +118,9 @@ class AttributesCacheSpec
120118

121119
"verify remove oldest" in new Ctx {
122120
val list = List.tabulate(20) { i =>
123-
Paths.get(i.toString) -> FileAttributes(
124-
s"file://$i",
125-
i.toLong,
126-
Digest(config.algorithm, i.toString),
127-
`image/jpeg`
128-
)
121+
val path = Paths.get(i.toString)
122+
val digest = Digest(config.algorithm, i.toString)
123+
path -> FileAttributes(path.toAkkaUri, i.toLong, digest, `image/jpeg`)
129124
}
130125

131126
forAll(list) {
@@ -150,12 +145,9 @@ class AttributesCacheSpec
150145

151146
"verify failure is skipped" in new Ctx {
152147
val list = List.tabulate(5) { i =>
153-
Paths.get(i.toString) -> FileAttributes(
154-
s"file://$i",
155-
i.toLong,
156-
Digest(config.algorithm, i.toString),
157-
`image/jpeg`
158-
)
148+
val path = Paths.get(i.toString)
149+
val digest = Digest(config.algorithm, i.toString)
150+
path -> FileAttributes(path.toAkkaUri, i.toLong, digest, `image/jpeg`)
159151
}
160152

161153
forAll(list) {

0 commit comments

Comments
 (0)