├── 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 [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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 | --------------------------------------------------------------------------------