Skip to content

Commit

Permalink
File Sandboxing, Directory Limiting, and Multiple Template Sources (#164
Browse files Browse the repository at this point in the history
)

* * Update for sandboxed NIOLeafFiles configuration
* Pin to Vapor4 release

* -

* * Update test to show configuring custom sandbox on NIOLeafFiles

* * Update for `LeafSources`

* -

* * Pin LeafKit from 1.0.0-rc.1.11
  • Loading branch information
tdotclare authored Jun 19, 2020
1 parent 409ad1c commit 7f5f878
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 17 deletions.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ let package = Package(
.library(name: "Leaf", targets: ["Leaf"]),
],
dependencies: [
.package(url: "https://github.com/vapor/leaf-kit.git", from: "1.0.0-rc.1.2"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc.1"),
.package(url: "https://github.com/vapor/leaf-kit.git", from: "1.0.0-rc.1.11"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
],
targets: [
.target(name: "Leaf", dependencies: [
Expand Down
14 changes: 9 additions & 5 deletions Sources/Leaf/Application+Leaf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension Application {
return .init(
configuration: self.configuration,
cache: self.cache,
files: self.files,
sources: self.sources,
eventLoop: self.application.eventLoopGroup.next(),
userInfo: userInfo
)
Expand All @@ -51,12 +51,16 @@ extension Application {
}
}

public var files: LeafFiles {
public var sources: LeafSources {
get {
self.storage.files ?? NIOLeafFiles(fileio: self.application.fileio)
self.storage.sources ?? LeafSources.singleSource(
NIOLeafFiles(fileio: self.application.fileio,
limits: .default,
sandboxDirectory: self.configuration.rootDirectory,
viewDirectory: self.configuration.rootDirectory))
}
nonmutating set {
self.storage.files = newValue
self.storage.sources = newValue
}
}

Expand Down Expand Up @@ -95,7 +99,7 @@ extension Application {
final class Storage {
var cache: LeafCache
var configuration: LeafConfiguration?
var files: LeafFiles?
var sources: LeafSources?
var tags: [String: LeafTag]
var userInfo: [AnyHashable: Any]

Expand Down
15 changes: 15 additions & 0 deletions Sources/Leaf/Deprecated.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Vapor


extension Application.Leaf {
/// Deprecated in Leaf-Kit 1.0.0rc-1.??
@available(*, deprecated, message: "Use .sources instead of .files")
public var files: LeafSource {
get {
fatalError("Unavailable")
}
nonmutating set {
self.storage.sources = .singleSource(newValue)
}
}
}
2 changes: 1 addition & 1 deletion Sources/Leaf/Request+Leaf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extension Request {
configuration: self.application.leaf.configuration,
tags: self.application.leaf.tags,
cache: self.application.leaf.cache,
files: self.application.leaf.files,
sources: self.application.leaf.sources,
eventLoop: self.eventLoop,
userInfo: userInfo
)
Expand Down
78 changes: 69 additions & 9 deletions Tests/LeafTests/LeafTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ class LeafTests: XCTestCase {
defer { app.shutdown() }

app.views.use(.leaf)
app.leaf.configuration.rootDirectory = projectFolder
app.leaf.cache.isEnabled = false

app.get("test-file") { req in
req.view.render(#file, ["foo": "bar"])
req.view.render("Tests/LeafTests/LeafTests.swift", ["foo": "bar"])
}

try app.test(.GET, "test-file") { res in
Expand All @@ -20,6 +21,46 @@ class LeafTests: XCTestCase {
XCTAssertContains(res.body.string, "test: bar")
}
}

func testSandboxing() throws {
let app = Application(.testing)
defer { app.shutdown() }

app.views.use(.leaf)
app.leaf.configuration.rootDirectory = templateFolder
app.leaf.sources = .singleSource(NIOLeafFiles(fileio: app.fileio,
limits: .default,
sandboxDirectory: projectFolder,
viewDirectory: templateFolder))

app.get("hello") { req in
req.view.render("hello")
}

app.get("allowed") { req in
req.view.render("../hello")
}

app.get("sandboxed") { req in
req.view.render("../../hello")
}

try app.test(.GET, "hello") { res in
XCTAssertEqual(res.status, .ok)
XCTAssertEqual(res.headers.contentType, .html)
XCTAssertEqual(res.body.string, "Hello, world!\n")
}

try app.test(.GET, "allowed") { res in
XCTAssertEqual(res.status, .internalServerError)
XCTAssert(res.body.string.contains("noTemplateExists"))
}

try app.test(.GET, "sandboxed") { res in
XCTAssertEqual(res.status, .internalServerError)
XCTAssert(res.body.string.contains("Attempted to escape sandbox"))
}
}

func testContextRequest() throws {
var test = TestFiles()
Expand All @@ -40,7 +81,7 @@ class LeafTests: XCTestCase {
app.leaf.configuration.rootDirectory = "/"
app.leaf.cache.isEnabled = false
app.leaf.tags["path"] = RequestPathTag()
app.leaf.files = test
app.leaf.sources = .singleSource(test)

app.get("test-file") { req in
req.view.render("foo", [
Expand Down Expand Up @@ -77,7 +118,7 @@ class LeafTests: XCTestCase {
app.leaf.configuration.rootDirectory = "/"
app.leaf.cache.isEnabled = false
app.leaf.tags["custom"] = CustomTag()
app.leaf.files = test
app.leaf.sources = .singleSource(test)
app.leaf.userInfo["info"] = "World"

app.get("test-file") { req in
Expand All @@ -94,20 +135,39 @@ class LeafTests: XCTestCase {
}
}

struct TestFiles: LeafFiles {
/// Helper `LeafFiles` struct providing an in-memory thread-safe map of "file names" to "file data"
internal struct TestFiles: LeafSource {
var files: [String: String]

var lock: Lock

init() {
files = [:]
lock = .init()
}

func file(path: String, on eventLoop: EventLoop) -> EventLoopFuture<ByteBuffer> {

public func file(template: String, escape: Bool = false, on eventLoop: EventLoop) -> EventLoopFuture<ByteBuffer> {
var path = template
if path.split(separator: "/").last?.split(separator: ".").count ?? 1 < 2,
!path.hasSuffix(".leaf") { path += ".leaf" }
if !path.hasPrefix("/") { path = "/" + path }

self.lock.lock()
defer { self.lock.unlock() }
if let file = self.files[path] {
var buffer = ByteBufferAllocator().buffer(capacity: 0)
var buffer = ByteBufferAllocator().buffer(capacity: file.count)
buffer.writeString(file)
return eventLoop.makeSucceededFuture(buffer)
} else {
return eventLoop.makeFailedFuture("no test file: \(path)")
return eventLoop.makeFailedFuture(LeafError(.noTemplateExists(template)))
}
}
}

internal var templateFolder: String {
return projectFolder + "Views/"
}

internal var projectFolder: String {
let folder = #file.split(separator: "/").dropLast(3).joined(separator: "/")
return "/" + folder + "/"
}

0 comments on commit 7f5f878

Please sign in to comment.