Skip to content

Commit

Permalink
fix: properly escape shell commands
Browse files Browse the repository at this point in the history
  • Loading branch information
123FLO321 committed Apr 27, 2022
1 parent 591ab84 commit d048593
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ internal class StaticMapController {
} else {
scaleString = "@\(staticMap.scale)x"
}
let tileURL = "\(tileServerURL)/styles/\(staticMap.style)/static/\(staticMap.longitude),\(staticMap.latitude),\(staticMap.zoom)@\(staticMap.bearing ?? 0),\(staticMap.pitch ?? 0)/\(staticMap.width)x\(staticMap.height)\(scaleString).\(staticMap.format ?? "png")"
let tileURL = "\(tileServerURL)/styles/\(staticMap.style)/static/\(staticMap.longitude),\(staticMap.latitude),\(staticMap.zoom)@\(staticMap.bearing ?? 0),\(staticMap.pitch ?? 0)/\(staticMap.width)x\(staticMap.height)\(scaleString).\(staticMap.format ?? ImageFormat.png)"
return APIUtils.downloadFile(request: request, from: tileURL, to: path, type: "image").flatMapError { error in
return request.eventLoop.makeFailedFuture(Abort(.badRequest, reason: "Failed to load base static map: (\(error.localizedDescription))"))
}
Expand All @@ -183,7 +183,7 @@ internal class StaticMapController {
x: point.x + xOffset,
y: point.y + yOffset,
scale: staticMap.scale,
format: staticMap.format ?? "png"
format: staticMap.format ?? ImageFormat.png
))
}
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/SwiftTileserverCache/Controller/TileController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ internal class TileController {
let y = request.parameters.get("y", as: Int.self),
let scale = request.parameters.get("scale", as: UInt8.self),
scale >= 1,
let format = request.parameters.get("format"),
format == "png" || format == "jpg" else {
let formatString = request.parameters.get("format"),
let format = ImageFormat(rawValue: formatString) else {
throw Abort(.badRequest)
}
return generateTileAndResponse(request: request, style: style, z: z, x: x, y: y, scale: scale, format: format)
}

// MARK: - Utils

internal func generateTile(request: Request, style: String, z: Int, x: Int, y: Int, scale: UInt8, format: String) -> EventLoopFuture<String> {
internal func generateTile(request: Request, style: String, z: Int, x: Int, y: Int, scale: UInt8, format: ImageFormat) -> EventLoopFuture<String> {
let path = "Cache/Tile/\(style)-\(z)-\(x)-\(y)-\(scale).\(format)"
guard !FileManager.default.fileExists(atPath: path) else {
request.application.logger.info("Served a cached tile")
Expand All @@ -54,7 +54,7 @@ internal class TileController {
.replacingOccurrences(of: "{y}", with: "\(y)")
.replacingOccurrences(of: "{scale}", with: "\(scale)")
.replacingOccurrences(of: "{@scale}", with: scaleString)
.replacingOccurrences(of: "{format}", with: format)
.replacingOccurrences(of: "{format}", with: format.rawValue)
} else {
tileURL = "\(tileServerURL)/styles/\(style)/\(z)/\(x)/\(y)\(scaleString).\(format)"
}
Expand All @@ -68,7 +68,7 @@ internal class TileController {
}.transform(to: path)
}

private func generateTileAndResponse(request: Request, style: String, z: Int, x: Int, y: Int, scale: UInt8, format: String) -> EventLoopFuture<Response> {
private func generateTileAndResponse(request: Request, style: String, z: Int, x: Int, y: Int, scale: UInt8, format: ImageFormat) -> EventLoopFuture<Response> {
return generateTile(request: request, style: style, z: z, x: x, y: y, scale: scale, format: format).flatMap { path in
return self.generateResponse(request: request, path: path)
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/SwiftTileserverCache/Misc/CacheCleaner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import Foundation
import Vapor
import ShellOut

public class CacheCleaner {

Expand All @@ -30,7 +29,7 @@ public class CacheCleaner {

private func runOnce() {
do {
let count = Int(try shellOut(to: "./Resources/Scripts/clear.bash", arguments: [folder.path, "\(maxAgeMinutes)"])) ?? 0
let count = Int(try escapedShellOut(to: "./Resources/Scripts/clear.bash")) ?? 0
if count != 0 {
logger.info("Removed \(count) Files")
}
Expand Down
18 changes: 18 additions & 0 deletions Sources/SwiftTileserverCache/Misc/EscapedShellOut.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// Created by Florian Kostenzer on 27.04.22.
//

import Foundation
import ShellOut

@discardableResult
public func escapedShellOut(
to command: String,
arguments: [String] = [],
at path: String = ".",
process: Process = .init(),
outputHandle: FileHandle? = nil,
errorHandle: FileHandle? = nil
) throws -> String {
return try shellOut(to: command, arguments: arguments.bashEscaped, at: path, process: process, outputHandle: outputHandle, errorHandle: errorHandle)
}
3 changes: 1 addition & 2 deletions Sources/SwiftTileserverCache/Misc/FileToucher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import Foundation
import Vapor
import ShellOut

public class FileToucher {

Expand All @@ -34,7 +33,7 @@ public class FileToucher {
if !queue.isEmpty {
for slice in queue.chunked(into: 100) {
do {
try shellOut(to: "/usr/bin/touch -c", arguments: slice)
try escapedShellOut(to: "/usr/bin/touch -c", arguments: slice)
count += slice.count
} catch {
logger.warning("Failed to touch files: \(error)")
Expand Down
38 changes: 19 additions & 19 deletions Sources/SwiftTileserverCache/Misc/ImageUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class ImageUtils {
var segments = [[String]]()
for tilePath in tilePaths.sorted() {
if segments.count == 0 {
segments.append(["\\("])
segments.append(["("])
}
let split = tilePath.components(separatedBy: "-")
let y = Int(split[split.count-3]) ?? 0
Expand All @@ -41,20 +41,20 @@ public class ImageUtils {
segments[currentSemgent].append(tilePath)
segments[currentSemgent].append("-append")
} else {
segments[currentSemgent].append("\\)")
segments[currentSemgent].append(")")
if segments.count != 1 {
segments[currentSemgent].append("+append")
}
currentSemgent += 1
segments.append(["\\("])
segments.append(["("])
segments[currentSemgent].append(tilePath)
}
} else {
segments[currentSemgent].append(tilePath)
}
lastY = y
}
segments[currentSemgent].append("\\)")
segments[currentSemgent].append(")")
segments[currentSemgent].append("+append")
args = segments.flatMap({$0})
}
Expand All @@ -77,7 +77,7 @@ public class ImageUtils {
args += ["-crop", "\(imgWidth)x\(imgHeight)+\(imgWidthOffset)+\(imgHeightOffset)", "+repage", path]
return request.application.threadPool.runIfActive(eventLoop: request.eventLoop) {
do {
try shellOut(to: ImageUtils.imagemagickConvertCommand, arguments: args)
try escapedShellOut(to: ImageUtils.imagemagickConvertCommand, arguments: args)
} catch let error as ShellOutError {
request.application.logger.error("Failed to run magick: \(error.message)")
throw Abort(.internalServerError, reason: "ImageMagick Error: \(error.message)")
Expand Down Expand Up @@ -117,10 +117,10 @@ public class ImageUtils {

polygonArguments += [
"-strokewidth", "\(polygon.strokeWidth)",
"-fill", polygon.fillColor.bashEncoded,
"-stroke", polygon.strokeColor.bashEncoded,
"-fill", polygon.fillColor,
"-stroke", polygon.strokeColor,
"-gravity", "Center",
"-draw", "\"polygon \(polygonPath)\""
"-draw", "polygon \(polygonPath)"
]
}

Expand Down Expand Up @@ -150,10 +150,10 @@ public class ImageUtils {

circleArguments += [
"-strokewidth", "\(circle.strokeWidth)",
"-fill", circle.fillColor.bashEncoded,
"-stroke", circle.strokeColor.bashEncoded,
"-fill", circle.fillColor,
"-stroke", circle.strokeColor,
"-gravity", "Center",
"-draw", "\"circle \(x),\(y) \(x),\(y+radius)\""
"-draw", "circle \(x),\(y) \(x),\(y+radius)"
]
}

Expand Down Expand Up @@ -206,7 +206,7 @@ public class ImageUtils {
}

markerArguments += [
"\\(", markerPath, "-resize", "\(marker.width * UInt16(staticMap.scale))x\(marker.height * UInt16(staticMap.scale))", "\\)",
"(", markerPath, "-resize", "\(marker.width * UInt16(staticMap.scale))x\(marker.height * UInt16(staticMap.scale))", ")",
"-gravity", "Center",
"-geometry", "\(realOffsetXPrefix)\(realOffset.x)\(realOffsetYPrefix)\(realOffset.y)",
"-composite"
Expand All @@ -216,13 +216,13 @@ public class ImageUtils {

return request.application.threadPool.runIfActive(eventLoop: request.eventLoop) {
do {
try shellOut(to: ImageUtils.imagemagickConvertCommand, arguments: [
basePath] +
let args = [basePath] +
polygonArguments +
circleArguments +
markerArguments +
[path
])
[path]

try escapedShellOut(to: ImageUtils.imagemagickConvertCommand, arguments: args)
} catch let error as ShellOutError {
request.application.logger.error("Failed to run magick: \(error.message)")
throw Abort(.internalServerError, reason: "ImageMagick Error: \(error.message)")
Expand Down Expand Up @@ -252,7 +252,7 @@ public class ImageUtils {

var args = [String]()
for grid in grids {
args.append("\\(")
args.append("(")
args.append(grid.firstPath)
for image in grid.images {
args.append(image.path)
Expand All @@ -262,7 +262,7 @@ public class ImageUtils {
args.append("+append")
}
}
args.append("\\)")
args.append(")")
if grid.direction == .bottom {
args.append("-append")
} else {
Expand All @@ -273,7 +273,7 @@ public class ImageUtils {

return request.application.threadPool.runIfActive(eventLoop: request.eventLoop) {
do {
try shellOut(to: imagemagickConvertCommand, arguments: args)
try escapedShellOut(to: imagemagickConvertCommand, arguments: args)
} catch let error as ShellOutError {
request.application.logger.error("Failed to run magick: \(error.message)")
throw Abort(.internalServerError, reason: "ImageMagick Error: \(error.message)")
Expand Down
17 changes: 0 additions & 17 deletions Sources/SwiftTileserverCache/Misc/String+BashEncode.swift

This file was deleted.

28 changes: 28 additions & 0 deletions Sources/SwiftTileserverCache/Misc/String+BashEscaped.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// String+BashEncode.swift
// SwiftTileserverCache
//
// Created by Florian Kostenzer on 17.05.20.
//

import Foundation

internal extension String {
var bashEscaped: String {
let safeChars = [
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"/", "-", "+", "_", ",", ".", ":", "="
]

return self.trimmingCharacters(in: .nonBaseCharacters).map { char in
let string = String(char)
if safeChars.contains(string) {
return string
} else {
return "\\" + string
}
}.joined()
}
}
14 changes: 14 additions & 0 deletions Sources/SwiftTileserverCache/Misc/StringArray+BashEscaped.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// String+BashEncode.swift
// SwiftTileserverCache
//
// Created by Florian Kostenzer on 17.05.20.
//

import Foundation

internal extension Array where Element == String {
var bashEscaped: [String] {
return self.map({$0.bashEscaped})
}
}
12 changes: 12 additions & 0 deletions Sources/SwiftTileserverCache/Model/ImageFormat.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// CombineDirection.swift
// SwiftTileserverCache
//
// Created by Florian Kostenzer on 04.03.20.
//

import Foundation

public enum ImageFormat: String, Codable, Hashable {
case png, jpg
}
4 changes: 2 additions & 2 deletions Sources/SwiftTileserverCache/Model/StaticMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ public struct StaticMap: Codable, Hashable, PersistentHashable {
public var width: UInt16
public var height: UInt16
public var scale: UInt8
public var format: String?
public var format: ImageFormat?
public var bearing: Double?
public var pitch: Double?
public var markers: [Marker]?
public var polygons: [Polygon]?
public var circles: [Circle]?

internal var path: String {
return "Cache/Static/\(persistentHash).\(format ?? "png")"
return "Cache/Static/\(persistentHash).\(format ?? ImageFormat.png)"
}
}

0 comments on commit d048593

Please sign in to comment.