Skip to content
This repository has been archived by the owner on Apr 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2 from nodes-vapor/public-interface
Browse files Browse the repository at this point in the history
Public interface and README
  • Loading branch information
BrettRToomey committed Dec 22, 2016
2 parents a3fd232 + 879a67a commit 62ddf43
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 9 deletions.
3 changes: 2 additions & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
coverage:
range: "80...100"
ignore:
- "Sources/DataURI/Scanner.swift"
- "Sources/Scanner.swift"
- "Sources/DataURI+String.swift"
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,40 @@
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nodes-vapor/DataURI/master/LICENSE)

A pure Swift parser for Data URIs.

## Integration
Update your `Package.swift` file.
```swift
.Package(url: "https://github.com/nodes-vapor/DataURI.git", majorVersion: 0)
```

## Getting started 🚀
There are two options for decoding a Data URI. The first is using the `String` extension and the second is by using the `DataURIParser` directly.

### The `String` method
This method is by far the easiest to use. All you need to do is call `.dataURIDecoded() throws -> (type: String, data: Bytes)` on any Data URI encoded `String`.

```swift
let uri = "data:,Hello%2C%20World!"
let (type, data) = try uri.dataURIDecoded()
print(type) // "text/plain;charset=US-ASCII"
print(data.string) // "Hello, World!"
```

### The `DataURIParser` method
Using the parser is a bit more involved as it returns all of its results as `Bytes`.

```swift
let uri = "data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E"
let (type, metadata, data) = try DataURIParser.parse(uri: uri)
print(type.string) // "text/html"
print(metadata == nil) // "true"
print(data.string) // "<h1>Hello, World!</h1>"
```


## 🏆 Credits
This package is developed and maintained by the Vapor team at [Nodes](https://www.nodes.dk).

## 📄 License
This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
30 changes: 30 additions & 0 deletions Sources/DataURI+String.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Core
import Foundation

//FIXME(Brett): I patched this into Core 1.1, remove when updated.
extension Sequence where Iterator.Element == Byte {
internal var base64Decoded: Bytes {
let bytes = [Byte](self)
let dataBase64 = Data(bytes: bytes)
guard let data = Data(base64Encoded: dataBase64) else {
return []
}

var encodedBytes = Bytes(repeating: 0, count: data.count)
data.copyBytes(to: &encodedBytes, count: data.count)

return encodedBytes
}
}

extension String {
/**
Parses a Data URI and returns its type and data.
- Returns: The type of the file and its data as bytes.
*/
public func dataURIDecoded() throws -> (type: String, data: Bytes) {
let (type, _, data) = try DataURIParser.parse(uri: self)
return (type.string, data)
}
}
Empty file removed Sources/DataURI.swift
Empty file.
65 changes: 61 additions & 4 deletions Sources/Parser.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Core

struct DataURIParser {
/// A parser for decoding Data URIs.
public struct DataURIParser {
enum Error: Swift.Error {
case invalidScheme
case invalidURI
Expand All @@ -14,7 +15,15 @@ struct DataURIParser {
}

extension DataURIParser {
static func parse(uri: String) throws -> (Bytes, Bytes?, Bytes) {
/**
Parses a Data URI and returns its type and data.
- Parameters:
- uri: The URI to be parsed.
- Returns: (type: Bytes, typeMetadata: Bytes?, data: Bytes)
*/
public static func parse(uri: String) throws -> (Bytes, Bytes?, Bytes) {
guard uri.hasPrefix("data:") else {
throw Error.invalidScheme
}
Expand All @@ -25,13 +34,17 @@ extension DataURIParser {

var parser = DataURIParser(scanner: scanner)
var (type, typeMetadata) = try parser.extractType()
let data = try parser.extractData()
var data = try parser.extractData()

//Required by RFC 2397
if type.isEmpty {
type = "text/plain;charset=US-ASCII".bytes
}

if let typeMetadata = typeMetadata, typeMetadata == "base64".bytes {
data = data.base64Decoded
}

return (type, typeMetadata, data)
}
}
Expand Down Expand Up @@ -63,7 +76,7 @@ extension DataURIParser {
mutating func extractData() throws -> Bytes {
assert(scanner.peek() == .comma)
scanner.pop()
return consume()
return try consumePercentDecoded()
}
}

Expand All @@ -80,6 +93,22 @@ extension DataURIParser {
return bytes
}

@discardableResult
mutating func consumePercentDecoded() throws -> Bytes {
var bytes: Bytes = []

while var byte = scanner.peek() {
if byte == .percent {
byte = try decodePercentEncoding()
}

scanner.pop()
bytes.append(byte)
}

return bytes
}

@discardableResult
mutating func consume(until terminators: Set<Byte>) -> Bytes {
var bytes: Bytes = []
Expand All @@ -105,3 +134,31 @@ extension DataURIParser {
}
}

extension DataURIParser {
mutating func decodePercentEncoding() throws -> Byte {
assert(scanner.peek() == .percent)

guard
let leftMostDigit = scanner.peek(aheadBy: 1),
let rightMostDigit = scanner.peek(aheadBy: 2)
else {
throw Error.invalidURI
}

scanner.pop(2)

return (leftMostDigit.asciiCode * 16) + rightMostDigit.asciiCode
}
}

extension Byte {
internal var asciiCode: Byte {
if self >= 48 && self <= 57 {
return self - 48
} else if self >= 65 && self <= 70 {
return self - 55
} else {
return 0
}
}
}
13 changes: 10 additions & 3 deletions Tests/DataURITests/DataURITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,21 @@ class DataURITests: XCTestCase {
("testSpeed", testSpeed)
]

func testBase64() {
//FIXME(Brett): remove when vapor/core is updated to 1.1
let base64Bytes = "SGVsbG8sIHdvcmxkIQ==".bytes
let output = base64Bytes.base64Decoded
XCTAssertEqual(output.string, "Hello, world!")
}

func testTextNoType() {
let (type, meta, data) = try! DataURIParser.parse(
uri: "data:,Hello%2C%20World!"
)

XCTAssertEqual(type.string, "text/plain;charset=US-ASCII")
XCTAssertNil(meta)
XCTAssertEqual(data.string, "Hello%2C%20World!")
XCTAssertEqual(data.string, "Hello, World!")
}

func testBase64Text() {
Expand All @@ -28,7 +35,7 @@ class DataURITests: XCTestCase {

XCTAssertEqual(type.string, "text/plain")
XCTAssertEqual(meta?.string, "base64")
XCTAssertEqual(data.string, "SGVsbG8sIFdvcmxkIQ%3D%3D")
XCTAssertEqual(data.string, "Hello, World!")
}

func testHTMLText() {
Expand All @@ -38,7 +45,7 @@ class DataURITests: XCTestCase {

XCTAssertEqual(type.string, "text/html")
XCTAssertNil(meta)
XCTAssertEqual(data.string, "%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E")
XCTAssertEqual(data.string, "<h1>Hello, World!</h1>")
}

func testHTMLJavascriptText() {
Expand Down
40 changes: 39 additions & 1 deletion Tests/DataURITests/ParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ class ParserTests: XCTestCase {
("testExtractTypeWithMetadata", testExtractTypeWithMetadata),
("testConsumeUntil", testConsumeUntil),
("testConsumeWhile", testConsumeWhile),
("testConsume", testConsume)
("testConsume", testConsume),
("testConsumePercentDecoded", testConsumePercentDecoded),
("testConsumePercentDecodedFailed", testConsumePercentDecodedFailed)
]

func testParserInit() {
Expand Down Expand Up @@ -135,4 +137,40 @@ class ParserTests: XCTestCase {
XCTAssertEqual(output, expected)
XCTAssertNil(parser.scanner.peek())
}

func testConsumePercentDecoded() {
let bytes: [Byte] = [
.a,
.C,
.semicolon,
.comma,
.percent,
0x32, //2
.C,
.percent,
0x32, // 2
0x00,
.f
]

let expected: [Byte] = [.a, .C, .semicolon, .comma, .comma, .space, .f]

var parser = DataURIParser(scanner: Scanner(bytes))
let output = try! parser.consumePercentDecoded()

XCTAssertEqual(output, expected)
XCTAssertNil(parser.scanner.peek())
}

func testConsumePercentDecodedFailed() {
let bytes: [Byte] = [
.percent,
.C,
]

expect(toThrow: DataURIParser.Error.invalidURI) {
var parser = DataURIParser(scanner: Scanner(bytes))
_ = try parser.consumePercentDecoded()
}
}
}
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import XCTest

XCTMain([
testCase(DataURITests.allTests),
testCase(ParserTests.allTests),
])

0 comments on commit 62ddf43

Please sign in to comment.