├── SwiftCGI Framework
├── SwiftCGITests-Bridging-Header.h
├── SwiftCGI.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── SwiftCGI.xcscmblueprint
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── SwiftCGI.xcscheme
│ └── project.pbxproj
├── Backends
│ ├── HTTP
│ │ ├── CHTTPParser
│ │ │ ├── module.map
│ │ │ ├── http_parser_accessors.h
│ │ │ ├── http_parser_accessors.c
│ │ │ └── HTTPParser.swift
│ │ ├── EmbeddedHTTPRequest.swift
│ │ └── EmbeddedHTTPBackend.swift
│ ├── BackendProtocol.swift
│ └── FCGI
│ │ ├── FCGIRequest.swift
│ │ ├── FCGIBackend.swift
│ │ └── Record.swift
├── SwiftCGI
│ ├── Util.swift
│ ├── Request.swift
│ ├── Info.plist
│ ├── Router.swift
│ ├── SwiftCGI.h
│ ├── UIntExtensions.swift
│ ├── Types.swift
│ ├── HTTPResponseHelpers.swift
│ ├── HTTPHeaderCollection.swift
│ ├── Server.swift
│ └── HTTPTypes.swift
├── SwiftCGITests
│ ├── RouterTests.swift
│ ├── Info.plist
│ ├── HTTPTests.swift
│ ├── PrimitiveTests.swift
│ └── RecordTests.swift
└── Core Modules
│ └── Sessions
│ ├── Sessions+Extensions.swift
│ ├── SessionManagement.swift
│ └── Tests
│ └── SwifftCGI_SessionsTests.swift
├── .gitmodules
├── LICENSE.txt
├── README.md
└── .gitignore
/SwiftCGI Framework/SwiftCGITests-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "GCDAsyncSocket.h"
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "SwiftCGI Framework/Backends/HTTP/CHTTPParser/http-parser"]
2 | path = SwiftCGI Framework/Backends/HTTP/CHTTPParser/http-parser
3 | url = git@github.com:joyent/http-parser.git
4 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/HTTP/CHTTPParser/module.map:
--------------------------------------------------------------------------------
1 | module CHTTPParser {
2 | explicit module Parser {
3 | header "./http-parser/http_parser.h"
4 | export *
5 | }
6 |
7 | explicit module Accessors {
8 | header "./http_parser_accessors.h"
9 | export *
10 | }
11 | }
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/Util.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Util.swift
3 | // SwiftCGI
4 | //
5 | // Created by Ian Wagner on 1/13/15.
6 | // Copyright (c) 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | public class Box {
10 | public let unboxedValue: T
11 | public init(_ value: T) { self.unboxedValue = value }
12 | }
13 |
14 |
15 | public func ==(lhs: Box, rhs: Box) -> Bool {
16 | return lhs.unboxedValue == rhs.unboxedValue
17 | }
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/BackendProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BackendProtocol.swift
3 | // SwiftCGI
4 | //
5 | // Created by Todd Bluhm on 9/26/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol BackendDelegate {
12 | func finishedParsingRequest(request: Request)
13 | }
14 |
15 | protocol Backend {
16 | var delegate: BackendDelegate? { get set }
17 |
18 | func startReadingFromSocket(sock: GCDAsyncSocket)
19 | func processData(sock: GCDAsyncSocket, data: NSData, tag: Int)
20 | func cleanUp(sock: GCDAsyncSocket)
21 | func sendResponse(request: Request, response: HTTPResponse) -> Bool
22 | }
23 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGITests/RouterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RouterTests.swift
3 | // SwiftCGI
4 | //
5 | // Created by Ian Wagner on 11/23/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class RouterTests: XCTestCase {
12 | func testLoneRootRouter() {
13 | let rootHandler: RequestHandler? = { (req) -> HTTPResponse? in
14 | return HTTPResponse(body: "Root handler")
15 | }
16 | let rootRouter = Router(path: "/", handleWildcardChildren: true, withHandler: rootHandler)
17 |
18 | XCTAssertNotNil(rootRouter.route("/"), "bar")
19 | XCTAssertNil(rootRouter.route("/foo"), "bar")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/HTTP/CHTTPParser/http_parser_accessors.h:
--------------------------------------------------------------------------------
1 | //
2 | // http_parser_accessors.h
3 | // SwiftCGI
4 | //
5 | // Created by Todd Bluhm on 9/23/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | #ifndef http_parser_accessors_h
10 | #define http_parser_accessors_h
11 |
12 | #include "http-parser/http_parser.h"
13 |
14 | const char* http_parser_get_method(struct http_parser *parser);
15 |
16 | const char* http_parser_get_error_name(struct http_parser *parser);
17 |
18 | const char* http_parser_get_error_description(struct http_parser *parser);
19 |
20 | unsigned int http_parser_get_status_code(struct http_parser *parser);
21 |
22 | #endif /* http_parser_accessors_h */
23 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/HTTP/CHTTPParser/http_parser_accessors.c:
--------------------------------------------------------------------------------
1 | //
2 | // http_parser_accessors.c
3 | // SwiftCGI
4 | //
5 | // Created by Todd Bluhm on 9/23/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | #include "http_parser_accessors.h"
10 |
11 | const char* http_parser_get_method(struct http_parser *parser) {
12 | return http_method_str(parser->method);
13 | }
14 |
15 | const char* http_parser_get_error_name(struct http_parser *parser) {
16 | return http_errno_name(parser->http_errno);
17 | }
18 |
19 | const char* http_parser_get_error_description(struct http_parser *parser) {
20 | return http_errno_description(parser->http_errno);
21 | }
22 |
23 | unsigned int http_parser_get_status_code(struct http_parser *parser) {
24 | return parser->status_code;
25 | }
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/Request.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Request.swift
3 | // SwiftCGI
4 | //
5 | // Created by Ian Wagner on 9/26/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum RequestCompletionStatus {
12 | case Complete
13 | }
14 |
15 | public protocol Request {
16 | var cookies: [String: String]? { get set }
17 | var params: RequestParams { get } /// Used to store things like CGI environment variables
18 | var path: String { get }
19 | var streamData: NSMutableData? { get set }
20 | var socket: GCDAsyncSocket? { get set }
21 | var method: HTTPMethod { get }
22 | // TODO: Add in a real type for Request Headers
23 | var headers: [String: String]? { get }
24 |
25 | func finish(status: RequestCompletionStatus)
26 | }
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2015 Ian Wagner. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI.xcodeproj/project.xcworkspace/xcshareddata/SwiftCGI.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "C5F5259C3CDB1C6AC3C26B07B96DBA4125C7E925",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "C5F5259C3CDB1C6AC3C26B07B96DBA4125C7E925" : 0,
8 | "90C3FD88F8002F902A5FDF9D64063244571F233C" : 0
9 | },
10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "E1C7C9AA-0096-4054-B593-3CA46F664E85",
11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
12 | "C5F5259C3CDB1C6AC3C26B07B96DBA4125C7E925" : "SwiftCGI\/",
13 | "90C3FD88F8002F902A5FDF9D64063244571F233C" : "SwiftCGI\/SwiftCGI%20Framework\/Backends\/HTTP\/CHTTPParser\/http-parser\/"
14 | },
15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "SwiftCGI",
16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204,
17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "SwiftCGI Framework\/SwiftCGI.xcodeproj",
18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
19 | {
20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:joyent\/http-parser.git",
21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "90C3FD88F8002F902A5FDF9D64063244571F233C"
23 | },
24 | {
25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/ianthetechie\/SwiftCGI.git",
26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C5F5259C3CDB1C6AC3C26B07B96DBA4125C7E925"
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/HTTP/EmbeddedHTTPRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmbeddedHTTPRequest.swift
3 | // SwiftCGI
4 | //
5 | // Created by Todd Bluhm on 9/23/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | struct EmbeddedHTTPRequest {
13 | var cookies: [String: String]? = [:]
14 | var streamData: NSMutableData?
15 | var socket: GCDAsyncSocket?
16 | let method: HTTPMethod
17 | let path: String
18 | let params: RequestParams
19 | var headers: [String: String]?
20 |
21 | init(pRequest: HTTPParserRequest) {
22 | // Get the Request Method
23 | guard let pRequestMethod = pRequest.method else {
24 | fatalError("HttpParserRequest has no method.")
25 | }
26 |
27 | guard let method = HTTPMethod(rawValue: pRequestMethod) else {
28 | fatalError("Could not parse out the HTTP request method")
29 | }
30 | self.method = method
31 |
32 | // Get the request path
33 | guard let path = pRequest.url else {
34 | fatalError("Can not read path from request")
35 | }
36 | self.path = path
37 |
38 | // Get the request headers
39 | if let headers = pRequest.headers {
40 | self.headers = headers
41 | }
42 |
43 | // TODO: Load cookies
44 |
45 | // Get the Request body if one exits
46 | if let bodyData = pRequest.body?.dataUsingEncoding(NSUTF8StringEncoding) {
47 | streamData = NSMutableData(data: bodyData)
48 | }
49 |
50 | params = [:]
51 | }
52 | }
53 |
54 | extension EmbeddedHTTPRequest: Request {
55 | func finish(status: RequestCompletionStatus) {
56 | switch status {
57 | case .Complete:
58 | socket?.disconnectAfterWriting()
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/Router.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Router.swift
3 | // SwiftCGI
4 | //
5 | // Created by Ian Wagner on 3/1/15.
6 | // Copyright (c) 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public class Router {
12 | private var subrouters: [Router] = []
13 | private let path: String
14 | private let wildcard: Bool
15 | private let handler: RequestHandler?
16 |
17 | public init(path: String, handleWildcardChildren wildcard: Bool, withHandler handler: RequestHandler?) {
18 | self.path = path
19 | self.wildcard = wildcard
20 | self.handler = handler
21 | }
22 |
23 | public func attachRouter(subrouter: Router) {
24 | subrouters.append(subrouter)
25 | }
26 |
27 | public func route(pathToRoute: String) -> RequestHandler? {
28 | // TODO: Seems a bit kludgey... Functional, but kludgey...
29 |
30 | // Ignore stray slashes
31 | let components = (pathToRoute as NSString).pathComponents.filter { return $0 != "/" }
32 |
33 | // if components.count > 0 {
34 | if components.count > 1 {
35 | // Match greedily on sub-routers first
36 | let subPath = Array(components[1..
32 |
33 | //! Project version number for SwiftCGI.
34 | FOUNDATION_EXPORT double SwiftCGIVersionNumber;
35 |
36 | //! Project version string for SwiftCGI.
37 | FOUNDATION_EXPORT const unsigned char SwiftCGIVersionString[];
38 |
39 | // In this header, you should import all the public headers of your framework using statements like #import
40 | #import "GCDAsyncSocket.h"
41 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | SwiftCGI License
2 | Copyright (c) 2014, Ian Wagner
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that thefollowing conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10 |
11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
12 |
13 |
14 | FCGIKit License (FCGIKit was used as a guide for structuring the intial versions of this project; including the license here just to be safe)
15 | Copyright (C) 2011 by Smiling Plants HB
16 |
17 | Permission is hereby granted, free of charge, to any person obtaining a copy
18 | of this software and associated documentation files (the "Software"), to deal
19 | in the Software without restriction, including without limitation the rights
20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 | copies of the Software, and to permit persons to whom the Software is
22 | furnished to do so, subject to the following conditions:
23 |
24 | The above copyright notice and this permission notice shall be included in
25 | all copies or substantial portions of the Software.
26 |
27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33 | THE SOFTWARE.
34 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Core Modules/Sessions/Sessions+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sessions+Extensions.swift
3 | // SwifftCGI Sessions
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | import Foundation
32 |
33 |
34 | let SessionIDCookieName = "sessionid"
35 |
36 | // Extension to implement session handling properties on Request
37 | public extension Request {
38 | public var sessionID: String? { return cookies?[SessionIDCookieName] }
39 |
40 | public mutating func generateSessionID() {
41 | if sessionID == nil {
42 | var newCookies = cookies ?? [:]
43 | newCookies[SessionIDCookieName] = NSUUID().UUIDString
44 | self.cookies = newCookies
45 | } else {
46 | fatalError("Attempted to generate a session ID for a request that already has a session ID")
47 | }
48 | }
49 |
50 | public func getSessionManager() -> RequestSessionManager? {
51 | return RequestSessionManager(request: self)
52 | }
53 | }
54 |
55 |
56 | // Define a handler function to modify the response accordingly
57 | public func sessionMiddlewareHandler(var request: Request, var response: HTTPResponse) -> HTTPResponse {
58 | // Add the session cookie if necessary
59 | if request.sessionID == nil {
60 | request.generateSessionID()
61 | }
62 |
63 | if let sessionID = request.sessionID {
64 | response.setResponseHeader(.SetCookie([SessionIDCookieName: "\(sessionID); Max-Age=86400"]))
65 | }
66 |
67 | return response
68 | }
69 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGITests/HTTPTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPTests.swift
3 | // SwiftCGI
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | import XCTest
32 |
33 | class HTTPTests: XCTestCase {
34 | func testHTTPResponse() {
35 | let status = HTTPStatus.OK
36 | let contentType = HTTPContentType.TextPlain(.UTF8)
37 | let body = "안녕하세요, Swifter!"
38 | let okResponse = HTTPResponse(body: body)
39 |
40 | XCTAssertEqual(okResponse.status, status, "Incorrect default HTTPStatus")
41 | XCTAssertEqual(okResponse.contentType, contentType, "Incorrect default content type")
42 | XCTAssertEqual(okResponse.contentLength, 25, "Incorrect content length computation")
43 | XCTAssertEqual(okResponse.body, body, "The request body is inexplicably different than its initial value")
44 | XCTAssertEqual(okResponse.responseData, "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 25\r\n\r\n\(body)".dataUsingEncoding(NSUTF8StringEncoding), "The request header is not being properly generated")
45 |
46 | let otherOKResponse = HTTPResponse(status: status, contentType: contentType, body: body)
47 | XCTAssertEqual(okResponse.status, otherOKResponse.status, "Incorrect default HTTPStatus")
48 | XCTAssertEqual(okResponse.contentType, otherOKResponse.contentType, "Incorrect default content type")
49 | XCTAssertEqual(okResponse.body, otherOKResponse.body, "The request body is inexplicably different than its initial value")
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/UIntExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIntExtensions.swift
3 | // SwiftCGI
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | // Design note: yes, this is very meta, aliasing an alias, but it is readable.
32 | typealias MSB = UInt8
33 | typealias LSB = UInt8
34 |
35 | extension UInt16 {
36 | func decomposeBigEndian() -> (MSB, LSB) {
37 | let bigEndianValue = CFSwapInt16HostToBig(self)
38 | let msb = UInt8(bigEndianValue & 0xFF)
39 | let lsb = UInt8(bigEndianValue >> 8)
40 |
41 | return (msb, lsb)
42 | }
43 |
44 | func decomposeLittleEndian() -> (LSB, MSB) {
45 | let littleEndianValue = CFSwapInt16HostToLittle(self)
46 | let lsb = UInt8(littleEndianValue & 0xFF)
47 | let msb = UInt8(littleEndianValue >> 8)
48 |
49 | return (lsb, msb)
50 | }
51 | }
52 |
53 | // Does the same decomposition as the above, but for 32-bit UInts
54 | extension UInt32 {
55 | func decomposeBigEndian() -> (MSB, UInt8, UInt8, LSB) {
56 | let bigEndianValue = CFSwapInt32HostToBig(self)
57 | let msb = UInt8(bigEndianValue & 0xFF)
58 | let b1 = UInt8((bigEndianValue >> 8) & 0xFF)
59 | let b2 = UInt8((bigEndianValue >> 16) & 0xFF)
60 | let lsb = UInt8((bigEndianValue >> 24) & 0xFF)
61 |
62 | return (msb, b1, b2, lsb)
63 | }
64 |
65 | func decomposeLittleEndian() -> (LSB, UInt8, UInt8, MSB) {
66 | let littleEndianValue = CFSwapInt32HostToLittle(self)
67 | let lsb = UInt8(littleEndianValue & 0xFF)
68 | let b1 = UInt8((littleEndianValue >> 8) & 0xFF)
69 | let b2 = UInt8((littleEndianValue >> 16) & 0xFF)
70 | let msb = UInt8((littleEndianValue >> 24) & 0xFF)
71 |
72 | return (lsb, b1, b2, msb)
73 | }
74 | }
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/HTTP/EmbeddedHTTPBackend.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmbeddedHTTPBackend.swift
3 | // SwiftCGI
4 | //
5 | // Created by Todd Bluhm on 9/21/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Dictionary where Value : Equatable {
12 | func allKeysForValue(val : Value) -> [Key] {
13 | // return self.filter { $1 == val }.map { $0.0 }
14 | return self.flatMap({ (key, value) -> Key? in
15 | return value == val ? key : nil
16 | })
17 | }
18 | }
19 |
20 | enum RequestProcess: Int {
21 | case Started = 0
22 | case Processing = 1
23 | case Finished = 2
24 | }
25 |
26 | protocol EmbeddedHTTPBackendDelegate {
27 | func finishedRequestConstruction(parser: HTTPParser)
28 | }
29 |
30 | class EmbeddedHTTPBackend {
31 | let readSize: UInt = 65536
32 | let endOfLine: NSData = "\r\n".dataUsingEncoding(NSUTF8StringEncoding)! // TODO: Are we sure we should encode this in UTF-8? What if the response is a different charset?
33 | var delegate: BackendDelegate?
34 | var currentRequests: [GCDAsyncSocket: HTTPParser] = [:]
35 |
36 | init() {}
37 | }
38 |
39 | extension EmbeddedHTTPBackend: Backend {
40 | func processData(sock: GCDAsyncSocket, data: NSData, tag: Int) {
41 | if let parser = currentRequests[sock] {
42 | parser.parseData(data)
43 | continueReadingFromSocket(sock)
44 | }
45 | }
46 |
47 | func startReadingFromSocket(sock: GCDAsyncSocket) {
48 | // Create a new parser and request data storage
49 | let newParser = HTTPParser()
50 | newParser.delegate = self
51 | currentRequests[sock] = newParser
52 | sock.readDataToData(endOfLine, withTimeout: 1000, tag: RequestProcess.Started.rawValue)
53 | }
54 |
55 | func continueReadingFromSocket(sock: GCDAsyncSocket) {
56 | sock.readDataWithTimeout(1000, tag: RequestProcess.Processing.rawValue)
57 | }
58 |
59 | func cleanUp(sock: GCDAsyncSocket) {
60 | currentRequests[sock] = nil
61 | }
62 |
63 | func sendResponse(request: Request, response: HTTPResponse) -> Bool {
64 | guard let sock = request.socket else {
65 | NSLog("ERROR: No socket for request")
66 | return false
67 | }
68 |
69 | guard let data = response.responseData else {
70 | NSLog("No response data")
71 | return true
72 | }
73 |
74 | let remainingData = data.mutableCopy() as! NSMutableData
75 | while remainingData.length > 0 {
76 | let chunk = remainingData.subdataWithRange(NSMakeRange(0, min(remainingData.length, 65535)))
77 | // print(chunk)
78 | sock.writeData(chunk, withTimeout: 1000, tag: 0)
79 |
80 | // Remove the data we just sent from the buffer
81 | remainingData.replaceBytesInRange(NSMakeRange(0, chunk.length), withBytes: nil, length: 0)
82 | }
83 |
84 | return true
85 | }
86 | }
87 |
88 | extension EmbeddedHTTPBackend: EmbeddedHTTPBackendDelegate {
89 | func finishedRequestConstruction(parser: HTTPParser) {
90 | var req = EmbeddedHTTPRequest(pRequest: parser.data)
91 |
92 | // FIXME: What is this abomination?!
93 | guard let sock = currentRequests.allKeysForValue(parser).first else {
94 | fatalError("Could not find associated socket")
95 | }
96 | req.socket = sock
97 | delegate?.finishedParsingRequest(req)
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Core Modules/Sessions/SessionManagement.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionManagement.swift
3 | // SwiftCGI Sessions
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | public typealias SessionID = String
32 | public typealias SessionData = [String: String]
33 |
34 |
35 | // MARK: Protocol that session managers must conform to
36 |
37 | public protocol SessionManager {
38 | static var instance: Self { get }
39 |
40 | func getDataForSessionID(sessionID: SessionID) -> SessionData?
41 | func setData(data: SessionData, forSessionID sessionID: SessionID)
42 | }
43 |
44 |
45 | public class RequestSessionManager {
46 | let sessionID: SessionID
47 | let sessionManager: T
48 |
49 | public init?(request: Request) {
50 | self.sessionManager = T.instance
51 |
52 | if let id = request.sessionID {
53 | sessionID = id
54 | } else {
55 | sessionID = "" // Silly compiler; the initializer failed... *shrug*
56 | return nil
57 | }
58 | }
59 |
60 | public func getData() -> SessionData? {
61 | return sessionManager.getDataForSessionID(sessionID)
62 | }
63 |
64 | public func setData(data: SessionData) {
65 | // TODO: Think about thread safety here
66 | sessionManager.setData(data, forSessionID: sessionID)
67 | }
68 | }
69 |
70 |
71 | // MARK: Can't get much more basic than this; session data is sent to /dev/null
72 |
73 | public final class NilSessionManager: SessionManager {
74 | public static var instance = NilSessionManager()
75 |
76 | public func getDataForSessionID(sessionID: SessionID) -> SessionData? {
77 | return nil
78 | }
79 |
80 | public func setData(data: SessionData, forSessionID sessionID: SessionID) {
81 | // Do nothing
82 | }
83 | }
84 |
85 |
86 | // MARK: Basic, transient in-memory session support; great for quick testing
87 |
88 | public final class TransientMemorySessionManager: SessionManager {
89 | private var sessionData: [String: SessionData] = [:]
90 |
91 | public static var instance = TransientMemorySessionManager()
92 |
93 | public func getDataForSessionID(sessionID: SessionID) -> SessionData? {
94 | return sessionData[sessionID]
95 | }
96 |
97 | public func setData(data: SessionData, forSessionID sessionID: SessionID) {
98 | sessionData[sessionID] = data
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI.xcodeproj/xcshareddata/xcschemes/SwiftCGI.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftCGI [](https://github.com/Carthage/Carthage)
2 |
3 | An object-functional microframework for developing FCGI applications in Swift
4 |
5 | ## About
6 |
7 | Well, the title pretty much says it all. This is a microframework (no,
8 | not the next Django or Rails), and it is written in Swift (well, OK, I
9 | admit, there is a little bit of Objective-C in here because I needed a
10 | quick socket library; a better version is in the works, most likely
11 | wrapping a more established C library).
12 |
13 | ## Why?
14 |
15 | A valid question. Mostly because I was bored one weekend. Also,
16 | because I am passionate about a couple of things. First, I love
17 | Swift. Apple hit the nail on the head. Second, I love lasers, but
18 | that's not important right now. Third, I am a huge supporter of
19 | functional (and similar derived styles) of programming. Fourth, I
20 | think that Swift is going to bring functional programming within reach
21 | of the "normal" programmer who can't convince his boss to let him
22 | rewrite the company intranet in some arcane Haskell
23 | framework. Finally, I hate programming web apps. I did it for way too
24 | many years. It scarred me for life. But I may be convinced to go back
25 | at some point after my JavaScript hangover wears off and I can write
26 | server-side code in a framework that is not bloated and works well
27 | with a functional approach.
28 |
29 | ## Vision
30 |
31 | SwiftCGI will eventually become a mature, modular microframework that
32 | encourages a functional approach to developing web apps in Swift. The
33 | design will be as modular as possible so that "pieces" can be
34 | assembled to form a fuller framework as desired. I envision an ORM,
35 | authentication plugins, etc. will be written over time, but my focus
36 | right now is the core framework. If the modularity part of the design
37 | works out properly, additional functionality will be relatively easy
38 | to hook in.
39 |
40 | ## Current status
41 |
42 | I would currently classify SwiftCGI as late alpha in that it is a
43 | funcitonal base that mostly modular design, is (mostly) unit tested,
44 | but is still somewhat incomplete. It is not ready for prime time yet,
45 | so expect things to change and occasionally break along the road
46 | to 1.0. NOTE: The current server has the same limitations of FCGIKit,
47 | which will be remedied in the future.
48 |
49 | ## Credits
50 |
51 | I'm not sure I'd say this project was "inspired" by
52 | [FCGIKit](https://github.com/fervo/FCGIKit), but the core of this
53 | project started as a port of (and is still heavily influenced by)
54 | FCGIKit. I started off by porting FCGIKit to Swift, and have since
55 | refactored much of it to be more "Swifty."
56 |
57 | # Quick Start
58 |
59 | 1. Clone the demo project from
60 | [ianthetechie/SwiftCGI-Demo](https://github.com/ianthetechie/SwiftCGI-Demo).
61 | 2. Open the SwiftCGI Demo project.
62 | 3. Run the project.
63 | 4. [RECOMMENDED] Configure nginx to serve fcgi from your application
64 | (a full nginx tutorial may come later if I get bored, but for now,
65 | the following nginx.conf snippet should suffice... Put this inside
66 | your server block). Alternatively, you may use the embedded HTTP
67 | server scheme, which starts a (rather expreimental a this point)
68 | local HTTP server, which can be used to get started. I do NOT
69 | recommend this for serious use though. It is still highly
70 | experimental and rather flawed. SwiftCGI is designed, first and
71 | foremost, for FCGI app development.
72 |
73 | ```
74 | location /cgi {
75 | fastcgi_pass localhost:9000;
76 | fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
77 | include fastcgi_params;
78 | }
79 | ```
80 |
81 |
82 | ## License This project is distributed under the terms of the 2-clause BSD license.
83 |
84 | TL;DR - if the use of this product causes the death of
85 | your firstborn, I'm not responsible (it is provided as-is; no warranty,
86 | liability, etc.) and if you redistribute this with or without
87 | modification, you need to credit me and copy the license along with
88 | the code. Otherwise, you can pretty much do what you want ;)
89 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGITests/PrimitiveTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrimitiveTests.swift
3 | // SwiftCGITests
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | import XCTest
32 |
33 | class PrimitiveTests: XCTestCase {
34 | func testUInt16DecomposeBigEndian() {
35 | // Test the big endian byte decomposition methods for UInt16
36 | let (msb, lsb) = UInt16(0xF00F).decomposeBigEndian()
37 | XCTAssertEqual(msb, UInt8(0xF0), "MSB should be 0xF0")
38 | XCTAssertEqual(lsb, UInt8(0x0F), "LSB should be 0x0F")
39 | }
40 |
41 | func testUInt16DecomposeLittleEndian() {
42 | // Test the little endian byte decomposition methods for UInt16
43 | let (lsb, msb) = UInt16(0xF00F).decomposeLittleEndian()
44 | XCTAssertEqual(lsb, UInt8(0x0F), "LSB should be 0x0F")
45 | XCTAssertEqual(msb, UInt8(0xF0), "MSB should be 0xF0")
46 | }
47 |
48 | func testUInt16FromBigEndianData() {
49 | let originalValue = UInt16(0xF00F)
50 | let (msb, lsb) = originalValue.decomposeBigEndian()
51 |
52 | var bytes = [UInt8](count:8, repeatedValue: 0)
53 | bytes[0] = msb
54 | bytes[1] = lsb
55 |
56 | // Throw in the value again later in the block to make sure the index
57 | // argument is working properly
58 | bytes[3] = msb
59 | bytes[4] = lsb
60 |
61 | let bigEndianData = NSData(bytes: &bytes, length: 8)
62 | let parsedValue = readUInt16FromBigEndianData(bigEndianData, atIndex: 0)
63 | XCTAssertEqual(originalValue, parsedValue, "Incorrect result from readUInt16FromBigEndianData")
64 |
65 | let parsedValueWithOffset = readUInt16FromBigEndianData(bigEndianData, atIndex: 3)
66 | XCTAssertEqual(originalValue, parsedValueWithOffset, "Incorrect result from readUInt16FromBigEndianData")
67 |
68 | let parsedValueWithInvalidOffset = readUInt16FromBigEndianData(bigEndianData, atIndex: 2)
69 | XCTAssert(originalValue != parsedValueWithInvalidOffset, "Incorrect result from readUInt16FromBigEndianData")
70 | }
71 |
72 | func testUInt32DecomposeBigEndian() {
73 | // Test the big endian byte decomposition methods for UInt32
74 | let (msb, b1, b2, lsb) = UInt32(0xDEADBEEF).decomposeBigEndian()
75 | XCTAssertEqual(msb, UInt8(0xDE), "MSB should be 0xDE")
76 | XCTAssertEqual(b1, UInt8(0xAD), "B1 should be 0xAD")
77 | XCTAssertEqual(b2, UInt8(0xBE), "B2 should be 0xBE")
78 | XCTAssertEqual(lsb, UInt8(0xEF), "LSB should be 0xEF")
79 | }
80 |
81 | func testUInt32DecomposeLittleEndian() {
82 | // Test the little endian byte decomposition methods for UInt32
83 | let (lsb, b1, b2, msb) = UInt32(0xDEADBEEF).decomposeLittleEndian()
84 | XCTAssertEqual(lsb, UInt8(0xEF), "LSB should be 0xEF")
85 | XCTAssertEqual(b1, UInt8(0xBE), "B1 should be 0xBE")
86 | XCTAssertEqual(b2, UInt8(0xAD), "B2 should be 0xAD")
87 | XCTAssertEqual(msb, UInt8(0xDE), "MSB should be 0xDE")
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #########################
2 | # .gitignore file for Xcode4 and Xcode5 Source projects
3 | #
4 | # Apple bugs, waiting for Apple to fix/respond:
5 | #
6 | # 15564624 - what does the xccheckout file in Xcode5 do? Where's the documentation?
7 | #
8 | # Version 2.3
9 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects
10 | #
11 | # 2014 updates:
12 | # - appended non-standard items DISABLED by default (uncomment if you use those tools)
13 | # - removed the edit that an SO.com moderator made without bothering to ask me
14 | # - researched CocoaPods .lock more carefully, thanks to Gokhan Celiker
15 | # 2013 updates:
16 | # - fixed the broken "save personal Schemes"
17 | # - added line-by-line explanations for EVERYTHING (some were missing)
18 | #
19 | # NB: if you are storing "built" products, this WILL NOT WORK,
20 | # and you should use a different .gitignore (or none at all)
21 | # This file is for SOURCE projects, where there are many extra
22 | # files that we want to exclude
23 | #
24 | #########################
25 |
26 | #####
27 | # OS X temporary files that should never be committed
28 | #
29 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html
30 |
31 | .DS_Store
32 |
33 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html
34 |
35 | .Trashes
36 |
37 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html
38 |
39 | *.swp
40 |
41 | #
42 | # *.lock - this is used and abused by many editors for many different things.
43 | # For the main ones I use (e.g. Eclipse), it should be excluded
44 | # from source-control, but YMMV.
45 | # (lock files are usually local-only file-synchronization on the local FS that should NOT go in git)
46 | # c.f. the "OPTIONAL" section at bottom though, for tool-specific variations!
47 |
48 | *.lock
49 |
50 |
51 | #
52 | # profile - REMOVED temporarily (on double-checking, I can't find it in OS X docs?)
53 | #profile
54 |
55 |
56 | ####
57 | # Xcode temporary files that should never be committed
58 | #
59 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this...
60 |
61 | *~.nib
62 |
63 |
64 | ####
65 | # Xcode build files -
66 | #
67 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData"
68 |
69 | DerivedData/
70 |
71 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build"
72 |
73 | build/
74 |
75 |
76 | #####
77 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups)
78 | #
79 | # This is complicated:
80 | #
81 | # SOMETIMES you need to put this file in version control.
82 | # Apple designed it poorly - if you use "custom executables", they are
83 | # saved in this file.
84 | # 99% of projects do NOT use those, so they do NOT want to version control this file.
85 | # ..but if you're in the 1%, comment out the line "*.pbxuser"
86 |
87 | # .pbxuser: http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html
88 |
89 | *.pbxuser
90 |
91 | # .mode1v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html
92 |
93 | *.mode1v3
94 |
95 | # .mode2v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html
96 |
97 | *.mode2v3
98 |
99 | # .perspectivev3: http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file
100 |
101 | *.perspectivev3
102 |
103 | # NB: also, whitelist the default ones, some projects need to use these
104 | !default.pbxuser
105 | !default.mode1v3
106 | !default.mode2v3
107 | !default.perspectivev3
108 |
109 |
110 | ####
111 | # Xcode 4 - semi-personal settings
112 | #
113 | #
114 | # OPTION 1: ---------------------------------
115 | # throw away ALL personal settings (including custom schemes!
116 | # - unless they are "shared")
117 | #
118 | # NB: this is exclusive with OPTION 2 below
119 | xcuserdata
120 |
121 | # OPTION 2: ---------------------------------
122 | # get rid of ALL personal settings, but KEEP SOME OF THEM
123 | # - NB: you must manually uncomment the bits you want to keep
124 | #
125 | # NB: this *requires* git v1.8.2 or above; you may need to upgrade to latest OS X,
126 | # or manually install git over the top of the OS X version
127 | # NB: this is exclusive with OPTION 1 above
128 | #
129 | #xcuserdata/**/*
130 |
131 | # (requires option 2 above): Personal Schemes
132 | #
133 | #!xcuserdata/**/xcschemes/*
134 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Core Modules/Sessions/Tests/SwifftCGI_SessionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwifftCGI_SessionsTests.swift
3 | // SwifftCGI SessionsTests
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | import XCTest
32 |
33 | class SwifftCGI_SessionsTests: XCTestCase {
34 | let randomSessionID = "LSkjhafdfsiFNDSF97(*^FDfs5FDS&fSusGDFfsdfgjvbcm,"
35 |
36 | func testNilSessionManager() {
37 | let manager = NilSessionManager.instance
38 |
39 | // Verify that we don't have a session yet
40 | XCTAssert(manager.getDataForSessionID(randomSessionID) == nil, "Initial session data should be nil")
41 |
42 | // Verify that we can persist a key
43 | let sessionData: SessionData = ["foo": "bar"]
44 | manager.setData(sessionData, forSessionID: randomSessionID)
45 |
46 | let persistedSessionData = manager.getDataForSessionID(randomSessionID)
47 | XCTAssert(persistedSessionData == nil, "Session data should not stored by NilSessionManager")
48 | }
49 |
50 | func testTransientMemorySessionManager() {
51 | let manager = TransientMemorySessionManager.instance
52 |
53 | // Verify that we don't have a session yet
54 | XCTAssert(manager.getDataForSessionID(randomSessionID) == nil, "Initial session data should be nil")
55 |
56 | // Verify that we can persist a key
57 | let sessionData: SessionData = ["foo": "bar"]
58 | manager.setData(sessionData, forSessionID: randomSessionID)
59 |
60 | let persistedSessionData = manager.getDataForSessionID(randomSessionID)
61 | XCTAssert(persistedSessionData != nil, "Session data was not stored")
62 | XCTAssert(persistedSessionData! == sessionData, "Stored session data does not match")
63 | }
64 |
65 | // TODO: Figure out how to test this... too many private types
66 | func testRequestSessionManager() {
67 | let record = BeginRequestRecord(version: .Version1, requestID: 1, contentLength: 8, paddingLength: 0)
68 | var request = FCGIRequest(record: record)
69 |
70 | let nilManager = RequestSessionManager(request: request)
71 | XCTAssert(nilManager == nil, "Initializer should fail when there is no sessionid parameter")
72 |
73 | request.generateSessionID()
74 |
75 | let manager = RequestSessionManager(request: request)
76 | XCTAssert(manager != nil, "Initializer should no longer fail")
77 |
78 | // Verify that we don't have a session yet
79 | XCTAssert(manager!.getData() == nil, "Initial session data should be nil")
80 |
81 | // Verify that we can persist a key
82 | let sessionData: SessionData = ["foo": "bar"]
83 | manager!.setData(sessionData)
84 |
85 | let persistedSessionData = manager!.getData()
86 | XCTAssert(persistedSessionData != nil, "Session data was not stored")
87 | XCTAssert(persistedSessionData! == sessionData, "Stored session data does not match")
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/Types.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FCGITypes.swift
3 | // SwiftCGI
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | // MARK: Request/response lifecycle
32 |
33 | public typealias RequestParams = [String: String]
34 | public typealias RequestHandler = Request -> HTTPResponse?
35 |
36 |
37 | //
38 | // Preware: Invoked BEFORE the request is handled or a response is generated.
39 | // Preware returns an (optionally modified) FCGIRequest; use preware
40 | // to modify the request before it is handled.
41 | // Middleware: Invoked AFTER the request handler has been called, but before
42 | // the response is sent back to the client. Middleware allows you to
43 | // alter the response (usually by doing things like setting cookies).
44 | // Postware: Invoked AFTER the request has completed and the response has been
45 | // sent to the client.
46 | //
47 |
48 | // TODO: Make these Request instances generic once Swift sees the light and lets us do generic
49 | // typeclasses
50 | public typealias RequestPrewareHandler = Request -> Request
51 | public typealias RequestMiddlewareHandler = (Request, HTTPResponse) -> HTTPResponse
52 | public typealias RequestPostwareHandler = (Request, HTTPResponse?) -> Void
53 |
54 |
55 | // MARK: Low-level stuff
56 |
57 | public typealias FCGIApplicationStatus = UInt32
58 |
59 |
60 | public enum FCGIOutputStream: UInt8 {
61 | case Stdout = 6
62 | case Stderr = 7
63 | }
64 |
65 | public enum FCGIProtocolStatus: UInt8 {
66 | case RequestComplete = 0
67 | case FCGI_CANT_MPX_CONN = 1
68 | case Overloaded = 2
69 | }
70 |
71 |
72 | // MARK: Private types
73 |
74 | typealias FCGIRequestID = UInt16
75 | typealias FCGIContentLength = UInt16
76 | typealias FCGIPaddingLength = UInt8
77 | typealias FCGIShortNameLength = UInt8
78 | typealias FCGILongNameLength = UInt32
79 | typealias FCGIShortValueLength = UInt8
80 | typealias FCGILongValueLength = UInt32
81 |
82 |
83 | enum FCGIVersion: UInt8 {
84 | case Version1 = 1
85 | }
86 |
87 | enum FCGIRequestRole: UInt16 {
88 | case Responder = 1
89 |
90 | // Not implemented
91 | //case Authorizer = 2
92 | //case Filter = 3
93 | }
94 |
95 | enum FCGIRecordKind: UInt8 {
96 | case BeginRequest = 1
97 | case AbortRequest = 2
98 | case EndRequest = 3
99 | case Params = 4
100 | case Stdin = 5
101 | case Stdout = 6
102 | case Stderr = 7
103 | case Data = 8
104 | case GetValues = 9
105 | case GetValuesResult = 10
106 | }
107 |
108 | struct FCGIRequestFlags : OptionSetType, BooleanType {
109 | typealias RawValue = UInt
110 | private var value: UInt = 0
111 | init(_ value: UInt) { self.value = value }
112 | init(rawValue value: UInt) { self.value = value }
113 | init(nilLiteral: ()) { self.value = 0 }
114 | static var allZeros: FCGIRequestFlags { return self.init(0) }
115 | static func fromMask(raw: UInt) -> FCGIRequestFlags { return self.init(raw) }
116 | var rawValue: UInt { return self.value }
117 | var boolValue: Bool { return self.value != 0 }
118 |
119 | static var None: FCGIRequestFlags { return self.init(0) }
120 | static var KeepConnection: FCGIRequestFlags { return FCGIRequestFlags(1 << 0) }
121 | }
122 |
123 | enum FCGISocketTag: Int {
124 | case AwaitingHeaderTag = 0
125 | case AwaitingContentAndPaddingTag
126 | }
127 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/FCGI/FCGIRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Request.swift
3 | // SwiftCGI
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | let FCGIRecordHeaderLength: UInt = 8
32 | let FCGITimeout: NSTimeInterval = 5
33 |
34 | class FCGIRequest {
35 | let record: BeginRequestRecord
36 | let keepConnection: Bool
37 |
38 | var params: RequestParams = [:] // Set externally and never reset to nil thereafter
39 |
40 | private var _cookies: [String: String]?
41 |
42 | var socket: GCDAsyncSocket? // Set externally by the server
43 | var streamData: NSMutableData?
44 | // TODO: actually set the headers
45 | var headers: [String: String]?
46 |
47 | init(record: BeginRequestRecord) {
48 | self.record = record
49 | keepConnection = record.flags?.contains(.KeepConnection) ?? false
50 | }
51 |
52 | // FCGI-specific implementation
53 | private func finishWithProtocolStatus(protocolStatus: FCGIProtocolStatus, andApplicationStatus applicationStatus: FCGIApplicationStatus) -> Bool {
54 | guard let sock = socket else {
55 | NSLog("ERROR: No socket for request")
56 | return false
57 | }
58 |
59 | let outRecord = EndRequestRecord(version: record.version, requestID: record.requestID, paddingLength: 0, protocolStatus: protocolStatus, applicationStatus: applicationStatus)
60 | sock.writeData(outRecord.fcgiPacketData, withTimeout: 5, tag: 0)
61 |
62 | if keepConnection {
63 | sock.readDataToLength(FCGIRecordHeaderLength, withTimeout: FCGITimeout, tag: FCGISocketTag.AwaitingHeaderTag.rawValue)
64 | } else {
65 | sock.disconnectAfterWriting()
66 | }
67 |
68 | return true
69 | }
70 |
71 | }
72 |
73 | extension FCGIRequest: Request {
74 |
75 | var path: String {
76 | guard let uri = params["REQUEST_URI"] else {
77 | fatalError("Encountered request that was missing a path")
78 | }
79 | return uri
80 | }
81 |
82 | func finish(status: RequestCompletionStatus) {
83 | switch status {
84 | case .Complete:
85 | finishWithProtocolStatus(.RequestComplete, andApplicationStatus: 0)
86 | }
87 | }
88 |
89 | var cookies: [String: String]? {
90 | get {
91 | if _cookies == nil {
92 | if let cookieString = params["HTTP_COOKIE"] {
93 | var result: [String: String] = [:]
94 | let cookieDefinitions = cookieString.componentsSeparatedByString("; ")
95 | for cookie in cookieDefinitions {
96 | let cookieDef = cookie.componentsSeparatedByString("=")
97 | result[cookieDef[0]] = cookieDef[1]
98 | }
99 | _cookies = result
100 | }
101 | }
102 | return _cookies
103 | }
104 | set {
105 | _cookies = newValue
106 | }
107 | }
108 |
109 | var method: HTTPMethod {
110 | guard let requestMethod = params["REQUEST_METHOD"] else {
111 | fatalError("No REQUEST_METHOD found in the FCGI request params.")
112 | }
113 |
114 | guard let meth = HTTPMethod(rawValue: requestMethod) else {
115 | fatalError("Invalid HTTP method specified in REQUEST_METHOD.")
116 | }
117 |
118 | return meth
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/HTTPResponseHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPResponseHelpers.swift
3 | // SwiftCGI
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | public struct HTTPResponse {
32 | // MARK: Stored Properties
33 |
34 | public var status: HTTPStatus
35 | public var contentType: HTTPContentType
36 | private var _headers: HTTPHeaderCollection = .Leaf
37 | public var headers: HTTPHeaderCollection {
38 | // Manually set the content length and content type. The latter is
39 | // set as a property at creation time, and the former is computed
40 | // dynamically. Both are obviously non-negotiable.
41 | var finalHeaders = _headers
42 | finalHeaders = setHeader(.ContentType(contentType), collection: finalHeaders)
43 | finalHeaders = setHeader(.ContentLength(contentLength), collection: finalHeaders)
44 |
45 | for header in _headers {
46 | finalHeaders = setHeader(header, collection: finalHeaders)
47 | }
48 |
49 | return finalHeaders
50 | }
51 | public var body: String
52 |
53 |
54 | // MARK: Computed properties
55 |
56 | public var contentLength: Int { return body.utf8.count }
57 | public var responseData: NSData? {
58 | let httpStart = "HTTP/1.1 \(status.rawValue) \(status.description)"
59 |
60 | let serializedHeaders = headers.map({ header in "\(header.key): \(header.headerSerializableValue)" }).joinWithSeparator(HTTPNewline)
61 | let headerString = [httpStart, serializedHeaders].joinWithSeparator(HTTPNewline) + HTTPTerminator
62 |
63 | let responseString = headerString + body
64 | let responseData: NSData?
65 | switch contentType {
66 | case .ApplicationJSON:
67 | fallthrough
68 | case .ImagePNG:
69 | fallthrough
70 | case .ImageJPEG:
71 | fallthrough
72 | case .TextHTML(.UTF8):
73 | fallthrough
74 | case .TextPlain(.UTF8):
75 | responseData = responseString.dataUsingEncoding(NSUTF8StringEncoding)
76 | }
77 |
78 | return responseData
79 | }
80 |
81 |
82 | // MARK: Init
83 |
84 | public init(status: HTTPStatus, contentType: HTTPContentType, body: String) {
85 | self.status = status
86 | self.contentType = contentType
87 | self.body = body
88 | }
89 |
90 | /// Creates an HTTPResponse assuming a `Content-Type` of `text/plain` encoded as UTF-8.
91 | public init(body: String) {
92 | // NOTE: The current Swift compiler doesn't allow convenience initializers
93 | // on structs, so the other initializer is not called
94 | status = .OK
95 | contentType = .TextPlain(.UTF8)
96 | self.body = body
97 | }
98 |
99 |
100 | // MARK: Helpers
101 | public mutating func setResponseHeader(header: HTTPResponseHeader) {
102 | switch header {
103 | case .ContentLength(_):
104 | // This is a fatal error because it is an error in programming
105 | fatalError("Content length cannot be set manually as it is dynamically computed")
106 | case .ContentType(let contentType):
107 | // If they want to set the content type this way, so be it...
108 | self.contentType = contentType
109 | return // IMPORTANT: Early return; no need to set this in the internal collection
110 | default:
111 | break // no more special behaviors
112 | }
113 |
114 | _headers = setHeader(header, collection: _headers)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/HTTPHeaderCollection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPHeaderCollection.swift
3 | // SwiftCGI
4 | //
5 | // Created by Ian Wagner on 1/3/16.
6 | // Copyright © 2016 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // TODO: This should probably be adjusted to be a red-black tree to maintain
12 | // efficiency with large collections constructed in sorted order (which, I
13 | // am guessing will be quite common...)
14 | public enum HTTPHeaderCollection: SequenceType, Equatable {
15 | case Leaf
16 | case Node(Box>, Box, Box>)
17 |
18 | public typealias Generator = HTTPHeaderGenerator
19 | public func generate() -> Generator {
20 | return HTTPHeaderGenerator(collection: self)
21 | }
22 | }
23 |
24 | public func ==(lhs: HTTPHeaderCollection, rhs: HTTPHeaderCollection) -> Bool {
25 | if isLeaf(lhs) && isLeaf(rhs) {
26 | return true
27 | } else {
28 | switch lhs {
29 | case .Leaf:
30 | return false
31 | case .Node(let lhsLeft, let lhsValue, let lhsRight):
32 | switch rhs {
33 | case .Leaf:
34 | return false // The first if statement established that both are not leaves, therefore lhs ≠ rhs
35 | case .Node(let rhsLeft, let rhsValue, let rhsRight):
36 | return lhsValue.unboxedValue == rhsValue.unboxedValue && lhsLeft.unboxedValue == rhsLeft.unboxedValue && lhsRight.unboxedValue == rhsRight.unboxedValue
37 | }
38 | }
39 | }
40 | }
41 |
42 | private func isLeaf(node: HTTPHeaderCollection) -> Bool {
43 | switch node {
44 | case .Leaf:
45 | return true
46 | default:
47 | return false
48 | }
49 | }
50 |
51 | private func isTerminalValue(node: HTTPHeaderCollection) -> Bool {
52 | switch node {
53 | case .Node(let left, _, let right):
54 | switch left.unboxedValue {
55 | case .Leaf:
56 | switch right.unboxedValue {
57 | case .Leaf:
58 | return true // Both left and right are leaves
59 | default:
60 | break
61 | }
62 | default:
63 | break
64 | }
65 | return false
66 | case .Leaf:
67 | return true // leaves are terminal too
68 | }
69 | }
70 |
71 | private func findMinimumNode(collection: HTTPHeaderCollection) -> T? {
72 | switch collection {
73 | case .Leaf:
74 | return nil
75 | case .Node(let left, let value, _):
76 | if isLeaf(left.unboxedValue) {
77 | return value.unboxedValue
78 | } else {
79 | return findMinimumNode(left.unboxedValue)
80 | }
81 | }
82 | }
83 |
84 | public func getHeaderForKey(key: String, collection: HTTPHeaderCollection) -> T? {
85 | switch collection {
86 | case .Leaf:
87 | return nil
88 | case .Node(let left, let boxedValue, let right):
89 | let value = boxedValue.unboxedValue
90 | if value.key == key {
91 | return value
92 | } else if key < value.key {
93 | return getHeaderForKey(key, collection: left.unboxedValue)
94 | } else {
95 | return getHeaderForKey(key, collection: right.unboxedValue)
96 | }
97 | }
98 | }
99 |
100 | public func removeHeaderForKey(key: String, collection: HTTPHeaderCollection) -> HTTPHeaderCollection {
101 | switch collection {
102 | case .Leaf:
103 | return .Leaf
104 | case .Node(let left, let current, let right):
105 | if current.unboxedValue.key == key { // Found the node to remove
106 | if isLeaf(left.unboxedValue) && isLeaf(right.unboxedValue) {
107 | return .Leaf // No children to take care of
108 | } else if isLeaf(left.unboxedValue) {
109 | return right.unboxedValue
110 | } else if isLeaf(right.unboxedValue) {
111 | return left.unboxedValue
112 | } else {
113 | // The complicated case: we have two children that are non-terminal
114 | let minimumNode = findMinimumNode(right.unboxedValue)! // This should never fail to unwrap
115 | return .Node(left, Box(minimumNode), Box(removeHeaderForKey(minimumNode.key, collection: right.unboxedValue)))
116 | }
117 | } else {
118 | return .Node(Box(removeHeaderForKey(key, collection: left.unboxedValue)), current, Box(removeHeaderForKey(key, collection: right.unboxedValue)))
119 | }
120 | }
121 | }
122 |
123 | public func setHeader(header: T, collection: HTTPHeaderCollection) -> HTTPHeaderCollection {
124 | switch collection {
125 | case .Leaf:
126 | // In this case, we have traversed the tree hierarchy, but never encountered
127 | // a node with the correct key before hitting a leaf, so we just insert
128 | // a new node.
129 | return .Node(Box(.Leaf), Box(header), Box(.Leaf))
130 | case .Node(let left, let current, let right):
131 | if current.unboxedValue.key == header.key {
132 | return .Node(left, Box(header), right)
133 | } else if (header.key < current.unboxedValue.key) {
134 | return .Node(Box(setHeader(header, collection: left.unboxedValue)), current, right)
135 | } else {
136 | return .Node(left, current, Box(setHeader(header, collection: right.unboxedValue)))
137 | }
138 | }
139 | }
140 |
141 |
142 | func serializeHeaders(headers: HTTPHeaderCollection) -> String {
143 | return headers.map({ (header) -> String in
144 | return "\(header.key): \(header.headerSerializableValue)"
145 | }).joinWithSeparator(HTTPNewline)
146 | }
147 |
148 | public class HTTPHeaderGenerator: GeneratorType {
149 | public typealias Element = T
150 |
151 | private var collection: HTTPHeaderCollection
152 |
153 | init(collection: HTTPHeaderCollection) {
154 | self.collection = collection
155 | }
156 |
157 | public func next() -> Element? {
158 | switch collection {
159 | case .Leaf:
160 | return nil
161 | case .Node(_, let current, _):
162 | // Remove current from the internal tree, then return current
163 | collection = removeHeaderForKey(current.unboxedValue.key, collection: collection)
164 | return current.unboxedValue
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/HTTP/CHTTPParser/HTTPParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPParser.swift
3 | // SwiftCGI
4 | //
5 | // Created by Todd Bluhm on 9/26/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import CHTTPParser.Parser
10 | import CHTTPParser.Accessors
11 |
12 | struct HTTPParserRequest {
13 | var method: String?
14 | var body: String?
15 | var url: String?
16 | var headers: [String:String]?
17 | var lastHeaderWasValue = true
18 | var isMessageComplete = false
19 | var headerName: String?
20 | var completeMethod: (() -> ())?
21 | }
22 |
23 | func ==(lhs: HTTPParser, rhs: HTTPParser) -> Bool {
24 | return lhs.hashValue == rhs.hashValue
25 | }
26 |
27 | class HTTPParser: Hashable {
28 | var parser = http_parser()
29 | var data = HTTPParserRequest()
30 | var settings: http_parser_settings
31 | var hashValue: Int {
32 | get {
33 | return "\(parser)".hashValue
34 | }
35 | }
36 | var delegate: EmbeddedHTTPBackend?
37 |
38 | init() {
39 | settings = http_parser_settings(
40 | on_message_begin: nil,
41 | on_url: onReceivedUrl,
42 | on_status: nil,
43 | on_header_field: onReceivedHeaderName,
44 | on_header_value: onReceivedHeaderValue,
45 | on_headers_complete: nil,
46 | on_body: onReceivedBody,
47 | on_message_complete: onMessageComplete,
48 | on_chunk_header: nil,
49 | on_chunk_complete: nil
50 | )
51 |
52 | data.completeMethod = requestComplete
53 |
54 | withUnsafeMutablePointer(&data) { (data: UnsafeMutablePointer) -> Void in
55 | parser.data = UnsafeMutablePointer(data)
56 | http_parser_init(&parser, HTTP_REQUEST)
57 | }
58 | }
59 |
60 | func requestComplete () {
61 | delegate?.finishedRequestConstruction(self)
62 | }
63 |
64 | func parseData(data: NSData) {
65 | http_parser_execute(&parser, &settings, UnsafePointer(data.bytes), data.length)
66 | }
67 | }
68 |
69 | func onMessageComplete(parser: UnsafeMutablePointer) -> Int32 {
70 | let dataPtr = UnsafeMutablePointer(parser.memory.data)
71 | dataPtr.memory.isMessageComplete = true
72 | dataPtr.memory.method = String.fromCString(http_parser_get_method(parser))
73 | dataPtr.memory.completeMethod?()
74 | return 0
75 | }
76 |
77 | func onReceivedHeaderName(parser: UnsafeMutablePointer, data: UnsafePointer, length: Int) -> Int32 {
78 | let dataPtr = UnsafeMutablePointer(parser.memory.data)
79 |
80 | if dataPtr.memory.headerName == nil {
81 | dataPtr.memory.headerName = ""
82 | }
83 |
84 | // Lets convert all this const char* stuff to native swift strings
85 | if let headerName = dataPtr.memory.headerName,
86 | let newPiece = String.fromCString(UnsafePointer(data)) {
87 |
88 | // Use length to get the proper substring from the data
89 | let start = newPiece.startIndex
90 | let end = start.advancedBy(length-1)
91 |
92 | // If the last piece of data recieved was a head value then
93 | // this must be the start of a new header
94 | if dataPtr.memory.lastHeaderWasValue {
95 | dataPtr.memory.headerName = newPiece[start...end]
96 | // otherwise lets append on to the previous header value
97 | } else {
98 | dataPtr.memory.headerName = headerName + newPiece[start...end]
99 | }
100 |
101 | dataPtr.memory.lastHeaderWasValue = false
102 | }
103 |
104 | return 0
105 | }
106 |
107 | func onReceivedHeaderValue(parser: UnsafeMutablePointer, data: UnsafePointer, length: Int) -> Int32 {
108 | let dataPtr = UnsafeMutablePointer(parser.memory.data)
109 |
110 | if dataPtr.memory.headers == nil {
111 | dataPtr.memory.headers = [:]
112 | }
113 |
114 | // Lets convert all this const char* stuff to native swift strings
115 | if let headerName = dataPtr.memory.headerName,
116 | let newPiece = String.fromCString(UnsafePointer(data)) {
117 |
118 | // Use length to get the proper substring from the data
119 | let start = newPiece.startIndex
120 | let end = start.advancedBy(length-1)
121 |
122 | // If the last piece of data recieved was not a head value then
123 | // we need to add this data as the start header value
124 | // TODO: Improve the safety of this code; all those forced unwraps look rather evil
125 | if !dataPtr.memory.lastHeaderWasValue {
126 | dataPtr.memory.headers![headerName] = newPiece[start...end]
127 | // otherwise lets append on to the previous header value
128 | } else {
129 | if let partialValue = dataPtr.memory.headers![headerName] {
130 | dataPtr.memory.headers![headerName] = partialValue + newPiece[start...end]
131 | }
132 | }
133 |
134 | dataPtr.memory.lastHeaderWasValue = true
135 | }
136 |
137 |
138 | return 0
139 | }
140 |
141 | func onReceivedUrl(parser: UnsafeMutablePointer, data: UnsafePointer, length: Int) -> Int32 {
142 | let dataPtr = UnsafeMutablePointer(parser.memory.data)
143 |
144 | if dataPtr.memory.url == nil {
145 | dataPtr.memory.url = ""
146 | }
147 |
148 | // Lets convert all this const char* stuff to native swift strings
149 | if let url = dataPtr.memory.url,
150 | let newPiece = String.fromCString(UnsafePointer(data)) {
151 | // Use length to get the proper substring from the data
152 | let start = newPiece.startIndex
153 | let end = start.advancedBy(length-1)
154 |
155 | dataPtr.memory.url = url + newPiece[start...end]
156 | }
157 |
158 | return 0
159 | }
160 |
161 | func onReceivedBody(parser: UnsafeMutablePointer, data: UnsafePointer, length: Int) -> Int32 {
162 | let dataPtr = UnsafeMutablePointer(parser.memory.data)
163 |
164 | if dataPtr.memory.body == nil {
165 | dataPtr.memory.body = ""
166 | }
167 |
168 | // Lets convert all this const char* stuff to native swift strings
169 | if let body = dataPtr.memory.body,
170 | let newPiece = String.fromCString(UnsafePointer(data)) {
171 | let start = newPiece.startIndex
172 | let end = start.advancedBy(length-1)
173 |
174 | dataPtr.memory.body = body + newPiece[start...end]
175 | }
176 |
177 | return 0
178 | }
179 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/Backends/FCGI/FCGIBackend.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FCGIParser.swift
3 | // SwiftCGI
4 | //
5 | // Created by Todd Bluhm on 9/26/15.
6 | // Copyright © 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class FCGIBackend {
12 | private var currentRequests: [String: FCGIRequest] = [:]
13 | private var recordContext: [GCDAsyncSocket: FCGIRecordType] = [:]
14 | private let defaultSendStream = FCGIOutputStream.Stdout
15 | var delegate: BackendDelegate?
16 |
17 | init() {}
18 |
19 | func handleRecord(record: FCGIRecordType, fromSocket socket: GCDAsyncSocket) {
20 | let globalRequestID = "\(record.requestID)-\(socket.connectedPort)"
21 |
22 | // Switch on record.type, since types can be mapped 1:1 to an FCGIRecord
23 | // subclass. This allows for a much cleaner chunk of code than a handful
24 | // of if/else ifs chained together, and allows the compiler to check that
25 | // we have covered all cases
26 | switch record.kind {
27 | case .BeginRequest:
28 | guard let record = record as? BeginRequestRecord else {
29 | fatalError("Invalid record type.")
30 | }
31 |
32 | let request = FCGIRequest(record: record)
33 | request.socket = socket
34 |
35 | currentRequests[globalRequestID] = request
36 |
37 | socket.readDataToLength(FCGIRecordHeaderLength, withTimeout: FCGITimeout, tag: FCGISocketTag.AwaitingHeaderTag.rawValue)
38 | case .Params:
39 | guard let request = currentRequests[globalRequestID] else {
40 | NSLog("WARNING: handleRecord called for invalid requestID.")
41 | return // TODO: Throw an error?
42 | }
43 |
44 | guard let record = record as? ParamsRecord else {
45 | fatalError("Invalid record type with kind .Params.")
46 | }
47 |
48 | if let params = record.params {
49 | // Copy the values into the request params dictionary
50 | for key in params.keys {
51 | request.params[key] = params[key]
52 | }
53 | }
54 |
55 | socket.readDataToLength(FCGIRecordHeaderLength, withTimeout: FCGITimeout, tag: FCGISocketTag.AwaitingHeaderTag.rawValue)
56 | case .Stdin:
57 | guard let request = currentRequests[globalRequestID] else {
58 | NSLog("WARNING: handleRecord called for invalid requestID.")
59 | return
60 | }
61 |
62 | if request.streamData == nil {
63 | request.streamData = NSMutableData(capacity: 65536)
64 | }
65 |
66 | guard let record = record as? ByteStreamRecord else {
67 | fatalError("Invalid record type with kind .Stding.")
68 | }
69 |
70 | if let recordData = record.rawData {
71 | request.streamData!.appendData(recordData) // Safe forced unwrap
72 | } else {
73 | delegate?.finishedParsingRequest(request)
74 | currentRequests.removeValueForKey(globalRequestID)
75 | }
76 | default:
77 | // TODO: Throw proper error here and catch in server
78 | fatalError("ERROR: handleRecord called with an invalid FCGIRecord type")
79 | }
80 | }
81 | }
82 |
83 | extension FCGIBackend: Backend {
84 | func startReadingFromSocket(sock: GCDAsyncSocket) {
85 | sock.readDataToLength(FCGIRecordHeaderLength, withTimeout: FCGITimeout,
86 | tag: FCGISocketTag.AwaitingHeaderTag.rawValue)
87 | }
88 |
89 | func processData(sock: GCDAsyncSocket, data: NSData, tag: Int) {
90 | if let socketTag = FCGISocketTag(rawValue: tag) {
91 | switch socketTag {
92 | case .AwaitingHeaderTag:
93 | // TODO: Clean up this
94 | // Phase 1 of 2 possible phases; first, try to parse the header
95 | if let record = createRecordFromHeaderData(data) {
96 | if record.contentLength == 0 {
97 | // No content; handle the message
98 | handleRecord(record, fromSocket: sock)
99 | } else {
100 | // Read additional content
101 | recordContext[sock] = record
102 | sock.readDataToLength(UInt(record.contentLength) + UInt(record.paddingLength), withTimeout: FCGITimeout, tag: FCGISocketTag.AwaitingContentAndPaddingTag.rawValue)
103 | }
104 | } else {
105 | // TODO: throw here and catch in server
106 | NSLog("ERROR: Unable to construct request record")
107 | sock.disconnect()
108 | }
109 | case .AwaitingContentAndPaddingTag:
110 | if var record = recordContext[sock] {
111 | record.processContentData(data)
112 | handleRecord(record, fromSocket: sock)
113 | } else {
114 | NSLog("ERROR: Case .AwaitingContentAndPaddingTag hit with no context")
115 | }
116 | }
117 | } else {
118 | // TODO: throw here and catch in server
119 | NSLog("ERROR: Unknown socket tag")
120 | sock.disconnect()
121 | }
122 | }
123 |
124 | func cleanUp(sock: GCDAsyncSocket) {
125 | recordContext[sock] = nil
126 | }
127 |
128 | func sendResponse(request: Request, response: HTTPResponse) -> Bool {
129 | guard let req = request as? FCGIRequest else { // TODO: Refactor with typealias?
130 | fatalError("Could not convert request to FCGIRequest")
131 | }
132 |
133 | guard let sock = request.socket else {
134 | NSLog("ERROR: No socket for request")
135 | return false
136 | }
137 |
138 | guard let streamType = FCGIRecordKind(rawValue: defaultSendStream.rawValue) else {
139 | NSLog("ERROR: invalid stream type")
140 | return false
141 | }
142 |
143 | guard let data = response.responseData else {
144 | NSLog("No response data")
145 | return true
146 | }
147 |
148 | if let remainingData = data.mutableCopy() as? NSMutableData {
149 | while remainingData.length > 0 {
150 | let chunk = remainingData.subdataWithRange(NSMakeRange(0, min(remainingData.length, 65535)))
151 | let outRecord = ByteStreamRecord(version: req.record.version, requestID: req.record.requestID, contentLength: UInt16(chunk.length), paddingLength: 0, kind: streamType, rawData: chunk)
152 |
153 | sock.writeData(outRecord.fcgiPacketData, withTimeout: FCGITimeout, tag: 0)
154 |
155 | // Remove the data we just sent from the buffer
156 | remainingData.replaceBytesInRange(NSMakeRange(0, chunk.length), withBytes: nil, length: 0)
157 | }
158 | }
159 |
160 | let termRecord = ByteStreamRecord(version: req.record.version, requestID: req.record.requestID, contentLength: 0, paddingLength: 0, kind: streamType)
161 | sock.writeData(termRecord.fcgiPacketData, withTimeout: FCGITimeout, tag: 0)
162 |
163 | return true
164 | }
165 | }
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/Server.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Server.swift
3 | // SwiftCGI
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 |
32 | // MARK: Main server class
33 |
34 | // NOTE: This class muse inherit from NSObject; otherwise the Obj-C code for
35 | // GCDAsyncSocket will somehow not be able to store a reference to the delegate
36 | // (it will remain nil and no error will be logged).
37 | public class FCGIServer: NSObject {
38 | // MARK: Properties
39 |
40 | public let port: UInt16
41 | public var requestRouter: Router
42 | // public var requestHandler: FCGIRequestHandler
43 |
44 | public var http404Handler: RequestHandler = { (request) -> HTTPResponse? in
45 | return HTTPResponse(status: .NotFound, contentType: .TextPlain(.UTF8), body: "HTTP 404 - This isn't the page you're looking for...")
46 | }
47 |
48 | private let delegateQueue: dispatch_queue_t
49 | private lazy var listener: GCDAsyncSocket = {
50 | GCDAsyncSocket(delegate: self, delegateQueue: self.delegateQueue)
51 | }()
52 |
53 |
54 | private var activeSockets: Set = []
55 |
56 | private var registeredPreware: [RequestPrewareHandler] = []
57 | private var registeredMiddleware: [RequestMiddlewareHandler] = []
58 | private var registeredPostware: [RequestPostwareHandler] = []
59 | private let backend: Backend
60 | private let useEmbeddedHTTPServer: Bool
61 |
62 | // MARK: Init
63 |
64 | public init(port: UInt16, requestRouter: Router, useEmbeddedHTTPServer: Bool = true) {
65 | self.useEmbeddedHTTPServer = useEmbeddedHTTPServer
66 |
67 | if useEmbeddedHTTPServer {
68 | backend = EmbeddedHTTPBackend()
69 | } else {
70 | backend = FCGIBackend()
71 | }
72 |
73 | self.port = port
74 | self.requestRouter = requestRouter
75 |
76 | delegateQueue = dispatch_queue_create("SocketAcceptQueue", DISPATCH_QUEUE_SERIAL)
77 | super.init()
78 |
79 | backend.delegate = self
80 | }
81 |
82 |
83 | // MARK: Instance methods
84 |
85 | public func start() throws {
86 | try listener.acceptOnPort(port)
87 | }
88 |
89 | // MARK: Pre/middle/postware registration
90 |
91 | public func registerPrewareHandler(handler: RequestPrewareHandler) -> Void {
92 | registeredPreware.append(handler)
93 | }
94 |
95 | public func registerMiddlewareHandler(handler: RequestMiddlewareHandler) -> Void {
96 | registeredMiddleware.append(handler)
97 | }
98 |
99 | public func registerPostwareHandler(handler: RequestPostwareHandler) -> Void {
100 | registeredPostware.append(handler)
101 | }
102 | }
103 |
104 | extension FCGIServer: GCDAsyncSocketDelegate {
105 | public func socket(sock: GCDAsyncSocket!, didAcceptNewSocket newSocket: GCDAsyncSocket!) {
106 | activeSockets.insert(newSocket)
107 |
108 | // Looks like a leak, but isn't.
109 | let acceptedSocketQueue = dispatch_queue_create("SocketAcceptQueue-\(newSocket.connectedPort)", DISPATCH_QUEUE_SERIAL)
110 | newSocket.delegateQueue = acceptedSocketQueue
111 |
112 | // Tell the backend we have started reading from a new socket
113 | backend.startReadingFromSocket(newSocket)
114 | }
115 |
116 | public func socketDidDisconnect(sock: GCDAsyncSocket?, withError err: NSError!) {
117 | if let sock = sock {
118 | backend.cleanUp(sock)
119 | activeSockets.remove(sock)
120 | } else {
121 | NSLog("WARNING: nil sock disconnect")
122 | }
123 | }
124 |
125 | public func socket(sock: GCDAsyncSocket!, didReadData data: NSData!, withTag tag: Int) {
126 | // Tell the backend the socket has send us some data, please process it
127 | backend.processData(sock, data: data, tag: tag)
128 | }
129 | }
130 |
131 | extension FCGIServer: BackendDelegate {
132 | // When our backend has parsed a full request, it sends it here for processing
133 | func finishedParsingRequest(request: Request) {
134 | var req = request
135 |
136 | // TODO: Future - when Swift gets exception handling, wrap this
137 | for handler in registeredPreware {
138 | req = handler(request) // Because we can't correctly force compiler type checking without generic typealiases
139 | }
140 |
141 | if let requestHandler = requestRouter.route(req.path) {
142 | if var response = requestHandler(req) {
143 | for handler in registeredMiddleware {
144 | response = handler(req, response)
145 | }
146 |
147 | backend.sendResponse(req, response: response)
148 |
149 | for handler in registeredPostware {
150 | handler(req, response)
151 | }
152 |
153 | req.finish(.Complete)
154 | } else {
155 | // TODO: Figure out how this should be handled
156 |
157 | // backend.sendResponse(req, response: response)
158 |
159 | for handler in registeredPostware {
160 | handler(req, nil)
161 | }
162 |
163 | req.finish(.Complete)
164 | }
165 | } else {
166 | // Invoke the overrideable HTTP 404 handler
167 | if let response = http404Handler(req) {
168 | backend.sendResponse(req, response: response)
169 |
170 | for handler in registeredPostware {
171 | handler(req, response)
172 | }
173 | } else {
174 | // Invoke postware anyways, but with a nil argument
175 | for handler in registeredPostware {
176 | handler(req, nil)
177 | }
178 | }
179 |
180 | req.finish(.Complete)
181 | }
182 |
183 | if let sock = request.socket {
184 | backend.cleanUp(sock)
185 | }
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI/HTTPTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPTypes.swift
3 | // SwiftCGI
4 | //
5 | // Created by Ian Wagner on 1/7/15.
6 | // Copyright (c) 2015 Ian Wagner. All rights reserved.
7 | //
8 |
9 | // MARK: Utility definitions
10 |
11 | let HTTPNewline = "\r\n"
12 | let HTTPTerminator = "\r\n\r\n"
13 |
14 |
15 | // MARK: Simple enumerations
16 |
17 | public enum HTTPRangeUnit: String {
18 | case Bytes = "bytes"
19 | case None = "none"
20 | }
21 |
22 | public enum HTTPMethod: String {
23 | case OPTIONS = "OPTIONS"
24 | case GET = "GET"
25 | case HEAD = "HEAD"
26 | case POST = "POST"
27 | case PUT = "PUT"
28 | case DELETE = "DELETE"
29 | case TRACE = "TRACE"
30 | case CONNECT = "CONNECT"
31 | }
32 |
33 | public enum HTTPCacheControlResponse {
34 | case Public
35 | case Private
36 | case NoCache
37 | case NoStore
38 | case NoTransform
39 | case MustRevalidate
40 | case ProxyRevalidate
41 | case MaxAge(Int)
42 | case SMaxAge(Int)
43 | case CacheExtension
44 | }
45 |
46 | extension HTTPCacheControlResponse: HTTPHeaderSerializable {
47 | public var headerSerializableValue: String {
48 | switch self {
49 | case .Public:
50 | return "public"
51 | case .Private: // TODO: optional field name
52 | return "private"
53 | case .NoCache: // TODO: optional field name
54 | return "no-cache"
55 | case .NoStore:
56 | return "no-store"
57 | case .NoTransform:
58 | return "no-transform"
59 | case .MustRevalidate:
60 | return "must-revalidate"
61 | case .ProxyRevalidate:
62 | return "proxy-revalidate"
63 | case .MaxAge(let seconds):
64 | return "max-age = \(seconds)"
65 | case .SMaxAge(let seconds):
66 | return "s-maxage = \(seconds)"
67 | case .CacheExtension:
68 | return "cache-extension"
69 | }
70 | }
71 | }
72 |
73 | public enum Charset: String {
74 | case UTF8 = "utf-8"
75 | }
76 |
77 |
78 | /// Types conforming to this protocol may be interpolated safely into an HTTP header line.
79 | public protocol HTTPHeaderSerializable {
80 | var headerSerializableValue: String { get }
81 | }
82 |
83 |
84 | // MARK: Status codes
85 |
86 | public typealias HTTPStatusCode = Int
87 | public enum HTTPStatus: HTTPStatusCode {
88 | case OK = 200
89 | case Created = 201
90 | case Accepted = 202
91 |
92 | case MovedPermanently = 301
93 | case SeeOther = 303
94 | case NotModified = 304
95 | case TemporaryRedirect = 307
96 |
97 | case BadRequest = 400
98 | case Unauthorized = 401
99 | case Forbidden = 403
100 | case NotFound = 404
101 | case MethodNotAllowed = 405
102 | case NotAcceptable = 406
103 |
104 | case InternalServerError = 500
105 | case NotImplemented = 501
106 | case ServiceUnavailable = 503
107 |
108 | var description: String {
109 | switch self {
110 | case .OK: return "OK"
111 | case .Created: return "Created"
112 | case .Accepted: return "Accepted"
113 |
114 | case .MovedPermanently: return "Moved Permanently"
115 | case .SeeOther: return "See Other"
116 | case .NotModified: return "Not Modified"
117 | case .TemporaryRedirect: return "Temporary Redirect"
118 |
119 | case .BadRequest: return "Bad Request"
120 | case .Unauthorized: return "Unauthorized"
121 | case .Forbidden: return "Forbidden"
122 | case .NotFound: return "Not Found"
123 | case .MethodNotAllowed: return "Method Not Allowed"
124 | case .NotAcceptable: return "Not Acceptable"
125 |
126 | case .InternalServerError: return "Internal Server Error"
127 | case .NotImplemented: return "Not Implemented"
128 | case .ServiceUnavailable: return "Service Unavailable"
129 | }
130 | }
131 | }
132 |
133 |
134 | // MARK: HTTP headers
135 |
136 | // TODO: Unit test everything below this line
137 | public protocol HTTPHeader: Equatable, HTTPHeaderSerializable {
138 | var key: String { get }
139 | }
140 |
141 | public func ==(lhs: T, rhs: T) -> Bool {
142 | return lhs.key == rhs.key && lhs.headerSerializableValue == rhs.headerSerializableValue
143 | }
144 |
145 |
146 | // TODO: Finish adding all of the HTTP response headers
147 | public enum HTTPResponseHeader: HTTPHeader {
148 | case AccessControlAllowOrigin(String)
149 | case AcceptPatch(HTTPContentType)
150 | case AcceptRanges(HTTPRangeUnit)
151 | case Age(Int)
152 | case Allow(HTTPMethod)
153 | case CacheControl(HTTPCacheControlResponse)
154 | case ContentLength(Int)
155 | case ContentType(HTTPContentType)
156 | case SetCookie([String: String])
157 |
158 | public var key: String {
159 | switch self {
160 | case .AccessControlAllowOrigin(_): return "Access-Control-Allow-Origin"
161 | case .AcceptPatch(_): return "Accept-Patch"
162 | case .AcceptRanges(_): return "Accept-Ranges"
163 | case .Age(_): return "Age"
164 | case .Allow(_): return "Allow"
165 | case .CacheControl(_): return "Cache-Control"
166 | case .ContentLength(_): return "Content-Length"
167 | case .ContentType(_): return "Content-Type"
168 | case .SetCookie(_): return "Set-Cookie"
169 | }
170 | }
171 |
172 | public var headerSerializableValue: String {
173 | switch self {
174 | case .AccessControlAllowOrigin(let value): return value
175 | case .AcceptPatch(let value): return value.headerSerializableValue
176 | case .AcceptRanges(let value): return value.rawValue
177 | case .Age(let value): return String(value)
178 | case .Allow(let value): return value.rawValue
179 | case .CacheControl(let value): return value.headerSerializableValue
180 | case .ContentLength(let length): return String(length)
181 | case .ContentType(let type): return type.headerSerializableValue
182 | case .SetCookie(let cookies): return cookies.map({ (key, value) in "\(key)=\(value)" }).joinWithSeparator("\(HTTPNewline)\(self.key): ")
183 | }
184 | }
185 | }
186 |
187 |
188 | // Note to future self and anyone else reading this code: additional types of generated data should
189 | // be included here. If someone (including my future self) thinks of a good reason to include types
190 | // such as video/mp4 that are typically used for static files, then there should be a VERY good use
191 | // case for it. Web application frameworks are designed for dynamic response generation, not serving
192 | // static files. nginx is perfectly good at that already. Notable exceptions to the "no static file
193 | // types" rule are images, which have many valid dynamic generation use cases (QR codes, barcodes,
194 | // transformations on uploaded files, etc).
195 | public enum HTTPContentType: Equatable {
196 | case TextHTML(Charset)
197 | case TextPlain(Charset)
198 | case ApplicationJSON
199 | case ImagePNG
200 | case ImageJPEG
201 | }
202 |
203 | extension HTTPContentType: HTTPHeaderSerializable {
204 | public var headerSerializableValue: String {
205 | switch self {
206 | case .TextHTML(let charset): return "text/html; charset=\(charset.rawValue)"
207 | case .TextPlain(let charset): return "text/plain; charset=\(charset.rawValue)"
208 | case .ApplicationJSON: return "application/json"
209 | case .ImagePNG: return "image/png"
210 | case .ImageJPEG: return "image/jpeg"
211 | }
212 | }
213 | }
214 |
215 | public func ==(a: HTTPContentType, b: HTTPContentType) -> Bool {
216 | switch (a, b) {
217 | case (.TextHTML(let x), .TextHTML(let y)) where x == y: return true
218 | case (.TextPlain(let x), .TextPlain(let y)) where x == y: return true
219 | case (.ApplicationJSON, .ApplicationJSON): return true
220 | case (.ImagePNG, .ImagePNG): return true
221 | case (.ImageJPEG, .ImageJPEG): return true
222 | default: return false
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGITests/RecordTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordTests.swift
3 | // SwiftCGI
4 | //
5 | // Copyright (c) 2014, Ian Wagner
6 | // All rights reserved.
7 | //
8 | // Redistribution and use in source and binary forms, with or without
9 | // modification, are permitted provided that thefollowing conditions are met:
10 | //
11 | // 1. Redistributions of source code must retain the above copyright notice,
12 | // this list of conditions and the following disclaimer.
13 | //
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 | // POSSIBILITY OF SUCH DAMAGE.
29 | //
30 |
31 | import XCTest
32 |
33 | class RecordTests: XCTestCase {
34 | func verifyBasicInfoAndHeaderForRecord(record: FCGIRecordType) {
35 | let fcgiData = record.fcgiPacketData
36 |
37 | // Get the data as raw bytes for ease of verification
38 | var bytes = [UInt8](count: 8, repeatedValue: 0)
39 | fcgiData.getBytes(&bytes, length: 8)
40 |
41 |
42 | // Verify the header bytes
43 | XCTAssertEqual(bytes[0], FCGIVersion.Version1.rawValue, "Incorrect FCGI version")
44 | XCTAssertEqual(bytes[1], record.kind.rawValue, "Incorrect type")
45 |
46 | let (requestIDMSB, requestIDLSB) = record.requestID.decomposeBigEndian()
47 | XCTAssertEqual(bytes[2], requestIDMSB, "Incorrect request ID MSB")
48 | XCTAssertEqual(bytes[3], requestIDLSB, "Incorrect request ID LSB")
49 |
50 | let (contentLengthMSB, contentLengthLSB) = record.contentLength.decomposeBigEndian()
51 | XCTAssertEqual(bytes[4], contentLengthMSB, "Incorrect content length MSB")
52 | XCTAssertEqual(bytes[5], contentLengthLSB, "Incorrect content length LSB")
53 |
54 | XCTAssertEqual(bytes[6], record.paddingLength, "Incorrect padding length")
55 | XCTAssertEqual(bytes[7], UInt8(0), "Incorrect reserved byte")
56 | }
57 |
58 | func testBeginRequestRecord() {
59 | var record = BeginRequestRecord(version: .Version1, requestID: 1, contentLength: 8, paddingLength: 0)
60 | verifyBasicInfoAndHeaderForRecord(record)
61 | XCTAssertEqual(record.kind, FCGIRecordKind.BeginRequest, "Incorrect record type")
62 |
63 | XCTAssertEqual(record.fcgiPacketData.length, 8, "Incorrect packet data length")
64 |
65 | // Verify that the processContentData function works correctly
66 | let role = FCGIRequestRole.Responder
67 | let (roleMSB, roleLSB) = role.rawValue.decomposeBigEndian()
68 | let flags = FCGIRequestFlags.KeepConnection
69 |
70 | var extraBytes = [UInt8](count: 8, repeatedValue: 0)
71 | extraBytes[0] = roleMSB
72 | extraBytes[1] = roleLSB
73 | extraBytes[2] = UInt8(flags.rawValue) // Flag to keep the connection active
74 |
75 | let extraData = NSData(bytes: &extraBytes, length: 8)
76 | record.processContentData(extraData)
77 |
78 | XCTAssertEqual(record.role, role, "Incorrect role parsed from record data")
79 | XCTAssertEqual(record.flags, flags, "Incorrect flags parsed")
80 | }
81 |
82 | func testEndRequestRecord() {
83 | let record = EndRequestRecord(version: .Version1, requestID: 1, paddingLength: 0, protocolStatus: .RequestComplete, applicationStatus: 0)
84 |
85 | verifyBasicInfoAndHeaderForRecord(record)
86 | XCTAssertEqual(record.kind, FCGIRecordKind.EndRequest, "Incorrect record type")
87 |
88 | let fcgiData = record.fcgiPacketData
89 | XCTAssertEqual(fcgiData.length, 16, "Incorrect packet data length")
90 |
91 | // Get the data as raw bytes for ease of verification
92 | var bytes = [UInt8](count: 16, repeatedValue: 0)
93 | fcgiData.getBytes(&bytes, length: 16)
94 |
95 | // Verify the extra block of data after the header
96 | XCTAssertEqual(bytes[8], UInt8(0), "Incorrect application status byte 1")
97 | XCTAssertEqual(bytes[9], UInt8(0), "Incorrect application status byte 2")
98 | XCTAssertEqual(bytes[10], UInt8(0), "Incorrect application status byte 3")
99 | XCTAssertEqual(bytes[11], UInt8(0), "Incorrect application status byte 4")
100 |
101 | XCTAssertEqual(bytes[12], record.protocolStatus.rawValue, "Incorrect protocol status")
102 | XCTAssertEqual(bytes[13], UInt8(0), "Incorrect reserved byte")
103 | XCTAssertEqual(bytes[14], UInt8(0), "Incorrect reserved byte")
104 | XCTAssertEqual(bytes[15], UInt8(0), "Incorrect reserved byte")
105 | }
106 |
107 | func testByteStreamRecord() {
108 | var record = ByteStreamRecord(version: .Version1, requestID: 1, contentLength: 0, paddingLength: 0)
109 |
110 | verifyBasicInfoAndHeaderForRecord(record)
111 | XCTAssertEqual(record.kind, FCGIRecordKind.Stdin, "Incorrect record type")
112 |
113 | XCTAssertEqual(record.fcgiPacketData.length, 8, "Incorrect packet data length (before processContentData)")
114 |
115 | // Generate some random data
116 | let numBytes = 16
117 | var bytes = [UInt8](count: numBytes, repeatedValue: 0)
118 | for i in 0..> 8) // LSB
59 |
60 | bytes[6] = paddingLength
61 |
62 | return NSData(bytes: &bytes, length: 8)
63 | }
64 | }
65 |
66 | // Begin request record
67 | struct BeginRequestRecord: FCGIRecordType {
68 | let version: FCGIVersion
69 | let requestID: FCGIRequestID
70 |
71 | private(set) var contentLength: FCGIContentLength
72 | let paddingLength: FCGIPaddingLength
73 |
74 | private(set) var role: FCGIRequestRole?
75 | private(set) var flags: FCGIRequestFlags?
76 |
77 | let kind: FCGIRecordKind = .BeginRequest
78 |
79 | mutating func processContentData(data: NSData) {
80 | let rawRole = readUInt16FromBigEndianData(data, atIndex: 0)
81 |
82 | if let concreteRole = FCGIRequestRole(rawValue: rawRole) {
83 | role = concreteRole
84 |
85 | var rawFlags: FCGIRequestFlags.RawValue = 0
86 | data.getBytes(&rawFlags, range: NSMakeRange(2, 1))
87 | flags = FCGIRequestFlags(rawValue: rawFlags)
88 | }
89 | }
90 |
91 | init(version: FCGIVersion, requestID: FCGIRequestID, contentLength: FCGIContentLength, paddingLength: FCGIPaddingLength) {
92 | self.version = version
93 | self.requestID = requestID
94 | self.contentLength = contentLength
95 | self.paddingLength = paddingLength
96 | }
97 | }
98 |
99 | // End request record
100 | struct EndRequestRecord: FCGIRecordType {
101 | let version: FCGIVersion
102 | let requestID: FCGIRequestID
103 |
104 | let contentLength: FCGIContentLength = 8 // Fixed value for this type
105 | let paddingLength: FCGIPaddingLength
106 |
107 | let applicationStatus: FCGIApplicationStatus
108 | let protocolStatus: FCGIProtocolStatus
109 |
110 | let kind: FCGIRecordKind = .EndRequest
111 |
112 | init(version: FCGIVersion, requestID: FCGIRequestID, paddingLength: FCGIPaddingLength, protocolStatus: FCGIProtocolStatus, applicationStatus: FCGIApplicationStatus) {
113 | self.version = version
114 | self.requestID = requestID
115 | self.paddingLength = paddingLength
116 |
117 | self.applicationStatus = applicationStatus
118 | self.protocolStatus = protocolStatus
119 | }
120 |
121 | var fcgiPacketData: NSData {
122 | // Note: because of the cast to the protocol type, this invokes the protocol extension method
123 | // implementation, and does not cause an infinite loop.
124 | let result = (self as FCGIRecordType).fcgiPacketData.mutableCopy() as! NSMutableData
125 |
126 | var extraBytes = [UInt8](count: 8, repeatedValue: 0)
127 |
128 | let (msb, b1, b2, lsb) = applicationStatus.decomposeBigEndian()
129 | extraBytes[0] = msb
130 | extraBytes[1] = b1
131 | extraBytes[2] = b2
132 | extraBytes[3] = lsb
133 |
134 | extraBytes[4] = protocolStatus.rawValue
135 |
136 | result.appendData(NSData(bytes: &extraBytes, length: 8))
137 |
138 | return result
139 | }
140 |
141 | func processContentData(data: NSData) {
142 | fatalError("Error: Attempted to call processContentData on an EndRequestRecord.")
143 | }
144 | }
145 |
146 | // Data record
147 | struct ByteStreamRecord: FCGIRecordType {
148 | let version: FCGIVersion
149 | let requestID: FCGIRequestID
150 |
151 | private let _initContentLength: FCGIContentLength
152 | var contentLength: FCGIContentLength {
153 | return FCGIContentLength(rawData?.length ?? 0)
154 | }
155 |
156 | let paddingLength: FCGIPaddingLength
157 |
158 | private(set) var rawData: NSData?
159 |
160 | private(set) var kind: FCGIRecordKind = .Stdin {
161 | willSet {
162 | switch newValue {
163 | case .Stdin, .Stdout, .Stderr:
164 | break // OK
165 | default:
166 | fatalError("ByteStreamRecord.type can only be .Stdin .Stdout or .Stderr")
167 | }
168 | }
169 | }
170 |
171 | var fcgiPacketData: NSData {
172 | // Note: because of the cast to the protocol type, this invokes the protocol extension method
173 | // implementation, and does not cause an infinite loop.
174 | let result = (self as FCGIRecordType).fcgiPacketData.mutableCopy() as! NSMutableData
175 | if let data = rawData {
176 | result.appendData(data)
177 | }
178 | return result
179 | }
180 |
181 | mutating func processContentData(data: NSData) {
182 | rawData = data.subdataWithRange(NSMakeRange(0, Int(_initContentLength)))
183 | }
184 |
185 | init(version: FCGIVersion, requestID: FCGIRequestID, contentLength: FCGIContentLength, paddingLength: FCGIPaddingLength, kind: FCGIRecordKind = .Stdin, rawData: NSData? = nil) {
186 | self.version = version
187 | self.requestID = requestID
188 | self._initContentLength = contentLength
189 | self.paddingLength = paddingLength
190 | self.kind = kind
191 | self.rawData = rawData
192 |
193 |
194 | }
195 | }
196 |
197 | // Params record
198 | struct ParamsRecord: FCGIRecordType {
199 | let version: FCGIVersion
200 | let requestID: FCGIRequestID
201 |
202 | private(set) var contentLength: FCGIContentLength
203 | let paddingLength: FCGIPaddingLength
204 |
205 | private(set) var params: RequestParams?
206 |
207 | let kind: FCGIRecordKind = .Params
208 |
209 | mutating func processContentData(data: NSData) {
210 | var paramData: [String: String] = [:]
211 |
212 | //Remove Padding
213 | let unpaddedData = data.subdataWithRange(NSMakeRange(0, Int(contentLength))).mutableCopy() as! NSMutableData
214 | while unpaddedData.length > 0 {
215 | var pos0 = 0, pos1 = 0, pos4 = 0
216 |
217 | var keyLengthB3 = 0, keyLengthB2 = 0, keyLengthB1 = 0, keyLengthB0 = 0
218 |
219 | var valueLengthB3 = 0, valueLengthB2 = 0, valueLengthB1 = 0, valueLengthB0 = 0
220 |
221 | var keyLength = 0, valueLength = 0
222 |
223 | unpaddedData.getBytes(&pos0, range: NSMakeRange(0, 1))
224 | unpaddedData.getBytes(&pos1, range: NSMakeRange(1, 1))
225 | unpaddedData.getBytes(&pos4, range: NSMakeRange(4, 1))
226 |
227 | if pos0 >> 7 == 0 {
228 | keyLength = pos0
229 | // NameValuePair11 or 14
230 | if pos1 >> 7 == 0 {
231 | //NameValuePair11
232 | valueLength = pos1
233 | unpaddedData.replaceBytesInRange(NSMakeRange(0,2), withBytes: nil, length: 0)
234 | } else {
235 | //NameValuePair14
236 | unpaddedData.getBytes(&valueLengthB3, range: NSMakeRange(1, 1))
237 | unpaddedData.getBytes(&valueLengthB2, range: NSMakeRange(2, 1))
238 | unpaddedData.getBytes(&valueLengthB1, range: NSMakeRange(3, 1))
239 | unpaddedData.getBytes(&valueLengthB0, range: NSMakeRange(4, 1))
240 |
241 | valueLength = ((valueLengthB3 & 0x7f) << 24) + (valueLengthB2 << 16) + (valueLengthB1 << 8) + valueLengthB0
242 | unpaddedData.replaceBytesInRange(NSMakeRange(0,5), withBytes: nil, length: 0)
243 | }
244 | } else {
245 | // NameValuePair41 or 44
246 | unpaddedData.getBytes(&keyLengthB3, range: NSMakeRange(0, 1))
247 | unpaddedData.getBytes(&keyLengthB2, range: NSMakeRange(1, 1))
248 | unpaddedData.getBytes(&keyLengthB1, range: NSMakeRange(2, 1))
249 | unpaddedData.getBytes(&keyLengthB0, range: NSMakeRange(3, 1))
250 | keyLength = ((keyLengthB3 & 0x7f) << 24) + (keyLengthB2 << 16) + (keyLengthB1 << 8) + keyLengthB0
251 |
252 | if (pos4 >> 7 == 0) {
253 | //NameValuePair41
254 | valueLength = pos4
255 | unpaddedData.replaceBytesInRange(NSMakeRange(0,5), withBytes: nil, length: 0)
256 | } else {
257 | //NameValuePair44
258 | unpaddedData.getBytes(&valueLengthB3, range: NSMakeRange(4, 1))
259 | unpaddedData.getBytes(&valueLengthB2, range: NSMakeRange(5, 1))
260 | unpaddedData.getBytes(&valueLengthB1, range: NSMakeRange(6, 1))
261 | unpaddedData.getBytes(&valueLengthB0, range: NSMakeRange(7, 1))
262 | valueLength = ((valueLengthB3 & 0x7f) << 24) + (valueLengthB2 << 16) + (valueLengthB1 << 8) + valueLengthB0
263 | unpaddedData.replaceBytesInRange(NSMakeRange(0,8), withBytes: nil, length: 0)
264 |
265 | }
266 | }
267 |
268 | let key = NSString(data: unpaddedData.subdataWithRange(NSMakeRange(0,keyLength)), encoding: NSUTF8StringEncoding)
269 | unpaddedData.replaceBytesInRange(NSMakeRange(0,keyLength), withBytes: nil, length: 0)
270 |
271 | let value = NSString(data: unpaddedData.subdataWithRange(NSMakeRange(0,valueLength)), encoding: NSUTF8StringEncoding)
272 | unpaddedData.replaceBytesInRange(NSMakeRange(0,valueLength), withBytes: nil, length: 0)
273 |
274 | if let key = key as? String, value = value as? String {
275 | paramData[key] = value
276 | } else {
277 | fatalError("Unable to decode key or value from content") // non-decodable value
278 | }
279 | }
280 |
281 | if paramData.count > 0 {
282 | params = paramData
283 | } else {
284 | params = nil
285 | }
286 | }
287 |
288 | init(version: FCGIVersion, requestID: FCGIRequestID, contentLength: FCGIContentLength, paddingLength: FCGIPaddingLength) {
289 | self.version = version
290 | self.requestID = requestID
291 | self.contentLength = contentLength
292 | self.paddingLength = paddingLength
293 | }
294 | }
295 |
296 |
297 | // MARK: Helper functions
298 |
299 | func readUInt16FromBigEndianData(data: NSData, atIndex index: Int) -> UInt16 {
300 | var bigUInt16: UInt16 = 0
301 | data.getBytes(&bigUInt16, range: NSMakeRange(index, 2))
302 | return CFSwapInt16BigToHost(bigUInt16)
303 | }
304 |
305 | func createRecordFromHeaderData(data: NSData) -> FCGIRecordType? {
306 | // Check the length of the data
307 | if data.length == Int(FCGIRecordHeaderLength) {
308 | // Parse the version number
309 | var rawVersion: FCGIVersion.RawValue = 0
310 | data.getBytes(&rawVersion, range: NSMakeRange(0, 1))
311 |
312 | if let version = FCGIVersion(rawValue: rawVersion) {
313 | // Check the version
314 | switch version {
315 | case .Version1:
316 | // Parse the request type
317 | var rawType: FCGIRecordKind.RawValue = 0
318 | data.getBytes(&rawType, range: NSMakeRange(1, 1))
319 |
320 | if let type = FCGIRecordKind(rawValue: rawType) {
321 | // Parse the request ID
322 | let requestID = readUInt16FromBigEndianData(data, atIndex: 2)
323 |
324 | // Parse the content length
325 | let contentLength = readUInt16FromBigEndianData(data, atIndex: 4)
326 |
327 | // Parse the padding length
328 | var paddingLength: FCGIPaddingLength = 0
329 | data.getBytes(&paddingLength, range: NSMakeRange(6, 1))
330 |
331 | switch type {
332 | case .BeginRequest:
333 | return BeginRequestRecord(version: version, requestID: requestID, contentLength: contentLength, paddingLength: paddingLength)
334 | case .Params:
335 | return ParamsRecord(version: version, requestID: requestID, contentLength: contentLength, paddingLength: paddingLength)
336 | case .Stdin:
337 | return ByteStreamRecord(version: version, requestID: requestID, contentLength: contentLength, paddingLength: paddingLength)
338 | default:
339 | return nil
340 | }
341 | }
342 | }
343 | }
344 | }
345 |
346 | return nil
347 | }
348 |
--------------------------------------------------------------------------------
/SwiftCGI Framework/SwiftCGI.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 68D438DD1BB7B14C009984DB /* BackendProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C01BB7B14C009984DB /* BackendProtocol.swift */; };
11 | 68D438DE1BB7B14C009984DB /* FCGIBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C21BB7B14C009984DB /* FCGIBackend.swift */; };
12 | 68D438DF1BB7B14C009984DB /* FCGIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C31BB7B14C009984DB /* FCGIRequest.swift */; };
13 | 68D438E01BB7B14C009984DB /* Record.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C41BB7B14C009984DB /* Record.swift */; };
14 | 68D438E81BB7B14C009984DB /* http_parser.c in Sources */ = {isa = PBXBuildFile; fileRef = 68D438D01BB7B14C009984DB /* http_parser.c */; };
15 | 68D438EA1BB7B14C009984DB /* http_parser.h in Headers */ = {isa = PBXBuildFile; fileRef = 68D438D21BB7B14C009984DB /* http_parser.h */; };
16 | 68D438EF1BB7B14C009984DB /* http_parser_accessors.c in Sources */ = {isa = PBXBuildFile; fileRef = 68D438D71BB7B14C009984DB /* http_parser_accessors.c */; };
17 | 68D438F01BB7B14C009984DB /* http_parser_accessors.h in Headers */ = {isa = PBXBuildFile; fileRef = 68D438D81BB7B14C009984DB /* http_parser_accessors.h */; };
18 | 68D438F11BB7B14C009984DB /* HTTPParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438D91BB7B14C009984DB /* HTTPParser.swift */; };
19 | 68D438F21BB7B14C009984DB /* EmbeddedHTTPBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438DB1BB7B14C009984DB /* EmbeddedHTTPBackend.swift */; };
20 | 68D438F31BB7B14C009984DB /* EmbeddedHTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438DC1BB7B14C009984DB /* EmbeddedHTTPRequest.swift */; };
21 | CE052E3A1A5CBAAB00F8DA34 /* UIntExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE052E391A5CBAAB00F8DA34 /* UIntExtensions.swift */; };
22 | CE052E3B1A5CBE1B00F8DA34 /* UIntExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE052E391A5CBAAB00F8DA34 /* UIntExtensions.swift */; };
23 | CE052E3C1A5CC17100F8DA34 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEBFD711A5A20590014D017 /* Types.swift */; };
24 | CE052E401A5CF85500F8DA34 /* HTTPResponseHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE052E3F1A5CF85500F8DA34 /* HTTPResponseHelpers.swift */; };
25 | CE052E411A5CF85500F8DA34 /* HTTPResponseHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE052E3F1A5CF85500F8DA34 /* HTTPResponseHelpers.swift */; };
26 | CE052E431A5CFE0B00F8DA34 /* HTTPTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE052E421A5CFE0B00F8DA34 /* HTTPTypes.swift */; };
27 | CE052E441A5CFE0B00F8DA34 /* HTTPTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE052E421A5CFE0B00F8DA34 /* HTTPTypes.swift */; };
28 | CE57390D1A65179200AB02B8 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE57390C1A65179200AB02B8 /* Util.swift */; };
29 | CE57390E1A65197B00AB02B8 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE57390C1A65179200AB02B8 /* Util.swift */; };
30 | CE7DFA7F1A5B6AE200221B93 /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7DFA7E1A5B6AE200221B93 /* Server.swift */; };
31 | CED98B291A5E58090077A4EC /* RecordTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED98B281A5E58090077A4EC /* RecordTests.swift */; };
32 | CED98B2B1A5E58CD0077A4EC /* HTTPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED98B2A1A5E58CD0077A4EC /* HTTPTests.swift */; };
33 | CEEBFD311A5A1D420014D017 /* SwiftCGI.h in Headers */ = {isa = PBXBuildFile; fileRef = CEEBFD301A5A1D420014D017 /* SwiftCGI.h */; settings = {ATTRIBUTES = (Public, ); }; };
34 | CEEBFD371A5A1D430014D017 /* SwiftCGI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEBFD2B1A5A1D420014D017 /* SwiftCGI.framework */; };
35 | CEEBFD3E1A5A1D430014D017 /* PrimitiveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEBFD3D1A5A1D430014D017 /* PrimitiveTests.swift */; };
36 | CEEBFD721A5A20590014D017 /* Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEBFD711A5A20590014D017 /* Types.swift */; };
37 | CEEBFD881A5A591F0014D017 /* GCDAsyncSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = CEEBFD861A5A591F0014D017 /* GCDAsyncSocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
38 | CEEBFD891A5A591F0014D017 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = CEEBFD871A5A591F0014D017 /* GCDAsyncSocket.m */; };
39 | E902EF9B1C437B9B0022770D /* SwifftCGI_SessionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91242381C002BBA00688157 /* SwifftCGI_SessionsTests.swift */; };
40 | E912423A1C002E8F00688157 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = CEEBFD871A5A591F0014D017 /* GCDAsyncSocket.m */; };
41 | E912423B1C002EE200688157 /* Record.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C41BB7B14C009984DB /* Record.swift */; };
42 | E912423C1C002EFF00688157 /* FCGIBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C21BB7B14C009984DB /* FCGIBackend.swift */; };
43 | E912423E1C002F1500688157 /* BackendProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C01BB7B14C009984DB /* BackendProtocol.swift */; };
44 | E91242471C00560100688157 /* FCGIRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68D438C31BB7B14C009984DB /* FCGIRequest.swift */; };
45 | E91242481C00561000688157 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993D6121BB73B12007C3489 /* Request.swift */; };
46 | E91242491C00567100688157 /* Sessions+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91242331C002AD400688157 /* Sessions+Extensions.swift */; };
47 | E912424A1C00567500688157 /* SessionManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91242341C002AD400688157 /* SessionManagement.swift */; };
48 | E97331C71C02F6DB00ABD30C /* RouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97331C61C02F6DB00ABD30C /* RouterTests.swift */; };
49 | E98F69411C02D91600E27F3F /* SessionManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91242341C002AD400688157 /* SessionManagement.swift */; };
50 | E98F69421C02D91C00E27F3F /* Sessions+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E91242331C002AD400688157 /* Sessions+Extensions.swift */; };
51 | E993D6131BB73B12007C3489 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = E993D6121BB73B12007C3489 /* Request.swift */; };
52 | E99E5FDF1AA2D8B600BED31D /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99E5FDE1AA2D8B600BED31D /* Router.swift */; };
53 | E99E5FE01AA2D8B600BED31D /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99E5FDE1AA2D8B600BED31D /* Router.swift */; };
54 | E9E403711C39E99F00503224 /* HTTPHeaderCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E403701C39E99F00503224 /* HTTPHeaderCollection.swift */; };
55 | E9E403721C39E99F00503224 /* HTTPHeaderCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E403701C39E99F00503224 /* HTTPHeaderCollection.swift */; };
56 | /* End PBXBuildFile section */
57 |
58 | /* Begin PBXContainerItemProxy section */
59 | CEEBFD381A5A1D430014D017 /* PBXContainerItemProxy */ = {
60 | isa = PBXContainerItemProxy;
61 | containerPortal = CEEBFD221A5A1D420014D017 /* Project object */;
62 | proxyType = 1;
63 | remoteGlobalIDString = CEEBFD2A1A5A1D420014D017;
64 | remoteInfo = SwiftCGI;
65 | };
66 | /* End PBXContainerItemProxy section */
67 |
68 | /* Begin PBXFileReference section */
69 | 68D438C01BB7B14C009984DB /* BackendProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackendProtocol.swift; sourceTree = ""; };
70 | 68D438C21BB7B14C009984DB /* FCGIBackend.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FCGIBackend.swift; sourceTree = ""; };
71 | 68D438C31BB7B14C009984DB /* FCGIRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FCGIRequest.swift; sourceTree = ""; };
72 | 68D438C41BB7B14C009984DB /* Record.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Record.swift; sourceTree = ""; };
73 | 68D438D01BB7B14C009984DB /* http_parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = http_parser.c; path = "http-parser/http_parser.c"; sourceTree = ""; };
74 | 68D438D21BB7B14C009984DB /* http_parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = http_parser.h; path = "http-parser/http_parser.h"; sourceTree = ""; };
75 | 68D438D71BB7B14C009984DB /* http_parser_accessors.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = http_parser_accessors.c; sourceTree = ""; };
76 | 68D438D81BB7B14C009984DB /* http_parser_accessors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = http_parser_accessors.h; sourceTree = ""; };
77 | 68D438D91BB7B14C009984DB /* HTTPParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPParser.swift; sourceTree = ""; };
78 | 68D438DA1BB7B14C009984DB /* module.map */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.map; sourceTree = ""; };
79 | 68D438DB1BB7B14C009984DB /* EmbeddedHTTPBackend.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedHTTPBackend.swift; sourceTree = ""; };
80 | 68D438DC1BB7B14C009984DB /* EmbeddedHTTPRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedHTTPRequest.swift; sourceTree = ""; };
81 | CE052E391A5CBAAB00F8DA34 /* UIntExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIntExtensions.swift; sourceTree = ""; };
82 | CE052E3F1A5CF85500F8DA34 /* HTTPResponseHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPResponseHelpers.swift; sourceTree = ""; };
83 | CE052E421A5CFE0B00F8DA34 /* HTTPTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPTypes.swift; sourceTree = ""; };
84 | CE57390C1A65179200AB02B8 /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; };
85 | CE7DFA7E1A5B6AE200221B93 /* Server.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = ""; };
86 | CED98B281A5E58090077A4EC /* RecordTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordTests.swift; sourceTree = ""; };
87 | CED98B2A1A5E58CD0077A4EC /* HTTPTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPTests.swift; sourceTree = ""; };
88 | CEEBFD2B1A5A1D420014D017 /* SwiftCGI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftCGI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
89 | CEEBFD2F1A5A1D420014D017 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
90 | CEEBFD301A5A1D420014D017 /* SwiftCGI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftCGI.h; sourceTree = ""; };
91 | CEEBFD361A5A1D430014D017 /* SwiftCGITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftCGITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
92 | CEEBFD3C1A5A1D430014D017 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
93 | CEEBFD3D1A5A1D430014D017 /* PrimitiveTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimitiveTests.swift; sourceTree = ""; };
94 | CEEBFD711A5A20590014D017 /* Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Types.swift; sourceTree = ""; };
95 | CEEBFD7B1A5A58BF0014D017 /* SwiftCGITests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftCGITests-Bridging-Header.h"; sourceTree = ""; };
96 | CEEBFD861A5A591F0014D017 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = ""; };
97 | CEEBFD871A5A591F0014D017 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = ""; };
98 | E91242331C002AD400688157 /* Sessions+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sessions+Extensions.swift"; sourceTree = ""; };
99 | E91242341C002AD400688157 /* SessionManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionManagement.swift; sourceTree = ""; };
100 | E91242381C002BBA00688157 /* SwifftCGI_SessionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwifftCGI_SessionsTests.swift; sourceTree = ""; };
101 | E97331C61C02F6DB00ABD30C /* RouterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterTests.swift; sourceTree = ""; };
102 | E993D6121BB73B12007C3489 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; };
103 | E99E5FDE1AA2D8B600BED31D /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; };
104 | E9E403701C39E99F00503224 /* HTTPHeaderCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPHeaderCollection.swift; sourceTree = ""; };
105 | /* End PBXFileReference section */
106 |
107 | /* Begin PBXFrameworksBuildPhase section */
108 | CEEBFD271A5A1D420014D017 /* Frameworks */ = {
109 | isa = PBXFrameworksBuildPhase;
110 | buildActionMask = 2147483647;
111 | files = (
112 | );
113 | runOnlyForDeploymentPostprocessing = 0;
114 | };
115 | CEEBFD331A5A1D430014D017 /* Frameworks */ = {
116 | isa = PBXFrameworksBuildPhase;
117 | buildActionMask = 2147483647;
118 | files = (
119 | CEEBFD371A5A1D430014D017 /* SwiftCGI.framework in Frameworks */,
120 | );
121 | runOnlyForDeploymentPostprocessing = 0;
122 | };
123 | /* End PBXFrameworksBuildPhase section */
124 |
125 | /* Begin PBXGroup section */
126 | 68D438BF1BB7B14C009984DB /* Backends */ = {
127 | isa = PBXGroup;
128 | children = (
129 | 68D438C01BB7B14C009984DB /* BackendProtocol.swift */,
130 | 68D438C11BB7B14C009984DB /* FCGI */,
131 | 68D438C51BB7B14C009984DB /* Embedded HTTP Server */,
132 | );
133 | path = Backends;
134 | sourceTree = "";
135 | };
136 | 68D438C11BB7B14C009984DB /* FCGI */ = {
137 | isa = PBXGroup;
138 | children = (
139 | 68D438C21BB7B14C009984DB /* FCGIBackend.swift */,
140 | 68D438C31BB7B14C009984DB /* FCGIRequest.swift */,
141 | 68D438C41BB7B14C009984DB /* Record.swift */,
142 | );
143 | path = FCGI;
144 | sourceTree = "";
145 | };
146 | 68D438C51BB7B14C009984DB /* Embedded HTTP Server */ = {
147 | isa = PBXGroup;
148 | children = (
149 | 68D438C61BB7B14C009984DB /* CHTTPParser */,
150 | 68D438DB1BB7B14C009984DB /* EmbeddedHTTPBackend.swift */,
151 | 68D438DC1BB7B14C009984DB /* EmbeddedHTTPRequest.swift */,
152 | );
153 | name = "Embedded HTTP Server";
154 | path = HTTP;
155 | sourceTree = "";
156 | };
157 | 68D438C61BB7B14C009984DB /* CHTTPParser */ = {
158 | isa = PBXGroup;
159 | children = (
160 | 68D438D01BB7B14C009984DB /* http_parser.c */,
161 | 68D438D21BB7B14C009984DB /* http_parser.h */,
162 | 68D438D71BB7B14C009984DB /* http_parser_accessors.c */,
163 | 68D438D81BB7B14C009984DB /* http_parser_accessors.h */,
164 | 68D438D91BB7B14C009984DB /* HTTPParser.swift */,
165 | 68D438DA1BB7B14C009984DB /* module.map */,
166 | );
167 | path = CHTTPParser;
168 | sourceTree = "";
169 | };
170 | CEEBFD211A5A1D420014D017 = {
171 | isa = PBXGroup;
172 | children = (
173 | 68D438BF1BB7B14C009984DB /* Backends */,
174 | CEEBFD2D1A5A1D420014D017 /* SwiftCGI */,
175 | E91242311C002AD400688157 /* Core Modules */,
176 | CEEBFD7A1A5A58850014D017 /* Other Sources */,
177 | CEEBFD2C1A5A1D420014D017 /* Products */,
178 | );
179 | sourceTree = "";
180 | };
181 | CEEBFD2C1A5A1D420014D017 /* Products */ = {
182 | isa = PBXGroup;
183 | children = (
184 | CEEBFD2B1A5A1D420014D017 /* SwiftCGI.framework */,
185 | CEEBFD361A5A1D430014D017 /* SwiftCGITests.xctest */,
186 | );
187 | name = Products;
188 | sourceTree = "";
189 | };
190 | CEEBFD2D1A5A1D420014D017 /* SwiftCGI */ = {
191 | isa = PBXGroup;
192 | children = (
193 | CEEBFD301A5A1D420014D017 /* SwiftCGI.h */,
194 | CE052E391A5CBAAB00F8DA34 /* UIntExtensions.swift */,
195 | CEEBFD711A5A20590014D017 /* Types.swift */,
196 | E993D6121BB73B12007C3489 /* Request.swift */,
197 | CE7DFA7E1A5B6AE200221B93 /* Server.swift */,
198 | CE052E421A5CFE0B00F8DA34 /* HTTPTypes.swift */,
199 | E9E403701C39E99F00503224 /* HTTPHeaderCollection.swift */,
200 | CE052E3F1A5CF85500F8DA34 /* HTTPResponseHelpers.swift */,
201 | CE57390C1A65179200AB02B8 /* Util.swift */,
202 | E99E5FDE1AA2D8B600BED31D /* Router.swift */,
203 | CEEBFD3A1A5A1D430014D017 /* Tests */,
204 | CEEBFD2E1A5A1D420014D017 /* Supporting Files */,
205 | );
206 | path = SwiftCGI;
207 | sourceTree = "";
208 | };
209 | CEEBFD2E1A5A1D420014D017 /* Supporting Files */ = {
210 | isa = PBXGroup;
211 | children = (
212 | CEEBFD2F1A5A1D420014D017 /* Info.plist */,
213 | );
214 | name = "Supporting Files";
215 | sourceTree = "";
216 | };
217 | CEEBFD3A1A5A1D430014D017 /* Tests */ = {
218 | isa = PBXGroup;
219 | children = (
220 | CEEBFD3D1A5A1D430014D017 /* PrimitiveTests.swift */,
221 | CED98B281A5E58090077A4EC /* RecordTests.swift */,
222 | CED98B2A1A5E58CD0077A4EC /* HTTPTests.swift */,
223 | E97331C61C02F6DB00ABD30C /* RouterTests.swift */,
224 | CEEBFD3B1A5A1D430014D017 /* Supporting Files */,
225 | );
226 | name = Tests;
227 | path = SwiftCGITests;
228 | sourceTree = SOURCE_ROOT;
229 | };
230 | CEEBFD3B1A5A1D430014D017 /* Supporting Files */ = {
231 | isa = PBXGroup;
232 | children = (
233 | CEEBFD3C1A5A1D430014D017 /* Info.plist */,
234 | );
235 | name = "Supporting Files";
236 | sourceTree = "";
237 | };
238 | CEEBFD7A1A5A58850014D017 /* Other Sources */ = {
239 | isa = PBXGroup;
240 | children = (
241 | CEEBFD861A5A591F0014D017 /* GCDAsyncSocket.h */,
242 | CEEBFD871A5A591F0014D017 /* GCDAsyncSocket.m */,
243 | CEEBFD7B1A5A58BF0014D017 /* SwiftCGITests-Bridging-Header.h */,
244 | );
245 | name = "Other Sources";
246 | sourceTree = "";
247 | };
248 | E91242311C002AD400688157 /* Core Modules */ = {
249 | isa = PBXGroup;
250 | children = (
251 | E91242321C002AD400688157 /* Sessions */,
252 | );
253 | path = "Core Modules";
254 | sourceTree = "";
255 | };
256 | E91242321C002AD400688157 /* Sessions */ = {
257 | isa = PBXGroup;
258 | children = (
259 | E91242331C002AD400688157 /* Sessions+Extensions.swift */,
260 | E91242341C002AD400688157 /* SessionManagement.swift */,
261 | E91242371C002BBA00688157 /* Tests */,
262 | );
263 | path = Sessions;
264 | sourceTree = "";
265 | };
266 | E91242371C002BBA00688157 /* Tests */ = {
267 | isa = PBXGroup;
268 | children = (
269 | E91242381C002BBA00688157 /* SwifftCGI_SessionsTests.swift */,
270 | );
271 | path = Tests;
272 | sourceTree = "";
273 | };
274 | /* End PBXGroup section */
275 |
276 | /* Begin PBXHeadersBuildPhase section */
277 | CEEBFD281A5A1D420014D017 /* Headers */ = {
278 | isa = PBXHeadersBuildPhase;
279 | buildActionMask = 2147483647;
280 | files = (
281 | CEEBFD311A5A1D420014D017 /* SwiftCGI.h in Headers */,
282 | 68D438EA1BB7B14C009984DB /* http_parser.h in Headers */,
283 | 68D438F01BB7B14C009984DB /* http_parser_accessors.h in Headers */,
284 | CEEBFD881A5A591F0014D017 /* GCDAsyncSocket.h in Headers */,
285 | );
286 | runOnlyForDeploymentPostprocessing = 0;
287 | };
288 | /* End PBXHeadersBuildPhase section */
289 |
290 | /* Begin PBXNativeTarget section */
291 | CEEBFD2A1A5A1D420014D017 /* SwiftCGI */ = {
292 | isa = PBXNativeTarget;
293 | buildConfigurationList = CEEBFD411A5A1D430014D017 /* Build configuration list for PBXNativeTarget "SwiftCGI" */;
294 | buildPhases = (
295 | CEEBFD261A5A1D420014D017 /* Sources */,
296 | CEEBFD271A5A1D420014D017 /* Frameworks */,
297 | CEEBFD281A5A1D420014D017 /* Headers */,
298 | CEEBFD291A5A1D420014D017 /* Resources */,
299 | );
300 | buildRules = (
301 | );
302 | dependencies = (
303 | );
304 | name = SwiftCGI;
305 | productName = SwiftCGI;
306 | productReference = CEEBFD2B1A5A1D420014D017 /* SwiftCGI.framework */;
307 | productType = "com.apple.product-type.framework";
308 | };
309 | CEEBFD351A5A1D430014D017 /* SwiftCGITests */ = {
310 | isa = PBXNativeTarget;
311 | buildConfigurationList = CEEBFD441A5A1D430014D017 /* Build configuration list for PBXNativeTarget "SwiftCGITests" */;
312 | buildPhases = (
313 | CEEBFD321A5A1D430014D017 /* Sources */,
314 | CEEBFD331A5A1D430014D017 /* Frameworks */,
315 | CEEBFD341A5A1D430014D017 /* Resources */,
316 | );
317 | buildRules = (
318 | );
319 | dependencies = (
320 | CEEBFD391A5A1D430014D017 /* PBXTargetDependency */,
321 | );
322 | name = SwiftCGITests;
323 | productName = SwiftCGITests;
324 | productReference = CEEBFD361A5A1D430014D017 /* SwiftCGITests.xctest */;
325 | productType = "com.apple.product-type.bundle.unit-test";
326 | };
327 | /* End PBXNativeTarget section */
328 |
329 | /* Begin PBXProject section */
330 | CEEBFD221A5A1D420014D017 /* Project object */ = {
331 | isa = PBXProject;
332 | attributes = {
333 | LastSwiftMigration = 0700;
334 | LastSwiftUpdateCheck = 0700;
335 | LastUpgradeCheck = 0710;
336 | ORGANIZATIONNAME = "Ian Wagner";
337 | TargetAttributes = {
338 | CEEBFD2A1A5A1D420014D017 = {
339 | CreatedOnToolsVersion = 6.1.1;
340 | };
341 | CEEBFD351A5A1D430014D017 = {
342 | CreatedOnToolsVersion = 6.1.1;
343 | };
344 | };
345 | };
346 | buildConfigurationList = CEEBFD251A5A1D420014D017 /* Build configuration list for PBXProject "SwiftCGI" */;
347 | compatibilityVersion = "Xcode 3.2";
348 | developmentRegion = English;
349 | hasScannedForEncodings = 0;
350 | knownRegions = (
351 | en,
352 | );
353 | mainGroup = CEEBFD211A5A1D420014D017;
354 | productRefGroup = CEEBFD2C1A5A1D420014D017 /* Products */;
355 | projectDirPath = "";
356 | projectRoot = "";
357 | targets = (
358 | CEEBFD2A1A5A1D420014D017 /* SwiftCGI */,
359 | CEEBFD351A5A1D430014D017 /* SwiftCGITests */,
360 | );
361 | };
362 | /* End PBXProject section */
363 |
364 | /* Begin PBXResourcesBuildPhase section */
365 | CEEBFD291A5A1D420014D017 /* Resources */ = {
366 | isa = PBXResourcesBuildPhase;
367 | buildActionMask = 2147483647;
368 | files = (
369 | );
370 | runOnlyForDeploymentPostprocessing = 0;
371 | };
372 | CEEBFD341A5A1D430014D017 /* Resources */ = {
373 | isa = PBXResourcesBuildPhase;
374 | buildActionMask = 2147483647;
375 | files = (
376 | );
377 | runOnlyForDeploymentPostprocessing = 0;
378 | };
379 | /* End PBXResourcesBuildPhase section */
380 |
381 | /* Begin PBXSourcesBuildPhase section */
382 | CEEBFD261A5A1D420014D017 /* Sources */ = {
383 | isa = PBXSourcesBuildPhase;
384 | buildActionMask = 2147483647;
385 | files = (
386 | 68D438F21BB7B14C009984DB /* EmbeddedHTTPBackend.swift in Sources */,
387 | CE7DFA7F1A5B6AE200221B93 /* Server.swift in Sources */,
388 | CE052E401A5CF85500F8DA34 /* HTTPResponseHelpers.swift in Sources */,
389 | 68D438DF1BB7B14C009984DB /* FCGIRequest.swift in Sources */,
390 | 68D438E81BB7B14C009984DB /* http_parser.c in Sources */,
391 | E98F69411C02D91600E27F3F /* SessionManagement.swift in Sources */,
392 | E98F69421C02D91C00E27F3F /* Sessions+Extensions.swift in Sources */,
393 | E9E403711C39E99F00503224 /* HTTPHeaderCollection.swift in Sources */,
394 | E99E5FDF1AA2D8B600BED31D /* Router.swift in Sources */,
395 | 68D438EF1BB7B14C009984DB /* http_parser_accessors.c in Sources */,
396 | CEEBFD891A5A591F0014D017 /* GCDAsyncSocket.m in Sources */,
397 | 68D438DD1BB7B14C009984DB /* BackendProtocol.swift in Sources */,
398 | E993D6131BB73B12007C3489 /* Request.swift in Sources */,
399 | 68D438E01BB7B14C009984DB /* Record.swift in Sources */,
400 | CEEBFD721A5A20590014D017 /* Types.swift in Sources */,
401 | 68D438F11BB7B14C009984DB /* HTTPParser.swift in Sources */,
402 | CE052E3A1A5CBAAB00F8DA34 /* UIntExtensions.swift in Sources */,
403 | CE57390D1A65179200AB02B8 /* Util.swift in Sources */,
404 | 68D438F31BB7B14C009984DB /* EmbeddedHTTPRequest.swift in Sources */,
405 | CE052E431A5CFE0B00F8DA34 /* HTTPTypes.swift in Sources */,
406 | 68D438DE1BB7B14C009984DB /* FCGIBackend.swift in Sources */,
407 | );
408 | runOnlyForDeploymentPostprocessing = 0;
409 | };
410 | CEEBFD321A5A1D430014D017 /* Sources */ = {
411 | isa = PBXSourcesBuildPhase;
412 | buildActionMask = 2147483647;
413 | files = (
414 | E912423A1C002E8F00688157 /* GCDAsyncSocket.m in Sources */,
415 | E91242481C00561000688157 /* Request.swift in Sources */,
416 | E99E5FE01AA2D8B600BED31D /* Router.swift in Sources */,
417 | E912423B1C002EE200688157 /* Record.swift in Sources */,
418 | CE57390E1A65197B00AB02B8 /* Util.swift in Sources */,
419 | CEEBFD3E1A5A1D430014D017 /* PrimitiveTests.swift in Sources */,
420 | CE052E411A5CF85500F8DA34 /* HTTPResponseHelpers.swift in Sources */,
421 | CE052E3B1A5CBE1B00F8DA34 /* UIntExtensions.swift in Sources */,
422 | E97331C71C02F6DB00ABD30C /* RouterTests.swift in Sources */,
423 | E9E403721C39E99F00503224 /* HTTPHeaderCollection.swift in Sources */,
424 | E91242471C00560100688157 /* FCGIRequest.swift in Sources */,
425 | CE052E441A5CFE0B00F8DA34 /* HTTPTypes.swift in Sources */,
426 | CED98B291A5E58090077A4EC /* RecordTests.swift in Sources */,
427 | CE052E3C1A5CC17100F8DA34 /* Types.swift in Sources */,
428 | E912423C1C002EFF00688157 /* FCGIBackend.swift in Sources */,
429 | E902EF9B1C437B9B0022770D /* SwifftCGI_SessionsTests.swift in Sources */,
430 | CED98B2B1A5E58CD0077A4EC /* HTTPTests.swift in Sources */,
431 | E912424A1C00567500688157 /* SessionManagement.swift in Sources */,
432 | E91242491C00567100688157 /* Sessions+Extensions.swift in Sources */,
433 | E912423E1C002F1500688157 /* BackendProtocol.swift in Sources */,
434 | );
435 | runOnlyForDeploymentPostprocessing = 0;
436 | };
437 | /* End PBXSourcesBuildPhase section */
438 |
439 | /* Begin PBXTargetDependency section */
440 | CEEBFD391A5A1D430014D017 /* PBXTargetDependency */ = {
441 | isa = PBXTargetDependency;
442 | target = CEEBFD2A1A5A1D420014D017 /* SwiftCGI */;
443 | targetProxy = CEEBFD381A5A1D430014D017 /* PBXContainerItemProxy */;
444 | };
445 | /* End PBXTargetDependency section */
446 |
447 | /* Begin XCBuildConfiguration section */
448 | CEEBFD3F1A5A1D430014D017 /* Debug */ = {
449 | isa = XCBuildConfiguration;
450 | buildSettings = {
451 | ALWAYS_SEARCH_USER_PATHS = NO;
452 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
453 | CLANG_CXX_LIBRARY = "libc++";
454 | CLANG_ENABLE_MODULES = YES;
455 | CLANG_ENABLE_OBJC_ARC = YES;
456 | CLANG_WARN_BOOL_CONVERSION = YES;
457 | CLANG_WARN_CONSTANT_CONVERSION = YES;
458 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
459 | CLANG_WARN_EMPTY_BODY = YES;
460 | CLANG_WARN_ENUM_CONVERSION = YES;
461 | CLANG_WARN_INT_CONVERSION = YES;
462 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
463 | CLANG_WARN_UNREACHABLE_CODE = YES;
464 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
465 | COPY_PHASE_STRIP = NO;
466 | CURRENT_PROJECT_VERSION = 1;
467 | ENABLE_STRICT_OBJC_MSGSEND = YES;
468 | ENABLE_TESTABILITY = YES;
469 | GCC_C_LANGUAGE_STANDARD = gnu99;
470 | GCC_DYNAMIC_NO_PIC = NO;
471 | GCC_OPTIMIZATION_LEVEL = 0;
472 | GCC_PREPROCESSOR_DEFINITIONS = (
473 | "DEBUG=1",
474 | "$(inherited)",
475 | );
476 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
477 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
478 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
479 | GCC_WARN_UNDECLARED_SELECTOR = YES;
480 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
481 | GCC_WARN_UNUSED_FUNCTION = YES;
482 | GCC_WARN_UNUSED_VARIABLE = YES;
483 | MACOSX_DEPLOYMENT_TARGET = 10.10;
484 | MTL_ENABLE_DEBUG_INFO = YES;
485 | ONLY_ACTIVE_ARCH = YES;
486 | SDKROOT = macosx;
487 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
488 | VERSIONING_SYSTEM = "apple-generic";
489 | VERSION_INFO_PREFIX = "";
490 | };
491 | name = Debug;
492 | };
493 | CEEBFD401A5A1D430014D017 /* Release */ = {
494 | isa = XCBuildConfiguration;
495 | buildSettings = {
496 | ALWAYS_SEARCH_USER_PATHS = NO;
497 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
498 | CLANG_CXX_LIBRARY = "libc++";
499 | CLANG_ENABLE_MODULES = YES;
500 | CLANG_ENABLE_OBJC_ARC = YES;
501 | CLANG_WARN_BOOL_CONVERSION = YES;
502 | CLANG_WARN_CONSTANT_CONVERSION = YES;
503 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
504 | CLANG_WARN_EMPTY_BODY = YES;
505 | CLANG_WARN_ENUM_CONVERSION = YES;
506 | CLANG_WARN_INT_CONVERSION = YES;
507 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
508 | CLANG_WARN_UNREACHABLE_CODE = YES;
509 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
510 | COPY_PHASE_STRIP = YES;
511 | CURRENT_PROJECT_VERSION = 1;
512 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
513 | ENABLE_NS_ASSERTIONS = NO;
514 | ENABLE_STRICT_OBJC_MSGSEND = YES;
515 | GCC_C_LANGUAGE_STANDARD = gnu99;
516 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
517 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
518 | GCC_WARN_UNDECLARED_SELECTOR = YES;
519 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
520 | GCC_WARN_UNUSED_FUNCTION = YES;
521 | GCC_WARN_UNUSED_VARIABLE = YES;
522 | MACOSX_DEPLOYMENT_TARGET = 10.10;
523 | MTL_ENABLE_DEBUG_INFO = NO;
524 | SDKROOT = macosx;
525 | VERSIONING_SYSTEM = "apple-generic";
526 | VERSION_INFO_PREFIX = "";
527 | };
528 | name = Release;
529 | };
530 | CEEBFD421A5A1D430014D017 /* Debug */ = {
531 | isa = XCBuildConfiguration;
532 | buildSettings = {
533 | CLANG_ENABLE_MODULES = YES;
534 | COMBINE_HIDPI_IMAGES = YES;
535 | DEFINES_MODULE = YES;
536 | DYLIB_COMPATIBILITY_VERSION = 1;
537 | DYLIB_CURRENT_VERSION = 1;
538 | DYLIB_INSTALL_NAME_BASE = "@rpath";
539 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
540 | FRAMEWORK_VERSION = A;
541 | INFOPLIST_FILE = SwiftCGI/Info.plist;
542 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
543 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
544 | PRODUCT_BUNDLE_IDENTIFIER = "com.ianthetechie.$(PRODUCT_NAME:rfc1034identifier)";
545 | PRODUCT_NAME = "$(TARGET_NAME)";
546 | SKIP_INSTALL = YES;
547 | SWIFT_INCLUDE_PATHS = "${SRCROOT}/Backends/HTTP/CHTTPParser";
548 | SWIFT_OBJC_BRIDGING_HEADER = "";
549 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
550 | };
551 | name = Debug;
552 | };
553 | CEEBFD431A5A1D430014D017 /* Release */ = {
554 | isa = XCBuildConfiguration;
555 | buildSettings = {
556 | CLANG_ENABLE_MODULES = YES;
557 | COMBINE_HIDPI_IMAGES = YES;
558 | DEFINES_MODULE = YES;
559 | DYLIB_COMPATIBILITY_VERSION = 1;
560 | DYLIB_CURRENT_VERSION = 1;
561 | DYLIB_INSTALL_NAME_BASE = "@rpath";
562 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
563 | FRAMEWORK_VERSION = A;
564 | INFOPLIST_FILE = SwiftCGI/Info.plist;
565 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
566 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
567 | PRODUCT_BUNDLE_IDENTIFIER = "com.ianthetechie.$(PRODUCT_NAME:rfc1034identifier)";
568 | PRODUCT_NAME = "$(TARGET_NAME)";
569 | SKIP_INSTALL = YES;
570 | SWIFT_INCLUDE_PATHS = "${SRCROOT}/Backends/HTTP/CHTTPParser";
571 | SWIFT_OBJC_BRIDGING_HEADER = "";
572 | };
573 | name = Release;
574 | };
575 | CEEBFD451A5A1D430014D017 /* Debug */ = {
576 | isa = XCBuildConfiguration;
577 | buildSettings = {
578 | CLANG_ENABLE_MODULES = YES;
579 | COMBINE_HIDPI_IMAGES = YES;
580 | FRAMEWORK_SEARCH_PATHS = (
581 | "$(DEVELOPER_FRAMEWORKS_DIR)",
582 | "$(inherited)",
583 | );
584 | GCC_PREPROCESSOR_DEFINITIONS = (
585 | "DEBUG=1",
586 | "$(inherited)",
587 | );
588 | INFOPLIST_FILE = SwiftCGITests/Info.plist;
589 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
590 | PRODUCT_BUNDLE_IDENTIFIER = "com.ianthetechie.$(PRODUCT_NAME:rfc1034identifier)";
591 | PRODUCT_NAME = "$(TARGET_NAME)";
592 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftCGITests-Bridging-Header.h";
593 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
594 | };
595 | name = Debug;
596 | };
597 | CEEBFD461A5A1D430014D017 /* Release */ = {
598 | isa = XCBuildConfiguration;
599 | buildSettings = {
600 | CLANG_ENABLE_MODULES = YES;
601 | COMBINE_HIDPI_IMAGES = YES;
602 | FRAMEWORK_SEARCH_PATHS = (
603 | "$(DEVELOPER_FRAMEWORKS_DIR)",
604 | "$(inherited)",
605 | );
606 | INFOPLIST_FILE = SwiftCGITests/Info.plist;
607 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
608 | PRODUCT_BUNDLE_IDENTIFIER = "com.ianthetechie.$(PRODUCT_NAME:rfc1034identifier)";
609 | PRODUCT_NAME = "$(TARGET_NAME)";
610 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftCGITests-Bridging-Header.h";
611 | };
612 | name = Release;
613 | };
614 | E9BFCAD21BDC4CB500A43AE3 /* Debug (Embedded Server) */ = {
615 | isa = XCBuildConfiguration;
616 | buildSettings = {
617 | ALWAYS_SEARCH_USER_PATHS = NO;
618 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
619 | CLANG_CXX_LIBRARY = "libc++";
620 | CLANG_ENABLE_MODULES = YES;
621 | CLANG_ENABLE_OBJC_ARC = YES;
622 | CLANG_WARN_BOOL_CONVERSION = YES;
623 | CLANG_WARN_CONSTANT_CONVERSION = YES;
624 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
625 | CLANG_WARN_EMPTY_BODY = YES;
626 | CLANG_WARN_ENUM_CONVERSION = YES;
627 | CLANG_WARN_INT_CONVERSION = YES;
628 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
629 | CLANG_WARN_UNREACHABLE_CODE = YES;
630 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
631 | COPY_PHASE_STRIP = NO;
632 | CURRENT_PROJECT_VERSION = 1;
633 | ENABLE_STRICT_OBJC_MSGSEND = YES;
634 | ENABLE_TESTABILITY = YES;
635 | GCC_C_LANGUAGE_STANDARD = gnu99;
636 | GCC_DYNAMIC_NO_PIC = NO;
637 | GCC_OPTIMIZATION_LEVEL = 0;
638 | GCC_PREPROCESSOR_DEFINITIONS = (
639 | "DEBUG=1",
640 | "$(inherited)",
641 | );
642 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
643 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
644 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
645 | GCC_WARN_UNDECLARED_SELECTOR = YES;
646 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
647 | GCC_WARN_UNUSED_FUNCTION = YES;
648 | GCC_WARN_UNUSED_VARIABLE = YES;
649 | MACOSX_DEPLOYMENT_TARGET = 10.10;
650 | MTL_ENABLE_DEBUG_INFO = YES;
651 | ONLY_ACTIVE_ARCH = YES;
652 | SDKROOT = macosx;
653 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
654 | VERSIONING_SYSTEM = "apple-generic";
655 | VERSION_INFO_PREFIX = "";
656 | };
657 | name = "Debug (Embedded Server)";
658 | };
659 | E9BFCAD31BDC4CB500A43AE3 /* Debug (Embedded Server) */ = {
660 | isa = XCBuildConfiguration;
661 | buildSettings = {
662 | CLANG_ENABLE_MODULES = YES;
663 | COMBINE_HIDPI_IMAGES = YES;
664 | DEFINES_MODULE = YES;
665 | DYLIB_COMPATIBILITY_VERSION = 1;
666 | DYLIB_CURRENT_VERSION = 1;
667 | DYLIB_INSTALL_NAME_BASE = "@rpath";
668 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
669 | FRAMEWORK_VERSION = A;
670 | INFOPLIST_FILE = SwiftCGI/Info.plist;
671 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
672 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
673 | PRODUCT_BUNDLE_IDENTIFIER = "com.ianthetechie.$(PRODUCT_NAME:rfc1034identifier)";
674 | PRODUCT_NAME = "$(TARGET_NAME)";
675 | SKIP_INSTALL = YES;
676 | SWIFT_INCLUDE_PATHS = "${SRCROOT}/Backends/HTTP/CHTTPParser";
677 | SWIFT_OBJC_BRIDGING_HEADER = "";
678 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
679 | };
680 | name = "Debug (Embedded Server)";
681 | };
682 | E9BFCAD41BDC4CB500A43AE3 /* Debug (Embedded Server) */ = {
683 | isa = XCBuildConfiguration;
684 | buildSettings = {
685 | CLANG_ENABLE_MODULES = YES;
686 | COMBINE_HIDPI_IMAGES = YES;
687 | FRAMEWORK_SEARCH_PATHS = (
688 | "$(DEVELOPER_FRAMEWORKS_DIR)",
689 | "$(inherited)",
690 | );
691 | GCC_PREPROCESSOR_DEFINITIONS = (
692 | "DEBUG=1",
693 | "$(inherited)",
694 | );
695 | INFOPLIST_FILE = SwiftCGITests/Info.plist;
696 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
697 | PRODUCT_BUNDLE_IDENTIFIER = "com.ianthetechie.$(PRODUCT_NAME:rfc1034identifier)";
698 | PRODUCT_NAME = "$(TARGET_NAME)";
699 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftCGITests-Bridging-Header.h";
700 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
701 | };
702 | name = "Debug (Embedded Server)";
703 | };
704 | /* End XCBuildConfiguration section */
705 |
706 | /* Begin XCConfigurationList section */
707 | CEEBFD251A5A1D420014D017 /* Build configuration list for PBXProject "SwiftCGI" */ = {
708 | isa = XCConfigurationList;
709 | buildConfigurations = (
710 | CEEBFD3F1A5A1D430014D017 /* Debug */,
711 | E9BFCAD21BDC4CB500A43AE3 /* Debug (Embedded Server) */,
712 | CEEBFD401A5A1D430014D017 /* Release */,
713 | );
714 | defaultConfigurationIsVisible = 0;
715 | defaultConfigurationName = Release;
716 | };
717 | CEEBFD411A5A1D430014D017 /* Build configuration list for PBXNativeTarget "SwiftCGI" */ = {
718 | isa = XCConfigurationList;
719 | buildConfigurations = (
720 | CEEBFD421A5A1D430014D017 /* Debug */,
721 | E9BFCAD31BDC4CB500A43AE3 /* Debug (Embedded Server) */,
722 | CEEBFD431A5A1D430014D017 /* Release */,
723 | );
724 | defaultConfigurationIsVisible = 0;
725 | defaultConfigurationName = Release;
726 | };
727 | CEEBFD441A5A1D430014D017 /* Build configuration list for PBXNativeTarget "SwiftCGITests" */ = {
728 | isa = XCConfigurationList;
729 | buildConfigurations = (
730 | CEEBFD451A5A1D430014D017 /* Debug */,
731 | E9BFCAD41BDC4CB500A43AE3 /* Debug (Embedded Server) */,
732 | CEEBFD461A5A1D430014D017 /* Release */,
733 | );
734 | defaultConfigurationIsVisible = 0;
735 | defaultConfigurationName = Release;
736 | };
737 | /* End XCConfigurationList section */
738 | };
739 | rootObject = CEEBFD221A5A1D420014D017 /* Project object */;
740 | }
741 |
--------------------------------------------------------------------------------