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

Fix .env parsing with no trailing newline #2225

Merged
merged 1 commit into from Mar 6, 2020
Merged
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
19 changes: 13 additions & 6 deletions Sources/Vapor/Utilities/DotEnv.swift
Expand Up @@ -86,7 +86,7 @@ public struct DotEnvFile {
}

/// Represents a `KEY=VALUE` pair in a dotenv file.
public struct Line: CustomStringConvertible {
public struct Line: CustomStringConvertible, Equatable {
/// The key.
public let key: String

Expand Down Expand Up @@ -122,9 +122,9 @@ public struct DotEnvFile {
}
}

// MARK: Private
// MARK: Parser

private extension DotEnvFile {
extension DotEnvFile {
struct Parser {
var source: ByteBuffer
init(source: ByteBuffer) {
Expand Down Expand Up @@ -179,13 +179,15 @@ private extension DotEnvFile {
guard let value = self.parseLineValue() else {
return nil
}
self.pop() // \n
return Line(key: key, value: value)
}

private mutating func parseLineValue() -> String? {
guard let valueLength = self.countDistance(to: .newLine) else {
return nil
let valueLength: Int
if let toNewLine = self.countDistance(to: .newLine) {
valueLength = toNewLine
} else {
valueLength = self.source.readableBytes
}
guard let value = self.source.readString(length: valueLength) else {
return nil
Expand Down Expand Up @@ -225,11 +227,16 @@ private extension DotEnvFile {

private func countDistance(to byte: UInt8) -> Int? {
var copy = self.source
var found = false
scan: while let next = copy.readInteger(as: UInt8.self) {
if next == byte {
found = true
break scan
}
}
guard found else {
return nil
}
let distance = copy.readerIndex - source.readerIndex
guard distance != 0 else {
return nil
Expand Down
30 changes: 0 additions & 30 deletions Tests/VaporTests/ApplicationTests.swift
Expand Up @@ -988,36 +988,6 @@ final class ApplicationTests: XCTestCase {
try XCTAssertEqual(c.wait(), [1, 2])
}

func testDotEnvRead() throws {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let pool = NIOThreadPool(numberOfThreads: 1)
pool.start()
let fileio = NonBlockingFileIO(threadPool: pool)
let folder = #file.split(separator: "/").dropLast().joined(separator: "/")
let path = "/" + folder + "/Utilities/test.env"
let file = try DotEnvFile.read(path: path, fileio: fileio, on: elg.next()).wait()
let test = file.lines.map { $0.description }.joined(separator: "\n")
XCTAssertEqual(test, """
NODE_ENV=development
BASIC=basic
AFTER_LINE=after_line
UNDEFINED_EXPAND=$TOTALLY_UNDEFINED_ENV_KEY
EMPTY=
SINGLE_QUOTES=single_quotes
DOUBLE_QUOTES=double_quotes
EXPAND_NEWLINES=expand\nnewlines
DONT_EXPAND_NEWLINES_1=dontexpand\\nnewlines
DONT_EXPAND_NEWLINES_2=dontexpand\\nnewlines
EQUAL_SIGNS=equals==
RETAIN_INNER_QUOTES={"foo": "bar"}
RETAIN_INNER_QUOTES_AS_STRING={"foo": "bar"}
INCLUDE_SPACE=some spaced out string
USERNAME=therealnerdybeast@example.tld
""")
try pool.syncShutdownGracefully()
try elg.syncShutdownGracefully()
}

// https://github.com/vapor/vapor/issues/1997
func testWebSocket404() throws {
let app = Application(.testing)
Expand Down
46 changes: 46 additions & 0 deletions Tests/VaporTests/DotEnvTests.swift
@@ -0,0 +1,46 @@
@testable import Vapor
import XCTVapor

final class DotEnvTests: XCTestCase {
func testReadFile() throws {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let pool = NIOThreadPool(numberOfThreads: 1)
pool.start()
let fileio = NonBlockingFileIO(threadPool: pool)
let folder = #file.split(separator: "/").dropLast().joined(separator: "/")
let path = "/" + folder + "/Utilities/test.env"
let file = try DotEnvFile.read(path: path, fileio: fileio, on: elg.next()).wait()
let test = file.lines.map { $0.description }.joined(separator: "\n")
XCTAssertEqual(test, """
NODE_ENV=development
BASIC=basic
AFTER_LINE=after_line
UNDEFINED_EXPAND=$TOTALLY_UNDEFINED_ENV_KEY
EMPTY=
SINGLE_QUOTES=single_quotes
DOUBLE_QUOTES=double_quotes
EXPAND_NEWLINES=expand\nnewlines
DONT_EXPAND_NEWLINES_1=dontexpand\\nnewlines
DONT_EXPAND_NEWLINES_2=dontexpand\\nnewlines
EQUAL_SIGNS=equals==
RETAIN_INNER_QUOTES={"foo": "bar"}
RETAIN_INNER_QUOTES_AS_STRING={"foo": "bar"}
INCLUDE_SPACE=some spaced out string
USERNAME=therealnerdybeast@example.tld
""")
try pool.syncShutdownGracefully()
try elg.syncShutdownGracefully()
}

func testNoTrailingNewline() throws {
let env = "FOO=bar\nBAR=baz"
var buffer = ByteBufferAllocator().buffer(capacity: 0)
buffer.writeString(env)
var parser = DotEnvFile.Parser(source: buffer)
let lines = parser.parse()
XCTAssertEqual(lines, [
.init(key: "FOO", value: "bar"),
.init(key: "BAR", value: "baz"),
])
}
}