Support auto-expansion of attachment jars on disk.

This commit is contained in:
Mike Hearn 2016-03-02 14:39:40 +01:00
parent 7fd9b43e50
commit 8d906c703d
2 changed files with 58 additions and 3 deletions

View File

@ -8,10 +8,12 @@
package core
import com.google.common.io.ByteStreams
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.SettableFuture
import org.slf4j.Logger
import java.io.BufferedInputStream
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Path
@ -21,6 +23,7 @@ import java.time.temporal.Temporal
import java.util.concurrent.Executor
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import java.util.zip.ZipInputStream
import kotlin.concurrent.withLock
import kotlin.reflect.KProperty
@ -122,3 +125,31 @@ class TransientProperty<T>(private val initializer: () -> T) {
return v!!
}
}
/**
* Given a path to a zip file, extracts it to the given directory.
*/
fun extractZipFile(zipPath: Path, toPath: Path) {
if (!Files.exists(toPath))
Files.createDirectories(toPath)
ZipInputStream(BufferedInputStream(Files.newInputStream(zipPath))).use { zip ->
while (true) {
val e = zip.nextEntry ?: break
val outPath = toPath.resolve(e.name)
// Security checks: we should reject a zip that contains tricksy paths that try to escape toPath.
if (!outPath.normalize().startsWith(toPath))
throw IllegalStateException("ZIP contained a path that resolved incorrectly: ${e.name}")
if (e.isDirectory) {
Files.createDirectories(outPath)
continue
}
Files.newOutputStream(outPath).use { out ->
ByteStreams.copy(zip, out)
}
zip.closeEntry()
}
}
}

View File

@ -15,11 +15,13 @@ import com.google.common.io.CountingInputStream
import core.Attachment
import core.AttachmentStorage
import core.crypto.SecureHash
import core.extractZipFile
import core.utilities.loggerFor
import java.io.FilterInputStream
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.*
import java.util.jar.JarInputStream
@ -35,6 +37,13 @@ class NodeAttachmentStorage(val storePath: Path) : AttachmentStorage {
@VisibleForTesting
var checkAttachmentsOnLoad = true
/**
* If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
* human browsing convenience: the attachment itself will still be the file (that is, edits to the extracted directory
* will not have any effect).
*/
@Volatile var automaticallyExtractAttachments = false
init {
require(Files.isDirectory(storePath)) { "$storePath must be a directory" }
}
@ -102,14 +111,29 @@ class NodeAttachmentStorage(val storePath: Path) : AttachmentStorage {
Files.deleteIfExists(tmp)
}
log.info("Stored new attachment $id")
if (automaticallyExtractAttachments) {
val extractTo = storePath.resolve("${id}.jar")
try {
Files.createDirectory(extractTo)
extractZipFile(finalPath, extractTo)
} catch(e: Exception) {
log.error("Failed to extract attachment jar $id, ", e)
// TODO: Delete the extractTo directory here.
}
}
return id
}
private fun checkIsAValidJAR(path: Path) {
// Just iterate over the entries with verification enabled: should be good enough to catch mistakes.
JarInputStream(Files.newInputStream(path), true).use { stream ->
var cursor = stream.nextJarEntry
while (cursor != null) cursor = stream.nextJarEntry
while (true) {
val cursor = stream.nextJarEntry ?: break
val entryPath = Paths.get(cursor.name)
// Security check to stop zips trying to escape their rightful place.
if (entryPath.isAbsolute || entryPath.normalize() != entryPath)
throw IllegalArgumentException("Path is either absolute or non-normalised: $entryPath")
}
}
}
}