diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..32d33ac0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +Sources/LinuxBridge/* linguist-vendored +Sources/OpenSSL/* linguist-vendored +Sources/cURL/* linguist-vendored +Xcode/PerfectLib/* linguist-vendored diff --git a/.gitignore b/.gitignore index 8615121b..9564a1a9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ DerivedData *.hmap *.ipa *.xcuserstate +*.xcscmblueprint # CocoaPods # @@ -31,3 +32,27 @@ DerivedData # Carthage/Checkouts Carthage/Build + +*.d + +*.o + +*.swiftdeps + +*.swiftdoc + +*.swiftmodule + +*.so + +*.DS_Store + +# Perfect binaries +PerfectServer/perfectserverfcgi +PerfectServer/perfectserverhttp + +# SwiftPM +.build/ +Packages/ +PerfectLib.xcodeproj/ +docs/ diff --git a/.jazzy.yaml b/.jazzy.yaml new file mode 100644 index 00000000..330e7aed --- /dev/null +++ b/.jazzy.yaml @@ -0,0 +1,8 @@ +module: PerfectLib +author: PerfectlySoft +author_url: https://perfect.org +github_url: https://github.com/PerfectlySoft/Perfect +copyright: '© 2016 PerfectlySoft Inc. and the Perfect project authors' +theme: fullwidth +xcodebuild_arguments: [-target, PerfectLib, -toolchain, org.swift.3020160620a] +clean: true diff --git a/.swift-version b/.swift-version new file mode 100644 index 00000000..c780b413 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +DEVELOPMENT-SNAPSHOT-2016-08-18-a diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..d05f4bc0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Connectors/MongoDB/README.md b/Connectors/MongoDB/README.md deleted file mode 100644 index c1ae1d6a..00000000 --- a/Connectors/MongoDB/README.md +++ /dev/null @@ -1 +0,0 @@ -# Perfect - MongoDB Connector diff --git a/Connectors/MySQL/README.md b/Connectors/MySQL/README.md deleted file mode 100644 index c5b0cab2..00000000 --- a/Connectors/MySQL/README.md +++ /dev/null @@ -1 +0,0 @@ -# Perfect - MySQL Connector diff --git a/Connectors/PostgreSQL/README.md b/Connectors/PostgreSQL/README.md deleted file mode 100644 index fcfb6731..00000000 --- a/Connectors/PostgreSQL/README.md +++ /dev/null @@ -1 +0,0 @@ -# Perfect - PostgreSQL Connector diff --git a/LICENSE b/LICENSE index 8f71f43f..8dada3ed 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..2c105d71 --- /dev/null +++ b/Package.swift @@ -0,0 +1,35 @@ +// +// Package.swift +// PerfectLib +// +// Created by Kyle Jessup on 3/22/16. +// Copyright (C) 2016 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import PackageDescription + +var urls = [String]() + +#if os(Linux) +urls += ["https://github.com/PerfectlySoft/Perfect-LinuxBridge.git"] +#else + +#endif + +let package = Package( + name: "PerfectLib", + targets: [], + dependencies: urls.map { .Package(url: $0, versions: Version(0,0,0).. + + Get Involed with Perfect! + +

+

+ + Star Perfect On Github + + + Chat on Gitter + + + Follow Perfect on Twitter + + + Join the Perfect Slack + +

+

+ + Swift 3.0 + + + Platforms OS X | Linux + + + License Apache + + + codebeat + + + PerfectlySoft Twitter + + + Join the chat at https://gitter.im/PerfectlySoft/Perfect + + + Slack Status + +

+ +##Perfect: Server-Side Swift + +Perfect is a complete and powerful toolbox, framework, and application server for Linux, iOS, and macOS (OS X). It provides everything a Swift engineer needs for developing lightweight, maintainable, and scalable apps and other REST services entirely in the Swift programming language for both client-facing and server-side applications. + +Perfect includes a suite of tools that will enhance your productivity as you use only one programming language to build your apps: Swift. The global development community’s most dynamic and popular server-side toolbox and framework available today, Perfect is the backbone for many live web applications and apps available on iTunes. + +This guide is designed for developers at all levels of experience to get Perfect up and running quickly. + +## Working with Perfect + +###Compatibility with Swift + +**The master branch of this project currently compiles with the August 23rd Swift toolchain snapshot. ** + +``` +Current version: DEVELOPMENT-SNAPSHOT-2016-08-23-a +``` + +We focus exclusively on the latest and most stable version of Swift to maximize developers’ productivity. *Until the release of Swift 3.0 (expected in September 2016), please treat this version of Perfect for R&D purposes only*. + +###Getting Started + +[Access a tutorial](https://github.com/PerfectlySoft/PerfectDocs/blob/master/guide/gettingStarted.md) to help you get started using Perfect quickly. It includes straightforward examples of how Perfect can be used. + +### Documentation +[Get started working with Perfect](https://github.com/PerfectlySoft/PerfectDocs), deploy your apps, and find more detailed help by consulting our reference library. + +We welcome contributions to Perfect’s documentation. If you spot a typo, bug, or other errata, or have additions or suggestions to recommend, please [create a pull request or log an issue in JIRA](http://jira.perfect.org:8080/servicedesk/customer/portal/1/user/login?destination=portal%2F1). + +### Community +We all need a little help now and then. If you do too, don’t be shy, ask us or the friendly and supportive Perfect community: + + +[Slack](http://perfect.ly/) | [Gitter](https://gitter.im/PerfectlySoft/Perfect?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | [Twitter](https://twitter.com/perfectlysoft) + +### Deployment + +Currently, there are options to deploy Perfect to [Docker](https://hub.docker.com/r/perfectlysoft/ubuntu/) and [Heroku](https://github.com/PerfectlySoft/Perfect-Heroku-Buildpack). We recommend you use these until the other deployment options are updated to compile with Swift 3.0 and Perfect 2.0. + +### Samples, Examples, and Tutorials + +Our library continues to grow as members of [the Swift-Perfect development community have shared many samples and examples](https://github.com/PerfectlySoft/PerfectExamples) of their projects in Perfect. Examples include: + +- [WebSockets Server](https://github.com/PerfectlySoft/PerfectExample-WebSocketsServer) +- [URL Routing](https://github.com/PerfectlySoft/PerfectExample-URLRouting) +- [Upload Enumerator](https://github.com/PerfectlySoft/PerfectExample-UploadEnumerator) +- [Ultimate Naughts & Crosses](https://github.com/PerfectlySoft/PerfectExample-UltimateNaughtsAndCrosses/tree/d11348f106f04dd48057c8d6cfff5a45fba1093e) multiplayer game + +There are [many more examples](https://github.com/PerfectlySoft/PerfectExamples) you can explore. Please share yours! + +[Access tutorials for working in Perfect 1.0](http://perfect.org/tutorials.html) (which supports Swift 2.2) published by members of the Swift-Perfect community. Or [start working in Perfect 2.0](http://perfect.org/downloads.html#download-perfect) (supports Swift 3.0). + +## Core Perfect Modules + +Perfect project is divided into several repositories to make it easy for you to find, download, and install the components you need: + +- [Perfect](https://github.com/PerfectlySoft/Perfect) – This repository contains the core PerfectLib and will continue to be the main landing point for the project +- [Perfect Docs](https://github.com/PerfectlySoft/PerfectDocs) – Contains all API reference-related material + + +### Examples + +- [Perfect Template](https://github.com/PerfectlySoft/PerfectTemplate) - A simple starter project which compiles with the Swift Package Manager into a standalone executable HTTP server. This repository is ideal for starting a Perfect-based project +- [Perfect Examples](https://github.com/PerfectlySoft/PerfectExamples) - All the Perfect example projects and documentation + + +### DataSources + + +- [Perfect Redis](https://github.com/PerfectlySoft/Perfect-Redis) - The Redis database connector +- [Perfect SQLite](https://github.com/PerfectlySoft/Perfect-SQLite) - SQLite3 database connector +- [Perfect PostgreSQL](https://github.com/PerfectlySoft/Perfect-PostgreSQL) - PostgreSQL database connector +- [Perfect MySQL](https://github.com/PerfectlySoft/Perfect-MySQL) - MySQL database connector +- [Perfect MongoDB](https://github.com/PerfectlySoft/Perfect-MongoDB) - MongoDB database connector +- [Perfect FileMaker](https://github.com/PerfectlySoft/Perfect-FileMaker) - FileMaker Server database connector + +### Utilities + +- [Perfect FastCGI Apache 2.4](https://github.com/PerfectlySoft/Perfect-FastCGI-Apache2.4) - Apache 2.4 FastCGI module; required for the Perfect FastCGI server variant +- [Perfect XML](https://github.com/PerfectlySoft/Perfect-XML) - DOM Core level 2 read-only APIs and XPath support +- [Perfect HTTP Server](https://github.com/PerfectlySoft/Perfect-HTTPServer) - HTTP 1.1 server for Perfect +- [Perfect Mustache](https://github.com/PerfectlySoft/Perfect-Mustache) - Mustache template support for Perfect +- [Perfect CURL](https://github.com/PerfectlySoft/Perfect-CURL) - cURL support for Perfect +- [Perfect WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) - WebSockets support for Perfect +- [Perfect Zip](https://github.com/PerfectlySoft/Perfect-Zip) - provides simple zip and unzip functionality + +## More about Perfect + +Perfect operates using either a standalone [HTTP server](https://github.com/PerfectlySoft/Perfect-HTTP), [HTTPS server](https://github.com/PerfectlySoft/Perfect-HTTPServer), or through [FastCGI server](https://github.com/PerfectlySoft/Perfect-FastCGI). It provides a system for loading your Swift-based modules at startup, for interfacing those modules with its request/response objects, or to the built-in [Mustache template processing system](https://github.com/PerfectlySoft/Perfect-Mustache). + +Perfect is built on a completely asynchronous, high-performance networking engine to provide a scalable option for internet services. It supports Secure Sockets Layer (SSL) encryption, and it features a suite of tools commonly required by internet servers such as [WebSockets](https://github.com/PerfectlySoft/Perfect-WebSockets) and [iOS push notifications](https://github.com/PerfectlySoft/Perfect-Notifications), but you are not limited to those options. + +Feel free to use your favourite JSON or templating systems, etc. + +### Join and Contribute to the Community + +The Swift-Perfect developer community is vital to improving Perfect and supporting one another.   + +You can help other developers by sharing your expertise and tips, as well as learn from others, by joining the [Perfect Slack channel](http://perfect.ly) or the [Perfect Gitter channel](https://gitter.im/PerfectlySoft/Perfect), whichever is your preference - we will be hanging out in both mediums. Contributions of all kinds are welcome: reporting issues, updating documentation, fixing bugs, building examples, sharing projects, and any other tips that may help the Swift-Perfect community. + +If you would like to report an issue, make a new feature request, or help others by working on a known issue, please see the [Perfect JIRA repository](http://jira.perfect.org:8080/secure/Dashboard.jspa). + +If you would like to share your example project, tutorial, or video, please share the URL of your work on GitHub and [Twitter](https://twitter.com/perfectlysoft), and the Perfect team will highlight it to the community. + +### Code of Conduct +The Perfect team welcomes people of all ethnicities, nationalities, ages, gender, disability, levels of experience, and religious beliefs to use and contribute to the Perfect project. We pledge to foster and enforce a harassment-free environment of openness, respect, and cooperation for everyone in all project and public spaces online or offline. + +Please report any behaviour that violates our [Code of Conduct](https://github.com/PerfectlySoft/Perfect/blob/master/CODE_OF_CONDUCT.md) to [info@perfect.org](mailto:info@perfect.org). The Perfect team is committed to enforcing this Code of Conduct to ensure everyone who wishes to use, contribute to, and comment on the Perfect project may do so freely and openly and without fear of reprisal. + +We will investigate all complaints of unacceptable or abusive behaviour or comments expediently, and we will maintain the confidentiality of the person who reports any perceived infraction or wrongdoing to us. We will not tolerate any form of direct or indirect harassment or discrimination within the Swift-Perfect community, and will take appropriate, fair, and corrective action to any instance of inappropriate behaviour. + +The Perfect team maintains the right to remove, edit, or reject any comments, code, edits, or issues that do not align with our Code of Conduct. diff --git a/Sources/PerfectLib/Bytes.swift b/Sources/PerfectLib/Bytes.swift new file mode 100644 index 00000000..c5cd2f95 --- /dev/null +++ b/Sources/PerfectLib/Bytes.swift @@ -0,0 +1,197 @@ +// +// Bytes.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/7/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +/// A Bytes object represents an array of UInt8 and provides various utilities for importing and exporting values into and out of that array. +/// The object maintains a position marker which is used to denote the position from which new export operations proceed. +/// An export will advance the position by the appropriate amount. +public final class Bytes { + + /// The position from which new export operations begin. + public var position = 0 + /// The underlying UInt8 array + public var data: [UInt8] + + /// Indicates the number of bytes which may be successfully exported + public var availableExportBytes: Int { return self.data.count - self.position } + + /// Create an empty Bytes object + public init() { + self.data = [UInt8]() + } + + /// Initialize with existing bytes + public init(existingBytes: [UInt8]) { + self.data = existingBytes + } + + // -- IMPORT + /// Imports one UInt8 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import8Bits(from frm: UInt8) -> Bytes { + data.append(frm) + return self + } + + /// Imports one UInt16 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import16Bits(from frm: UInt16) -> Bytes { + data.append(UInt8(frm & 0xFF)) + data.append(UInt8((frm >> 8) & 0xFF)) + return self + } + + /// Imports one UInt32 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import32Bits(from frm: UInt32) -> Bytes { + data.append(UInt8(frm & 0xFF)) + data.append(UInt8((frm >> 8) & 0xFF)) + data.append(UInt8((frm >> 16) & 0xFF)) + data.append(UInt8((frm >> 24) & 0xFF)) + return self + } + + /// Imports one UInt64 value appending it to the end of the array + /// - returns: The Bytes object + @discardableResult + public func import64Bits(from frm: UInt64) -> Bytes { + data.append(UInt8(frm & 0xFF)) + data.append(UInt8((frm >> 8) & 0xFF)) + data.append(UInt8((frm >> 16) & 0xFF)) + data.append(UInt8((frm >> 24) & 0xFF)) + data.append(UInt8((frm >> 32) & 0xFF)) + data.append(UInt8((frm >> 40) & 0xFF)) + data.append(UInt8((frm >> 48) & 0xFF)) + data.append(UInt8((frm >> 56) & 0xFF)) + return self + } + + /// Imports an array of UInt8 values appending them to the end of the array + /// - returns: The Bytes object + @discardableResult + public func importBytes(from frm: [UInt8]) -> Bytes { + data.append(contentsOf: frm) + return self + } + + /// Imports the array values of the given Bytes appending them to the end of the array + /// - returns: The Bytes object + @discardableResult + public func importBytes(from frm: Bytes) -> Bytes { + data.append(contentsOf: frm.data) + return self + } + + /// Imports an `ArraySlice` of UInt8 values appending them to the end of the array + /// - returns: The Bytes object + @discardableResult + public func importBytes(from frm: ArraySlice) -> Bytes { + data.append(contentsOf: frm) + return self + } + + // -- EXPORT + + /// Exports one UInt8 from the current position. Advances the position marker by 1 byte. + /// - returns: The UInt8 value + public func export8Bits() -> UInt8 { + let result = data[position] + position += 1 + return result + } + + /// Exports one UInt16 from the current position. Advances the position marker by 2 bytes. + /// - returns: The UInt16 value + public func export16Bits() -> UInt16 { + + let one = UInt16(data[position]) + position += 1 + let two = UInt16(data[position]) + position += 1 + + return (two << 8) + one + } + + /// Exports one UInt32 from the current position. Advances the position marker by 4 bytes. + /// - returns: The UInt32 value + public func export32Bits() -> UInt32 { + let one = UInt32(data[position]) + position += 1 + let two = UInt32(data[position]) + position += 1 + let three = UInt32(data[position]) + position += 1 + let four = UInt32(data[position]) + position += 1 + + return (four << 24) + (three << 16) + (two << 8) + one + } + + /// Exports one UInt64 from the current position. Advances the position marker by 8 bytes. + /// - returns: The UInt64 value + public func export64Bits() -> UInt64 { + let one = UInt64(data[position]) + position += 1 + let two = UInt64(data[position]) << 8 + position += 1 + let three = UInt64(data[position]) << 16 + position += 1 + let four = UInt64(data[position]) << 24 + position += 1 + let five = UInt64(data[position]) << 32 + position += 1 + let six = UInt64(data[position]) << 40 + position += 1 + let seven = UInt64(data[position]) << 48 + position += 1 + let eight = UInt64(data[position]) << 56 + position += 1 + + return (one+two+three+four)+(five+six+seven+eight) + } + + /// Exports the indicated number of bytes + public func exportBytes(count cnt: Int) -> [UInt8] { + var sub = [UInt8]() + let end = self.position + cnt + while self.position < end { + sub.append(self.data[self.position]) + self.position += 1 + } + return sub + } +} + + + + + + + + + + + + + + + + diff --git a/Sources/PerfectLib/Dir.swift b/Sources/PerfectLib/Dir.swift new file mode 100644 index 00000000..3bd454f4 --- /dev/null +++ b/Sources/PerfectLib/Dir.swift @@ -0,0 +1,182 @@ +// +// Dir.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/7/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +#if os(Linux) +import LinuxBridge +#else +import Darwin +#endif + +/// This class represents a directory on the file system. +/// It can be used for creating & inspecting directories and enumerating directory contents. +public struct Dir { + + public typealias PermissionMode = File.PermissionMode + + var internalPath = "" + + /// Create a new Dir object with the given path + public init(_ path: String) { + let pth = path.ends(with: "/") ? path : path + "/" + self.internalPath = File.resolveTilde(inPath: pth) + } + + /// Returns true if the directory exists + public var exists: Bool { + return exists(realPath) + } + + public func setAsWorkingDir() throws { + let res = chdir(self.internalPath) + guard res == 0 else { + try ThrowFileError() + } + } + + public static var workingDir: Dir { + let buffer = Array(repeating: 0 as UInt8, count: 2049) + let _ = getcwd(UnsafeMutableRawPointer(mutating: buffer).assumingMemoryBound(to: Int8.self), 2048) + let path = String(validatingUTF8: UnsafeMutableRawPointer(mutating: buffer).assumingMemoryBound(to: Int8.self)) ?? "." + return Dir(path) + } + + func exists(_ path: String) -> Bool { + return access(path, F_OK) != -1 + } + + /// Creates the directory using the provided permissions. All directories along the path will be created if need be. + /// - parameter perms: The permissions for use for the new directory and preceeding directories which need to be created. Defaults to RWX-GUO + /// - throws: `PerfectError.FileError` + public func create(perms: PermissionMode = [.rwxUser, .rxGroup, .rxOther]) throws { + let pth = realPath + var currPath = pth.begins(with: "/") ? "/" : "" + for component in pth.filePathComponents where component != "/" { + currPath += component + defer { + currPath += "/" + } + guard !exists(currPath) else { + continue + } + let res = mkdir(currPath, perms.rawValue) + guard res != -1 else { + try ThrowFileError() + } + } + } + + /// Deletes the directory. The directory must be empty in order to be successfuly deleted. + /// - throws: `PerfectError.FileError` + public func delete() throws { + let res = rmdir(realPath) + guard res != -1 else { + try ThrowFileError() + } + } + + /// Returns the name of the directory. + public var name: String { + return internalPath.lastFilePathComponent + } + + /// Returns a Dir object representing the current Dir's parent. Returns nil if there is no parent. + public var parentDir: Dir? { + guard internalPath != "/" else { + return nil // can not go up + } + return Dir(internalPath.deletingLastFilePathComponent) + } + + /// Returns the path to the current directory. + public var path: String { + return internalPath + } + + /// Returns the UNIX style permissions for the directory. + public var perms: PermissionMode { + get { + return File(internalPath).perms + } + set { + File(internalPath).perms = newValue + } + } + + var realPath: String { + return internalPath.resolvingSymlinksInFilePath + } + +#if os(Linux) + func readDir(_ d: OpaquePointer, _ dirEnt: inout dirent, _ endPtr: UnsafeMutablePointer?>!) -> Int32 { + return readdir_r(d, &dirEnt, endPtr) + } +#else + func readDir(_ d: UnsafeMutablePointer, _ dirEnt: inout dirent, _ endPtr: UnsafeMutablePointer?>!) -> Int32 { + return readdir_r(d, &dirEnt, endPtr) + } +#endif + + /// Enumerates the contents of the directory passing the name of each contained element to the provided callback. + /// - parameter closure: The callback which will receive each entry's name + /// - throws: `PerfectError.FileError` + public func forEachEntry(closure: (String) throws -> ()) throws { + guard let dir = opendir(realPath) else { + try ThrowFileError() + } + + defer { closedir(dir) } + + var ent = dirent() + let entPtr = UnsafeMutablePointer?>.allocate(capacity: 1) + defer { entPtr.deallocate(capacity: 1) } + + while readDir(dir, &ent, entPtr) == 0 && entPtr.pointee != nil { + let name = ent.d_name + #if os(Linux) + let nameLen = 1024 + #else + let nameLen = ent.d_namlen + #endif + let type = ent.d_type + + var nameBuf = [CChar]() + let mirror = Mirror(reflecting: name) + let childGen = mirror.children.makeIterator() + for _ in 0..> 3) +let S_IWGRP = (S_IWUSR >> 3) +let S_IXGRP = (S_IXUSR >> 3) +let S_IRWXU = (__S_IREAD|__S_IWRITE|__S_IEXEC) +let S_IRWXG = (S_IRWXU >> 3) +let S_IRWXO = (S_IRWXG >> 3) +let S_IROTH = (S_IRGRP >> 3) +let S_IWOTH = (S_IWGRP >> 3) +let S_IXOTH = (S_IXGRP >> 3) + +let SEEK_CUR: Int32 = 1 +let EXDEV = Int32(18) +let EACCES = Int32(13) +let EAGAIN = Int32(11) +let F_OK: Int32 = 0 + +#else +import Darwin +#endif + +let fileCopyBufferSize = 16384 + +/// Provides access to a file on the local file system +public class File { + + public var fd = -1 + var internalPath = "" + + /// Checks that the file exists on the file system + /// - returns: True if the file exists or false otherwise + public var exists: Bool { + return access(internalPath, F_OK) != -1 + } + + /// Returns true if the file has been opened + public var isOpen: Bool { + return fd != -1 + } + + /// Returns the file's path + public var path: String { return internalPath } + + /// Returns the file path. If the file is a symbolic link, the link will be resolved. + public var realPath: String { + let maxPath = 2048 + guard isLink else { + return internalPath + } + var ary = [UInt8](repeating: 0, count: maxPath) + let buffer = UnsafeMutableRawPointer(mutating: ary).assumingMemoryBound(to: Int8.self) + let res = readlink(internalPath, buffer, maxPath) + guard res != -1 else { + return internalPath + } + ary.removeLast(maxPath - res) + let trailPath = UTF8Encoding.encode(bytes: ary) + let lastChar = trailPath[trailPath.startIndex] + guard lastChar != "/" && lastChar != "." else { + return trailPath + } + return internalPath.deletingLastFilePathComponent + "/" + trailPath + } + + /// Returns the modification date for the file in the standard UNIX format of seconds since 1970/01/01 00:00:00 GMT + /// - returns: The date as Int + public var modificationTime: Int { + var st = stat() + let res = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard res == 0 else { + return Int.max + } + #if os(Linux) + return Int(st.st_mtim.tv_sec) + #else + return Int(st.st_mtimespec.tv_sec) + #endif + } + + static func resolveTilde(inPath: String) -> String { + if !inPath.isEmpty && inPath[inPath.startIndex] == "~" { + var wexp = wordexp_t() + wordexp(inPath, &wexp, 0) + if let resolved = wexp.we_wordv[0], let pth = String(validatingUTF8: resolved) { + return pth + } + } + return inPath + } + + /// Create a file object given a path and open mode + /// - parameter path: Path to the file which will be accessed + /// - parameter fd: The file descriptor, if any, for an already opened file + public init(_ path: String, fd: Int32 = -1) { + self.internalPath = File.resolveTilde(inPath: path) + self.fd = Int(fd) + } + + /// Closes the file if it had been opened + public func close() { + if fd != -1 { + #if os(Linux) + _ = SwiftGlibc.close(CInt(fd)) + #else + _ = Darwin.close(CInt(fd)) + #endif + fd = -1 + } + } + + /// Resets the internal file descriptor, leaving the file opened if it had been. + public func abandon() { + fd = -1 + } +} + +public extension File { + /// The open mode for the file. + public enum OpenMode { + /// Opens the file for read-only access. + case read + /// Opens the file for write-only access, creating the file if it did not exist. + case write + /// Opens the file for read-write access, creating the file if it did not exist. + case readWrite + /// Opens the file for read-write access, creating the file if it did not exist and moving the file marker to the end. + case append + /// Opens the file for read-write access, creating the file if it did not exist and setting the file's size to zero. + case truncate + + var toMode: Int { + switch self { + case .read: return Int(O_RDONLY) + case .write: return Int(O_WRONLY|O_CREAT) + case .readWrite: return Int(O_RDWR|O_CREAT) + case .append: return Int(O_RDWR|O_APPEND|O_CREAT) + case .truncate: return Int(O_RDWR|O_TRUNC|O_CREAT) + } + } + } + /// A file or directory access permission value. + public struct PermissionMode: OptionSet { + public typealias Mode = mode_t + public let rawValue: Mode + public init(rawValue: Mode) { + self.rawValue = rawValue + } + +#if os(Linux) + init(rawValue: Int32) { + self.init(rawValue: UInt32(rawValue)) + } +#endif + + /// Readable by user. + public static let readUser = PermissionMode(rawValue: S_IRUSR) + /// Writable by user. + public static let writeUser = PermissionMode(rawValue: S_IWUSR) + /// Executable by user. + public static let executeUser = PermissionMode(rawValue: S_IXUSR) + /// Readable by group. + public static let readGroup = PermissionMode(rawValue: S_IRGRP) + /// Writable by group. + public static let writeGroup = PermissionMode(rawValue: S_IWGRP) + /// Executable by group. + public static let executeGroup = PermissionMode(rawValue: S_IXGRP) + /// Readable by others. + public static let readOther = PermissionMode(rawValue: S_IROTH) + /// Writable by others. + public static let writeOther = PermissionMode(rawValue: S_IWOTH) + /// Executable by others. + public static let executeOther = PermissionMode(rawValue: S_IXOTH) + + /// Read, write, execute by user. + public static let rwxUser: PermissionMode = [.readUser, .writeUser, .executeUser] + /// Read, write by user and group. + public static let rwUserGroup: PermissionMode = [.readUser, .writeUser, .readGroup, .writeGroup] + + /// Read, execute by group. + public static let rxGroup: PermissionMode = [.readGroup, .executeGroup] + /// Read, execute by other. + public static let rxOther: PermissionMode = [.readOther, .executeOther] + + } + + /// Opens the file using the given mode. + /// - throws: `PerfectError.FileError` + public func open(_ mode: OpenMode = .read, permissions: PermissionMode = .rwUserGroup) throws { + if fd != -1 { + close() + } + #if os(Linux) + let openFd = linux_open(internalPath, CInt(mode.toMode), permissions.rawValue) + #else + let openFd = Darwin.open(internalPath, CInt(mode.toMode), permissions.rawValue) + #endif + guard openFd != -1 else { + try ThrowFileError() + } + fd = Int(openFd) + } +} + +public extension File { + /// The current file read/write position. + public var marker: Int { + /// Returns the value of the file's current position marker + get { + if isOpen { + return Int(lseek(Int32(self.fd), 0, SEEK_CUR)) + } + return 0 + } + /// Sets the file's position marker given the value as measured from the begining of the file. + set { + lseek(Int32(self.fd), off_t(newValue), SEEK_SET) + } + } +} + +public extension File { + + /// Closes and deletes the file + public func delete() { + close() + unlink(path) + } + + /// Moves the file to the new location, optionally overwriting any existing file + /// - parameter path: The path to move the file to + /// - parameter overWrite: Indicates that any existing file at the destination path should first be deleted + /// - returns: Returns a new file object representing the new location + /// - throws: `PerfectError.FileError` + public func moveTo(path: String, overWrite: Bool = false) throws -> File { + let destFile = File(path) + if destFile.exists { + guard overWrite else { + throw PerfectError.fileError(-1, "Can not overwrite existing file") + } + destFile.delete() + } + close() + let res = rename(self.path, path) + if res == 0 { + return destFile + } + if errno == EXDEV { + _ = try self.copyTo(path: path, overWrite: overWrite) + self.delete() + return destFile + } + try ThrowFileError() + } + + /// Copies the file to the new location, optionally overwriting any existing file + /// - parameter path: The path to copy the file to + /// - parameter overWrite: Indicates that any existing file at the destination path should first be deleted + /// - returns: Returns a new file object representing the new location + /// - throws: `PerfectError.FileError` + @discardableResult + public func copyTo(path pth: String, overWrite: Bool = false) throws -> File { + let destFile = File(pth) + if destFile.exists { + guard overWrite else { + throw PerfectError.fileError(-1, "Can not overwrite existing file") + } + destFile.delete() + } + let wasOpen = self.isOpen + let oldMarker = self.marker + if !wasOpen { + try open() + } else { + _ = marker = 0 + } + defer { + if !wasOpen { + close() + } else { + _ = marker = oldMarker + } + } + + try destFile.open(.truncate) + + var bytes = try self.readSomeBytes(count: fileCopyBufferSize) + while bytes.count > 0 { + try destFile.write(bytes: bytes) + bytes = try self.readSomeBytes(count: fileCopyBufferSize) + } + + destFile.close() + return destFile + } +} + +public extension File { + + /// Returns the size of the file in bytes + public var size: Int { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return 0 + } + return Int(st.st_size) + } + + /// Returns true if the file is a symbolic link + public var isLink: Bool { + var st = stat() + let statRes = lstat(internalPath, &st) + guard statRes != -1 else { + return false + } + let mode = st.st_mode + return (Int32(mode) & Int32(S_IFMT)) == Int32(S_IFLNK) + } + + /// Returns true if the file is actually a directory + public var isDir: Bool { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return false + } + let mode = st.st_mode + return (Int32(mode) & Int32(S_IFMT)) == Int32(S_IFDIR) + } + + /// Returns the UNIX style permissions for the file + public var perms: PermissionMode { + get { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return PermissionMode(rawValue: PermissionMode.Mode(0)) + } + let mode = st.st_mode + return PermissionMode(rawValue: mode_t(Int32(mode) ^ Int32(S_IFMT))) + } + set { + _ = chmod(internalPath, newValue.rawValue) + } + } +} + +public extension File { + + /// Reads up to the indicated number of bytes from the file + /// - parameter count: The maximum number of bytes to read + /// - returns: The bytes read as an array of UInt8. May have a count of zero. + /// - throws: `PerfectError.FileError` + public func readSomeBytes(count: Int) throws -> [UInt8] { + if !isOpen { + try open() + } + + func sizeOr(_ value: Int) -> Int { + var st = stat() + let statRes = isOpen ? fstat(Int32(fd), &st) : stat(internalPath, &st) + guard statRes != -1 else { + return 0 + } + if (Int32(st.st_mode) & Int32(S_IFMT)) == Int32(S_IFREG) { + return Int(st.st_size) + } + return value + } + + let bSize = min(count, sizeOr(count)) + var ary = [UInt8](repeating: 0, count: bSize) + let ptr = UnsafeMutableRawPointer(mutating: ary).assumingMemoryBound(to: Int8.self) + + let readCount = read(CInt(fd), ptr, bSize) + guard readCount >= 0 else { + try ThrowFileError() + } + if readCount < bSize { + ary.removeLast(bSize - readCount) + } + return ary + } + + /// Reads the entire file as a string + public func readString() throws -> String { + let bytes = try self.readSomeBytes(count: self.size) + return UTF8Encoding.encode(bytes: bytes) + } +} + +public extension File { + + /// Writes the string to the file using UTF-8 encoding + /// - parameter s: The string to write + /// - returns: Returns the number of bytes which were written + /// - throws: `PerfectError.FileError` + @discardableResult + public func write(string: String) throws -> Int { + return try write(bytes: Array(string.utf8)) + } + + /// Write the indicated bytes to the file + /// - parameter bytes: The array of UInt8 to write. + /// - parameter dataPosition: The offset within `bytes` at which to begin writing. + /// - parameter length: The number of bytes to write. + /// - throws: `PerfectError.FileError` + @discardableResult + public func write(bytes: [UInt8], dataPosition: Int = 0, length: Int = Int.max) throws -> Int { + let len = min(bytes.count - dataPosition, length) + let ptr = UnsafeMutableRawPointer(mutating: bytes).assumingMemoryBound(to: UInt8.self).advanced(by: dataPosition) + #if os(Linux) + let wrote = SwiftGlibc.write(Int32(fd), ptr, len) + #else + let wrote = Darwin.write(Int32(fd), ptr, len) + #endif + guard wrote == len else { + try ThrowFileError() + } + return wrote + } +} + +public extension File { + + /// Attempts to place an advisory lock starting from the current position marker up to the indicated byte count. This function will block the current thread until the lock can be performed. + /// - parameter byteCount: The number of bytes to lock + /// - throws: `PerfectError.FileError` + public func lock(byteCount: Int) throws { + if !isOpen { + try open(.write) + } + let res = lockf(Int32(self.fd), F_LOCK, off_t(byteCount)) + guard res == 0 else { + try ThrowFileError() + } + } + + /// Unlocks the number of bytes starting from the current position marker up to the indicated byte count. + /// - parameter byteCount: The number of bytes to unlock + /// - throws: `PerfectError.FileError` + public func unlock(byteCount: Int) throws { + if !isOpen { + try open(.write) + } + let res = lockf(Int32(self.fd), F_ULOCK, off_t(byteCount)) + guard res == 0 else { + try ThrowFileError() + } + } + + /// Attempts to place an advisory lock starting from the current position marker up to the indicated byte count. This function will throw an exception if the file is already locked, but will not block the current thread. + /// - parameter byteCount: The number of bytes to lock + /// - throws: `PerfectError.FileError` + public func tryLock(byteCount: Int) throws { + if !isOpen { + try open(.write) + } + let res = lockf(Int32(self.fd), F_TLOCK, off_t(byteCount)) + guard res == 0 else { + try ThrowFileError() + } + } + + /// Tests if the indicated bytes are locked + /// - parameter byteCount: The number of bytes to test + /// - returns: True if the file is locked + /// - throws: `PerfectError.FileError` + public func testLock(byteCount: Int) throws -> Bool { + if !isOpen { + try open(.write) + } + let res = Int(lockf(Int32(self.fd), F_TEST, off_t(byteCount))) + guard res == 0 || res == Int(EACCES) || res == Int(EAGAIN) else { + try ThrowFileError() + } + return res != 0 + } +} + +// Subclass to represent a file which can not be closed +private final class UnclosableFile : File { + override init(_ path: String, fd: Int32) { + super.init(path, fd: fd) + } + override func close() { + // nothing + } +} + +/// A temporary, randomly named file. +public final class TemporaryFile: File { + /// Create a temporary file, usually in the system's /tmp/ directory + /// - parameter withPrefix: The prefix for the temporary file's name. Random characters will be appended to the file's eventual name. + public convenience init(withPrefix: String) { + let template = withPrefix + "XXXXXX" + let utf8 = template.utf8 + let name = UnsafeMutablePointer.allocate(capacity: utf8.count + 1) + var i = utf8.startIndex + for index in 0.. JSONConvertibleObject + + static private var jsonDecodableRegistry = [String:JSONConvertibleObjectCreator]() + + /// Register a custom object to be JSON encoded/decoded. + static public func registerJSONDecodable(name nam: String, creator: @escaping JSONConvertibleObjectCreator) { + JSONDecoding.jsonDecodableRegistry[nam] = creator + } + + /// Instantiate a custom object based on the JSON data. + /// The system will use the `JSONDecoding.objectIdentifierKey` key to locate the custom object creator. + static public func createJSONConvertibleObject(values:[String:Any]) -> JSONConvertibleObject? { + guard let objkey = values[JSONDecoding.objectIdentifierKey] as? String else { + return nil + } + return JSONDecoding.createJSONConvertibleObject(name: objkey, values: values) + } + + /// Instantiate a custom object based on the JSON data. + /// The system will use the `name` parameter to locate the custom object creator. + static public func createJSONConvertibleObject(name: String, values:[String:Any]) -> JSONConvertibleObject? { + guard let creator = JSONDecoding.jsonDecodableRegistry[name] else { + return nil + } + let jsonObj = creator() + jsonObj.setJSONValues(values) + return jsonObj + } +} + +/// Protocol for an object which can be converted into JSON text. +public protocol JSONConvertible { + /// Returns the JSON encoded String for any JSONConvertible type. + func jsonEncodedString() throws -> String +} + +// !FIX! changed this to be a class due to Linux protocols failing 'as' tests. +// revisit this +/// Base for a custom object which can be converted to and from JSON. +open class JSONConvertibleObject: JSONConvertible { + /// Default initializer. + public init() {} + /// Get the JSON keys/value. + open func setJSONValues(_ values:[String:Any]) {} + /// Set the object properties based on the JSON keys/values. + open func getJSONValues() -> [String:Any] { return [String:Any]() } + /// Encode the object into JSON text + public func jsonEncodedString() throws -> String { + return try self.getJSONValues().jsonEncodedString() + } +} + +/// Get the JSON keys/values from a custom object. +public extension JSONConvertibleObject { + + func getJSONValue(named namd: String, from: [String:Any], defaultValue: T, separator: String = ".") -> T { + let keys = namd.components(separatedBy: separator) + + var value: Any? = from + for key in keys { + guard value != nil else { + return defaultValue + } + + if let data = value as? [String:Any] { + value = data[key] + } + } + + if let result = value as? T { + return result + } + + return defaultValue + } +} + + +/// An error occurring during JSON conversion. +public enum JSONConversionError: Error { + /// The object did not suppport JSON conversion. + case notConvertible(Any) + /// A provided key was not a String. + case invalidKey(Any) + /// The JSON text contained a syntax error. + case syntaxError +} + +private let jsonBackSlash = UnicodeScalar(UInt32(92))! +private let jsonBackSpace = UnicodeScalar(UInt32(8))! +private let jsonFormFeed = UnicodeScalar(UInt32(12))! +private let jsonLF = UnicodeScalar(UInt32(10))! +private let jsonCR = UnicodeScalar(UInt32(13))! +private let jsonTab = UnicodeScalar(UInt32(9))! +private let jsonQuoteDouble = UnicodeScalar(UInt32(34))! + +private let jsonOpenObject = UnicodeScalar(UInt32(123))! +private let jsonOpenArray = UnicodeScalar(UInt32(91))! +private let jsonCloseObject = UnicodeScalar(UInt32(125))! +private let jsonCloseArray = UnicodeScalar(UInt32(93))! +private let jsonWhiteSpace = UnicodeScalar(UInt32(32))! +private let jsonColon = UnicodeScalar(UInt32(58))! +private let jsonComma = UnicodeScalar(UInt32(44))! + +private let highSurrogateLowerBound = UInt32(strtoul("d800", nil, 16)) +private let highSurrogateUpperBound = UInt32(strtoul("dbff", nil, 16)) +private let lowSurrogateLowerBound = UInt32(strtoul("dc00", nil, 16)) +private let lowSurrogateUpperBound = UInt32(strtoul("dfff", nil, 16)) +private let surrogateStep = UInt32(strtoul("400", nil, 16)) +private let surrogateBase = UInt32(strtoul("10000", nil, 16)) + +/// This is a stand-in for a JSON null. +/// May be produced when decoding. +public struct JSONConvertibleNull: JSONConvertible { + public func jsonEncodedString() throws -> String { + return "null" + } +} + +extension String: JSONConvertible { + /// Convert a String into JSON text + public func jsonEncodedString() throws -> String { + var s = "\"" + for uchar in self.unicodeScalars { + switch(uchar) { + case jsonBackSlash: + s.append("\\\\") + case jsonQuoteDouble: + s.append("\\\"") + case jsonBackSpace: + s.append("\\b") + case jsonFormFeed: + s.append("\\f") + case jsonLF: + s.append("\\n") + case jsonCR: + s.append("\\r") + case jsonTab: + s.append("\\t") + default: + s.append(String(uchar)) + } + } + s.append("\"") + return s + } +} + +extension Int: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension UInt: JSONConvertible { + /// Convert a UInt into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Int32: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Int64: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension UInt32: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension UInt64: JSONConvertible { + /// Convert an Int into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Double: JSONConvertible { + /// Convert a Double into JSON text. + public func jsonEncodedString() throws -> String { + return String(self) + } +} + +extension Optional: JSONConvertible { + /// Convert an Optional into JSON text. + public func jsonEncodedString() throws -> String { + if self == nil { + return "null" + } else if let v = self! as? JSONConvertible { + return try v.jsonEncodedString() + } + throw JSONConversionError.notConvertible(self) + } +} + +extension Bool: JSONConvertible { + /// Convert a Bool into JSON text. + public func jsonEncodedString() throws -> String { + if true == self { + return "true" + } + return "false" + } +} + +// !FIX! Downcasting to protocol does not work on Linux +// Not sure if this is intentional, or a bug. +func jsonEncodedStringWorkAround(_ o: Any) throws -> String { + switch o { + case let jsonAble as JSONConvertibleObject: // as part of Linux work around + return try jsonAble.jsonEncodedString() + case let jsonAble as JSONConvertible: + return try jsonAble.jsonEncodedString() + case let jsonAble as String: + return try jsonAble.jsonEncodedString() + case let jsonAble as Int: + return try jsonAble.jsonEncodedString() + case let jsonAble as UInt: + return try jsonAble.jsonEncodedString() + case let jsonAble as Double: + return try jsonAble.jsonEncodedString() + case let jsonAble as Bool: + return try jsonAble.jsonEncodedString() + case let jsonAble as [Any]: + return try jsonAble.jsonEncodedString() + case let jsonAble as [[String:Any]]: + return try jsonAble.jsonEncodedString() + case let jsonAble as [String:Any]: + return try jsonAble.jsonEncodedString() + default: + throw JSONConversionError.notConvertible(o) + } +} + +extension Array: JSONConvertible { + /// Convert an Array into JSON text. + public func jsonEncodedString() throws -> String { + var s = "[" + var first = true + for v in self { + if !first { + s.append(",") + } else { + first = false + } + s.append(try jsonEncodedStringWorkAround(v)) + } + s.append("]") + return s + } +} + +extension Dictionary: JSONConvertible { + /// Convert a Dictionary into JSON text. + public func jsonEncodedString() throws -> String { + var s = "{" + var first = true + for (k, v) in self { + guard let strKey = k as? String else { + throw JSONConversionError.invalidKey(k) + } + if !first { + s.append(",") + } else { + first = false + } + s.append(try strKey.jsonEncodedString()) + s.append(":") + s.append(try jsonEncodedStringWorkAround(v)) + } + s.append("}") + return s + } +} + +extension String { + /// Decode the object represented by the JSON text. + public func jsonDecode() throws -> JSONConvertible { + let state = JSONDecodeState() + state.g = self.unicodeScalars.makeIterator() + + let o = try state.readObject() + if let _ = o as? JSONDecodeState.EOF { + throw JSONConversionError.syntaxError + } + return o + } +} + +private class JSONDecodeState { + struct EOF: JSONConvertible { + func jsonEncodedString() throws -> String { return "" } + } + + var g = String().unicodeScalars.makeIterator() + var pushBack: UnicodeScalar? + + func movePastWhite() { + while let c = self.next() { + if !c.isWhiteSpace() { + self.pushBack = c + break + } + } + } + + func readObject() throws -> JSONConvertible { + self.movePastWhite() + + guard let c = self.next() else { + return EOF() + } + + switch(c) { + case jsonOpenArray: + var a = [Any]() + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c != jsonCloseArray { + self.pushBack = c + while true { + a.append(try readObject()) + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c == jsonCloseArray { + break + } + if c != jsonComma { + throw JSONConversionError.syntaxError + } + } + } + return a + case jsonOpenObject: + var d = [String:Any]() + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c != jsonCloseObject { + self.pushBack = c + while true { + guard let key = try readObject() as? String else { + throw JSONConversionError.syntaxError + } + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + guard c == jsonColon else { + throw JSONConversionError.syntaxError + } + self.movePastWhite() + d[key] = try readObject() + do { + self.movePastWhite() + guard let c = self.next() else { + throw JSONConversionError.syntaxError + } + if c == jsonCloseObject { + break + } + if c != jsonComma { + throw JSONConversionError.syntaxError + } + } + } + } + if let objid = d[JSONDecoding.objectIdentifierKey] as? String { + if let o = JSONDecoding.createJSONConvertibleObject(name: objid, values: d) { + return o + } + } + return d + case jsonQuoteDouble: + return try readString() + default: + if c.isWhiteSpace() { + // nothing + } else if c.isDigit() || c == "-" || c == "+" { + return try readNumber(firstChar: c) + } else if c == "t" || c == "T" { + return try readTrue() + } else if c == "f" || c == "F" { + return try readFalse() + } else if c == "n" || c == "N" { + try readNull() + return JSONConvertibleNull() + } + } + throw JSONConversionError.syntaxError + } + + func next() -> UnicodeScalar? { + if pushBack != nil { + let c = pushBack! + pushBack = nil + return c + } + return g.next() + } + + // the opening quote has been read + func readString() throws -> String { + var next = self.next() + var esc = false + var s = "" + while let c = next { + + if esc { + switch(c) { + case jsonBackSlash: + s.append(String(jsonBackSlash)) + case jsonQuoteDouble: + s.append(String(jsonQuoteDouble)) + case "b": + s.append(String(jsonBackSpace)) + case "f": + s.append(String(jsonFormFeed)) + case "n": + s.append(String(jsonLF)) + case "r": + s.append(String(jsonCR)) + case "t": + s.append(String(jsonTab)) + case "u": + var hexStr = "" + for _ in 1...4 { + next = self.next() + guard let hexC = next else { + throw JSONConversionError.syntaxError + } + guard hexC.isHexDigit() else { + throw JSONConversionError.syntaxError + } + hexStr.append(String(hexC)) + } + var uint32Value = UInt32(strtoul(hexStr, nil, 16)) + // if unicode is a high/low surrogate, it can't be converted directly by UnicodeScalar + // if it's a low surrogate (not expected), throw error + if case lowSurrogateLowerBound...lowSurrogateUpperBound = uint32Value { + throw JSONConversionError.syntaxError + } + // if it's a high surrogate, find the low surrogate which the next unicode is supposed to be, then calculate the pair + if case highSurrogateLowerBound...highSurrogateUpperBound = uint32Value { + let highSurrogateValue = uint32Value + guard self.next() == jsonBackSlash else { + throw JSONConversionError.syntaxError + } + guard self.next() == "u" else { + throw JSONConversionError.syntaxError + } + var lowSurrogateHexStr = "" + for _ in 1...4 { + next = self.next() + guard let hexC = next else { + throw JSONConversionError.syntaxError + } + guard hexC.isHexDigit() else { + throw JSONConversionError.syntaxError + } + lowSurrogateHexStr.append(String(hexC)) + } + let lowSurrogateValue = UInt32(strtoul(lowSurrogateHexStr, nil, 16)) + uint32Value = ( highSurrogateValue - highSurrogateLowerBound ) * surrogateStep + ( lowSurrogateValue - lowSurrogateLowerBound ) + surrogateBase + } + if let result = UnicodeScalar(uint32Value) { + s.append(String(Character(result))) + } + default: + s.append(String(c)) + } + esc = false + } else if c == jsonBackSlash { + esc = true + } else if c == jsonQuoteDouble { + return s + } else { + s.append(String(c)) + } + + next = self.next() + } + throw JSONConversionError.syntaxError + } + + func readNumber(firstChar first: UnicodeScalar) throws -> JSONConvertible { + var s = "" + var needPeriod = true, needExp = true + s.append(String(Character(first))) + + if first == "." { + needPeriod = false + } + + var next = self.next() + var last = first + while let c = next { + if c.isDigit() { + s.append(String(c)) + } else if c == "." && !needPeriod { + break + } else if (c == "e" || c == "E") && !needExp { + break + } else if c == "." { + needPeriod = false + s.append(String(c)) + } else if c == "e" || c == "E" { + needExp = false + s.append(String(c)) + + next = self.next() + if next != nil && (next! == "-" || next! == "+") { + s.append(String(next!)) + } else { + pushBack = next! + } + + } else if last.isDigit() { + pushBack = c + if needPeriod && needExp { + return Int(s)! + } + return Double(s)! + } else { + break + } + last = c + next = self.next() + } + + throw JSONConversionError.syntaxError + } + + func readTrue() throws -> Bool { + var next = self.next() + if next != "r" && next != "R" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "u" && next != "U" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "e" && next != "E" { + throw JSONConversionError.syntaxError + } + next = self.next() + guard next != nil && !next!.isAlphaNum() else { + throw JSONConversionError.syntaxError + } + pushBack = next! + return true + } + + func readFalse() throws -> Bool { + var next = self.next() + if next != "a" && next != "A" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "l" && next != "L" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "s" && next != "S" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "e" && next != "E" { + throw JSONConversionError.syntaxError + } + next = self.next() + guard next != nil && !next!.isAlphaNum() else { + throw JSONConversionError.syntaxError + } + pushBack = next! + return false + } + + func readNull() throws { + var next = self.next() + if next != "u" && next != "U" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "l" && next != "L" { + throw JSONConversionError.syntaxError + } + next = self.next() + if next != "l" && next != "L" { + throw JSONConversionError.syntaxError + } + next = self.next() + guard next != nil && !next!.isAlphaNum() else { + throw JSONConversionError.syntaxError + } + pushBack = next! + } +} diff --git a/Sources/PerfectLib/Log.swift b/Sources/PerfectLib/Log.swift new file mode 100644 index 00000000..012d40b9 --- /dev/null +++ b/Sources/PerfectLib/Log.swift @@ -0,0 +1,154 @@ +// +// LogManager.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/21/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +#if os(Linux) + import SwiftGlibc + import LinuxBridge +#else + import Darwin +#endif + +/// Placeholder functions for logging system +public protocol Logger { + func debug(message: String) + func info(message: String) + func warning(message: String) + func error(message: String) + func critical(message: String) + func terminal(message: String) +} + +public struct ConsoleLogger: Logger { + public init(){} + + public func debug(message: String) { + print("[DBG] " + message) + } + + public func info(message: String) { + print("[INFO] " + message) + } + + public func warning(message: String) { + print("[WARN] " + message) + } + + public func error(message: String) { + print("[ERR] " + message) + } + + public func critical(message: String) { + print("[CRIT] " + message) + } + + public func terminal(message: String) { + print("[TERM] " + message) + } +} + +public struct SysLogger: Logger { + let consoleEcho = ConsoleLogger() + public init(){} + + func syslog(priority: Int32, _ args: CVarArg...) { + withVaList(args) { + vsyslog(priority, "%s", $0) + } + } + + public func debug(message: String) { + consoleEcho.debug(message: message) + message.withCString { + f in + syslog(priority: LOG_DEBUG, f) + } + } + + public func info(message: String) { + consoleEcho.info(message: message) + message.withCString { + f in + syslog(priority: LOG_INFO, f) + } + } + + public func warning(message: String) { + consoleEcho.warning(message: message) + message.withCString { + f in + syslog(priority: LOG_WARNING, f) + } + } + + public func error(message: String) { + consoleEcho.error(message: message) + message.withCString { + f in + syslog(priority: LOG_ERR, f) + } + } + + public func critical(message: String) { + consoleEcho.critical(message: message) + message.withCString { + f in + syslog(priority: LOG_CRIT, f) + } + } + + public func terminal(message: String) { + consoleEcho.terminal(message: message) + message.withCString { + f in + syslog(priority: LOG_EMERG, f) + } + } +} + +/// Placeholder functions for logging system +public struct Log { + public static var logger: Logger = ConsoleLogger() + + public static func debug(message: @autoclosure () -> String) { +// #if DEBUG + Log.logger.debug(message: message()) +// #endif + } + + public static func info(message: String) { + Log.logger.info(message: message) + } + + public static func warning(message: String) { + Log.logger.warning(message: message) + } + + public static func error(message: String) { + Log.logger.error(message: message) + } + + public static func critical(message: String) { + Log.logger.critical(message: message) + } + + public static func terminal(message: String) -> Never { + Log.logger.terminal(message: message) + fatalError(message) + } +} diff --git a/Sources/PerfectLib/PerfectError.swift b/Sources/PerfectLib/PerfectError.swift new file mode 100644 index 00000000..ef3aa7e6 --- /dev/null +++ b/Sources/PerfectLib/PerfectError.swift @@ -0,0 +1,70 @@ +// +// PerfectError.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/5/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +#if os(Linux) +import SwiftGlibc + +var errno: Int32 { + return __errno_location().pointee +} +#else +import Darwin +#endif + +/// Some but not all of the exception types which may be thrown by the system +public enum PerfectError : Error { + /// A network related error code and message. + case networkError(Int32, String) + /// A file system related error code and message. + case fileError(Int32, String) + /// A OS level error code and message. + case systemError(Int32, String) + /// An API exception error message. + case apiError(String) +} + + +func ThrowFileError(file: String = #file, function: String = #function, line: Int = #line) throws -> Never { + let err = errno + let msg = String(validatingUTF8: strerror(err))! + +// print("FileError: \(err) \(msg)") + + throw PerfectError.fileError(err, msg + " \(file) \(function) \(line)") +} + + +func ThrowSystemError(file: String = #file, function: String = #function, line: Int = #line) throws -> Never { + let err = errno + let msg = String(validatingUTF8: strerror(err))! + +// print("SystemError: \(err) \(msg)") + + throw PerfectError.systemError(err, msg + " \(file) \(function) \(line)") +} + + +func ThrowNetworkError(file: String = #file, function: String = #function, line: Int = #line) throws -> Never { + let err = errno + let msg = String(validatingUTF8: strerror(err))! + +// print("NetworkError: \(err) \(msg)") + + throw PerfectError.networkError(err, msg + " \(file) \(function) \(line)") +} diff --git a/Sources/PerfectLib/PerfectServer.swift b/Sources/PerfectLib/PerfectServer.swift new file mode 100644 index 00000000..67821eff --- /dev/null +++ b/Sources/PerfectLib/PerfectServer.swift @@ -0,0 +1,50 @@ +// +// Perfect.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/5/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +#if os(Linux) + import SwiftGlibc +#else + import Darwin +#endif + +/// Provides access to various system level features for the process. +/// A static instance of this class is created at startup and all access to this object go through the `PerfectServer.staticPerfectServer` static property. +public struct PerfectServer { + + @available(*, deprecated, message: "No longer required to call this") + public static func initializeServices() { + + } + + /// Switch the current process to run with the permissions of the indicated user + public static func switchTo(userName unam: String) throws { + guard let pw = getpwnam(unam) else { + try ThrowSystemError() + } + let gid = pw.pointee.pw_gid + let uid = pw.pointee.pw_uid + guard 0 == setgid(gid) else { + try ThrowSystemError() + } + guard 0 == setuid(uid) else { + try ThrowSystemError() + } + } +} + diff --git a/Sources/PerfectLib/SwiftCompatibility.swift b/Sources/PerfectLib/SwiftCompatibility.swift new file mode 100644 index 00000000..b0ba36f3 --- /dev/null +++ b/Sources/PerfectLib/SwiftCompatibility.swift @@ -0,0 +1,33 @@ +// +// SwiftCompatibility.swift +// PerfectLib +// +// Created by Kyle Jessup on 2016-04-22. +// Copyright © 2016 PerfectlySoft. All rights reserved. +// + +extension String { + func range(ofString string: String, ignoreCase: Bool = false) -> Range? { + var idx = self.startIndex + let endIdx = self.endIndex + + while idx != endIdx { + if ignoreCase ? (String(self[idx]).lowercased() == String(string[string.startIndex]).lowercased()) : (self[idx] == string[string.startIndex]) { + var newIdx = self.index(after: idx) + var findIdx = string.index(after: string.startIndex) + let findEndIdx = string.endIndex + + while newIdx != endIndex && findIdx != findEndIdx && (ignoreCase ? (String(self[newIdx]).lowercased() == String(string[findIdx]).lowercased()) : (self[newIdx] == string[findIdx])) { + newIdx = self.index(after: newIdx) + findIdx = string.index(after: findIdx) + } + + if findIdx == findEndIdx { // match + return idx..? + + let cArgsCount = args?.count ?? 0 + let cArgs = UnsafeMutablePointer.allocate(capacity: cArgsCount + 2) + + defer { + cArgs.deinitialize(count: cArgsCount + 2) + cArgs.deallocate(capacity: cArgsCount + 2) + } + + cArgs[0] = strdup(cmd) + cArgs[cArgsCount + 1] = nil + + for idx in 0...allocate(capacity: cEnvCount + 1) + + defer { cEnv.deinitialize(count: cEnvCount + 1) ; cEnv.deallocate(capacity: cEnvCount + 1) } + + cEnv[cEnvCount] = nil + for idx in 0.. Bool { + return self.pid != -1 + } + + /// Terminate the process and clean up. + public func close() { + if self.stdin != nil { + self.stdin!.close() + } + if self.stdout != nil { + self.stdout!.close() + } + if self.stderr != nil { + self.stderr!.close() + } + if self.pid != -1 { + do { + let _ = try self.kill() + } catch { + + } + } + self.stdin = nil + self.stdout = nil + self.stderr = nil + self.pid = -1 + } + + /// Detach from the process such that it will not be manually terminated when this object is deinitialized. + public func detach() { + self.pid = -1 + } + + /// Determine if the process has completed running and retrieve its result code. + public func wait(hang: Bool = true) throws -> Int32 { + var code = Int32(0) + while true { + let status = waitpid(self.pid, &code, WUNTRACED | (hang ? 0 : WNOHANG)) + if status == -1 && errno == EINTR { + continue + } + guard status != -1 else { + try ThrowSystemError() + } + break + } + self.pid = -1 + return code + } + + /// Terminate the process and return its result code. + public func kill(signal: Int32 = SIGTERM) throws -> Int32 { + #if os(Linux) + let status = SwiftGlibc.kill(self.pid, signal) + #else + let status = Darwin.kill(self.pid, signal) + #endif + guard status != -1 else { + try ThrowSystemError() + } + return try self.wait() + } +} diff --git a/Sources/PerfectLib/Utilities.swift b/Sources/PerfectLib/Utilities.swift new file mode 100644 index 00000000..970ef836 --- /dev/null +++ b/Sources/PerfectLib/Utilities.swift @@ -0,0 +1,688 @@ +// +// Utilities.swift +// PerfectLib +// +// Created by Kyle Jessup on 7/17/15. +// Copyright (C) 2015 PerfectlySoft, Inc. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import Foundation + +#if os(Linux) +import LinuxBridge +#else +import Darwin +#endif + +/// This class permits an UnsafeMutablePointer to be used as a GeneratorType +public struct GenerateFromPointer : IteratorProtocol { + + public typealias Element = T + + var count = 0 + var pos = 0 + var from: UnsafeMutablePointer + + /// Initialize given an UnsafeMutablePointer and the number of elements pointed to. + public init(from: UnsafeMutablePointer, count: Int) { + self.from = from + self.count = count + } + + /// Return the next element or nil if the sequence has been exhausted. + mutating public func next() -> Element? { + guard count > 0 else { + return nil + } + self.count -= 1 + let result = self.from[self.pos] + self.pos += 1 + return result + } +} + +/// A generalized wrapper around the Unicode codec operations. +public struct Encoding { + + /// Return a String given a character generator. + public static func encode(codec inCodec: D, generator: G) -> String where G.Element == D.CodeUnit, G.Element == D.CodeUnit { + var encodedString = "" + var finished: Bool = false + var mutableDecoder = inCodec + var mutableGenerator = generator + repeat { + let decodingResult = mutableDecoder.decode(&mutableGenerator) + switch decodingResult { + case .scalarValue(let char): + encodedString.append(String(char)) + case .emptyInput: + finished = true + /* ignore errors and unexpected values */ + case .error: + finished = true + } + } while !finished + return encodedString + } +} + +/// Utility wrapper permitting a UTF-8 character generator to encode a String. Also permits a String to be converted into a UTF-8 byte array. +public struct UTF8Encoding { + + /// Use a character generator to create a String. + public static func encode(generator gen: G) -> String where G.Element == UTF8.CodeUnit { + return Encoding.encode(codec: UTF8(), generator: gen) + } + + /// Use a character sequence to create a String. + public static func encode(bytes byts: S) -> String where S.Iterator.Element == UTF8.CodeUnit { + return encode(generator: byts.makeIterator()) + } + + /// Decode a String into an array of UInt8. + public static func decode(string str: String) -> Array { + return [UInt8](str.utf8) + } +} + +extension UInt8 { + var shouldURLEncode: Bool { + let cc = self + return ( ( cc >= 128 ) + || ( cc < 33 ) + || ( cc >= 34 && cc < 38 ) + || ( ( cc > 59 && cc < 61) || cc == 62 || cc == 58) + || ( ( cc >= 91 && cc < 95 ) || cc == 96 ) + || ( cc >= 123 && cc <= 126 ) + || self == 43 ) + } + + // same as String(self, radix: 16) + // but outputs two characters. i.e. 0 padded + var hexString: String { + var s = "" + let b = self >> 4 + s.append(String(Character(UnicodeScalar(b > 9 ? b - 10 + 65 : b + 48)))) + let b2 = self & 0x0F + s.append(String(Character(UnicodeScalar(b2 > 9 ? b2 - 10 + 65 : b2 + 48)))) + return s + } +} + +extension String { + /// Returns the String with all special HTML characters encoded. + public var stringByEncodingHTML: String { + var ret = "" + var g = self.unicodeScalars.makeIterator() + var lastWasCR = false + while let c = g.next() { + if c == UnicodeScalar(10) { + if lastWasCR { + lastWasCR = false + ret.append("\n") + } else { + ret.append("
\n") + } + continue + } else if c == UnicodeScalar(13) { + lastWasCR = true + ret.append("
\r") + continue + } + lastWasCR = false + if c < UnicodeScalar(0x0009) { + if let scale = UnicodeScalar(0x0030 + UInt32(c)) { + ret.append("&#x") + ret.append(String(Character(scale))) + ret.append(";") + } + } else if c == UnicodeScalar(0x0022) { + ret.append(""") + } else if c == UnicodeScalar(0x0026) { + ret.append("&") + } else if c == UnicodeScalar(0x0027) { + ret.append("'") + } else if c == UnicodeScalar(0x003C) { + ret.append("<") + } else if c == UnicodeScalar(0x003E) { + ret.append(">") + } else if c > UnicodeScalar(126) { + ret.append("&#\(UInt32(c));") + } else { + ret.append(String(Character(c))) + } + } + return ret + } + + /// Returns the String with all special URL characters encoded. + public var stringByEncodingURL: String { + var ret = "" + var g = self.utf8.makeIterator() + while let c = g.next() { + if c.shouldURLEncode { + ret.append(String(Character(UnicodeScalar(37)))) + ret.append(c.hexString) + } else { + ret.append(String(Character(UnicodeScalar(c)))) + } + } + return ret + } + + // Utility - not sure if it makes the most sense to have here or outside or elsewhere + static func byteFromHexDigits(one c1v: UInt8, two c2v: UInt8) -> UInt8? { + + let capA: UInt8 = 65 + let capF: UInt8 = 70 + let lowA: UInt8 = 97 + let lowF: UInt8 = 102 + let zero: UInt8 = 48 + let nine: UInt8 = 57 + + var newChar = UInt8(0) + + if c1v >= capA && c1v <= capF { + newChar = c1v - capA + 10 + } else if c1v >= lowA && c1v <= lowF { + newChar = c1v - lowA + 10 + } else if c1v >= zero && c1v <= nine { + newChar = c1v - zero + } else { + return nil + } + + newChar *= 16 + + if c2v >= capA && c2v <= capF { + newChar += c2v - capA + 10 + } else if c2v >= lowA && c2v <= lowF { + newChar += c2v - lowA + 10 + } else if c2v >= zero && c2v <= nine { + newChar += c2v - zero + } else { + return nil + } + return newChar + } + + /// Decode the % encoded characters in a URL and return result + public var stringByDecodingURL: String? { + + let percent: UInt8 = 37 + let plus: UInt8 = 43 + let space: UInt8 = 32 + + var bytesArray = [UInt8]() + + var g = self.utf8.makeIterator() + while let c = g.next() { + if c == percent { + guard let c1v = g.next() else { + return nil + } + guard let c2v = g.next() else { + return nil + } + guard let newChar = String.byteFromHexDigits(one: c1v, two: c2v) else { + return nil + } + bytesArray.append(newChar) + } else if c == plus { + bytesArray.append(space) + } else { + bytesArray.append(c) + } + } + return UTF8Encoding.encode(bytes: bytesArray) + } + + /// Decode a hex string into resulting byte array + public var decodeHex: [UInt8]? { + + var bytesArray = [UInt8]() + var g = self.utf8.makeIterator() + while let c1v = g.next() { + + guard let c2v = g.next() else { + return nil + } + + guard let newChar = String.byteFromHexDigits(one: c1v, two: c2v) else { + return nil + } + + bytesArray.append(newChar) + } + return bytesArray + } +} + +public struct UUID { + let uuid: uuid_t + + public init() { + let u = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) + defer { + u.deallocate(capacity: MemoryLayout.size) + } + uuid_generate_random(u) + self.uuid = UUID.uuidFromPointer(u) + } + + public init(_ string: String) { + let u = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) + defer { + u.deallocate(capacity: MemoryLayout.size) + } + uuid_parse(string, u) + self.uuid = UUID.uuidFromPointer(u) + } + + init(_ uuid: uuid_t) { + self.uuid = uuid + } + + private static func uuidFromPointer(_ u: UnsafeMutablePointer) -> uuid_t { + // is there a better way? + return uuid_t(u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]) + } + + public var string: String { + let u = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) + let unu = UnsafeMutablePointer.allocate(capacity: 37) // as per spec. 36 + null + defer { + u.deallocate(capacity: MemoryLayout.size) + unu.deallocate(capacity: 37) + } + var uu = self.uuid + memcpy(u, &uu, MemoryLayout.size) + uuid_unparse_lower(u, unu) + return String(validatingUTF8: unu)! + } +} + +extension String { + + @available(*, unavailable, message: "Use UUID(_:String)") + public func asUUID() -> uuid_t { + return UUID(self).uuid + } + + @available(*, unavailable, message: "Use UUID.string") + public static func fromUUID(uuid: uuid_t) -> String { + return UUID(uuid).string + } +} + +@available(*, unavailable, renamed: "UUID()") +public func random_uuid() -> uuid_t { + return UUID().uuid +} + +extension String { + + /// Parse an HTTP Digest authentication header returning a Dictionary containing each part. + public func parseAuthentication() -> [String:String] { + var ret = [String:String]() + if let _ = self.range(ofString: "Digest ") { + ret["type"] = "Digest" + let wantFields = ["username", "nonce", "nc", "cnonce", "response", "uri", "realm", "qop", "algorithm"] + for field in wantFields { + if let foundField = String.extractField(from: self, named: field) { + ret[field] = foundField + } + } + } + return ret + } + + private static func extractField(from frm: String, named: String) -> String? { + guard let range = frm.range(ofString: named + "=") else { + return nil + } + + var currPos = range.upperBound + var ret = "" + let quoted = frm[currPos] == "\"" + if quoted { + currPos = frm.index(after: currPos) + let tooFar = frm.endIndex + while currPos != tooFar { + if frm[currPos] == "\"" { + break + } + ret.append(frm[currPos]) + currPos = frm.index(after: currPos) + } + } else { + let tooFar = frm.endIndex + while currPos != tooFar { + if frm[currPos] == "," { + break + } + ret.append(frm[currPos]) + currPos = frm.index(after: currPos) + } + } + return ret + } +} + +extension String { + + /// Replace all occurrences of `string` with `withString`. + public func stringByReplacing(string strng: String, withString: String) -> String { + + guard !strng.isEmpty else { + return self + } + guard !self.isEmpty else { + return self + } + + var ret = "" + var idx = self.startIndex + let endIdx = self.endIndex + + while idx != endIdx { + if self[idx] == strng[strng.startIndex] { + var newIdx = self.index(after: idx) + var findIdx = strng.index(after: strng.startIndex) + let findEndIdx = strng.endIndex + + while newIdx != endIndex && findIdx != findEndIdx && self[newIdx] == strng[findIdx] { + newIdx = self.index(after: newIdx) + findIdx = strng.index(after: findIdx) + } + + if findIdx == findEndIdx { // match + ret.append(withString) + idx = newIdx + continue + } + } + ret.append(self[idx]) + idx = self.index(after: idx) + } + + return ret + } + + // For compatibility due to shifting swift + public func contains(string strng: String) -> Bool { + return nil != self.range(ofString: strng) + } +} + +extension String { + func begins(with str: String) -> Bool { + return self.characters.starts(with: str.characters) + } + + func ends(with str: String) -> Bool { + let mine = self.characters + let theirs = str.characters + + guard mine.count >= theirs.count else { + return false + } + + return str.begins(with: self[self.index(self.endIndex, offsetBy: -theirs.count).. Double { + + var posixTime = timeval() + gettimeofday(&posixTime, nil) + return Double((posixTime.tv_sec * 1000) + (Int(posixTime.tv_usec)/1000)) +} +/// Converts the milliseconds based ICU date to seconds since the epoch +public func icuDateToSeconds(_ icuDate: Double) -> Int { + return Int(icuDate / 1000) +} +/// Converts the seconds since the epoch into the milliseconds based ICU date +public func secondsToICUDate(_ seconds: Int) -> Double { + return Double(seconds * 1000) +} + +/// Format a date value according to the indicated format string and return a date string. +/// - parameter date: The date value +/// - parameter format: The format by which the date will be formatted +/// - parameter timezone: The optional timezone in which the date is expected to be based. Default is the local timezone. +/// - parameter locale: The optional locale which will be used when parsing the date. Default is the current global locale. +/// - returns: The resulting date string +/// - throws: `PerfectError.ICUError` +/// - Seealso [Date Time Format Syntax](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax) +public func formatDate(_ date: Double, format: String, timezone inTimezone: String? = nil, locale inLocale: String? = nil) throws -> String { + + var t = tm() + var time = time_t(date / 1000.0) + gmtime_r(&time, &t) + let maxResults = 1024 + let results = UnsafeMutablePointer.allocate(capacity: maxResults) + defer { + results.deallocate(capacity: maxResults) + } + let res = strftime(results, maxResults, format, &t) + if res > 0 { + let formatted = String(validatingUTF8: results) + return formatted! + } + try ThrowSystemError() +} + +extension UnicodeScalar { + + /// Returns true if the UnicodeScalar is a white space character + public func isWhiteSpace() -> Bool { + return isspace(Int32(self.value)) != 0 + } + /// Returns true if the UnicodeScalar is a digit character + public func isDigit() -> Bool { + return isdigit(Int32(self.value)) != 0 + } + /// Returns true if the UnicodeScalar is an alpha-numeric character + public func isAlphaNum() -> Bool { + return isalnum(Int32(self.value)) != 0 + } + /// Returns true if the UnicodeScalar is a hexadecimal character + public func isHexDigit() -> Bool { + if self.isDigit() { + return true + } + switch self { + case "A", "B", "C", "D", "E", "F", "a", "b", "c", "d", "e", "f": + return true + default: + return false + } + } +} + +//public extension NetNamedPipe { +// /// Send the existing & opened `File`'s descriptor over the connection to the recipient +// /// - parameter file: The `File` whose descriptor to send +// /// - parameter callBack: The callback to call when the send completes. The parameter passed will be `true` if the send completed without error. +// /// - throws: `PerfectError.NetworkError` +// public func sendFile(_ file: File, callBack: @escaping (Bool) -> ()) throws { +// try self.sendFd(Int32(file.fd), callBack: callBack) +// } +// +// /// Receive an existing opened `File` descriptor from the sender +// /// - parameter callBack: The callback to call when the receive completes. The parameter passed will be the received `File` object or nil. +// /// - throws: `PerfectError.NetworkError` +// public func receiveFile(callBack: @escaping (File?) -> ()) throws { +// try self.receiveFd { +// fd in +// +// if fd == invalidSocket { +// callBack(nil) +// } else { +// callBack(File("", fd: fd)) +// } +// } +// } +//} +// +//import OpenSSL +// +//extension String.UTF8View { +// var sha1: [UInt8] { +// let bytes = UnsafeMutablePointer.allocate(capacity: Int(SHA_DIGEST_LENGTH)) +// defer { bytes.deallocate(capacity: Int(SHA_DIGEST_LENGTH)) } +// +// SHA1(Array(self), (self.count), bytes) +// +// var r = [UInt8]() +// for idx in 0.. 0 else { + return false + } + return unis[unis.startIndex] == Character(filePathSeparator) + } + + public var endsWithFilePathSeparator: Bool { + let unis = self.characters + guard unis.count > 0 else { + return false + } + return unis[unis.index(before: unis.endIndex)] == Character(filePathSeparator) + } + + private func filePathComponents(addFirstLast addfl: Bool) -> [String] { + var r = [String]() + let unis = self.characters + guard unis.count > 0 else { + return r + } + + if addfl && self.beginsWithFilePathSeparator { + r.append(String(filePathSeparator)) + } + + r.append(contentsOf: self.characters.split(separator: Character(filePathSeparator)).map { String($0) }) + + if addfl && self.endsWithFilePathSeparator { + if !self.beginsWithFilePathSeparator || r.count > 1 { + r.append(String(filePathSeparator)) + } + } + return r + } + + public var filePathComponents: [String] { + return self.filePathComponents(addFirstLast: true) + } + + public var lastFilePathComponent: String { + let last = self.filePathComponents(addFirstLast: false).last ?? "" + if last.isEmpty && self.characters.first == Character(filePathSeparator) { + return String(filePathSeparator) + } + return last + } + + public var deletingLastFilePathComponent: String { + var comps = self.filePathComponents(addFirstLast: false) + guard comps.count > 1 else { + if self.beginsWithFilePathSeparator { + return String(filePathSeparator) + } + return "" + } + comps.removeLast() + let joined = comps.joined(separator: String(filePathSeparator)) + if self.beginsWithFilePathSeparator { + return String(filePathSeparator) + joined + } + return joined + } + + private func lastPathSeparator(in unis: String.CharacterView) -> String.CharacterView.Index { + let startIndex = unis.startIndex + var endIndex = unis.endIndex + while endIndex != startIndex { + if unis[unis.index(before: endIndex)] != Character(filePathSeparator) { + break + } + endIndex = unis.index(before: endIndex) + } + return endIndex + } + + private func lastExtensionSeparator(in unis: String.CharacterView, endIndex: String.CharacterView.Index) -> String.CharacterView.Index { + var endIndex = endIndex + while endIndex != startIndex { + endIndex = unis.index(before: endIndex) + if unis[endIndex] == Character(fileExtensionSeparator) { + break + } + } + return endIndex + } + + public var deletingFileExtension: String { + let unis = self.characters + let startIndex = unis.startIndex + var endIndex = lastPathSeparator(in: unis) + let noTrailsIndex = endIndex + endIndex = lastExtensionSeparator(in: unis, endIndex: endIndex) + guard endIndex != startIndex else { + if noTrailsIndex == startIndex { + return self + } + return self[startIndex.. [String : Any] { + return [JSONDecoding.objectIdentifierKey:Test.registerName, "One":1] + } + } + + JSONDecoding.registerJSONDecodable(name: Test.registerName, creator: { return Test() }) + + do { + let encoded = try Test().jsonEncodedString() + let decoded = try encoded.jsonDecode() as? Test + + XCTAssert(decoded != nil) + + XCTAssert(decoded!.one == 1) + } catch { + XCTAssert(false, "Exception \(error)") + } + } + + func testJSONConvertibleObject2() { + + class User: JSONConvertibleObject { + static let registerName = "user" + var firstName = "" + var lastName = "" + var age = 0 + override func setJSONValues(_ values: [String : Any]) { + self.firstName = getJSONValue(named: "firstName", from: values, defaultValue: "") + self.lastName = getJSONValue(named: "lastName", from: values, defaultValue: "") + self.age = getJSONValue(named: "age", from: values, defaultValue: 0) + } + override func getJSONValues() -> [String : Any] { + return [ + JSONDecoding.objectIdentifierKey:User.registerName, + "firstName":firstName, + "lastName":lastName, + "age":age + ] + } + } + + // register the class. do this once + JSONDecoding.registerJSONDecodable(name: User.registerName, creator: { return User() }) + + // encode and decode the object + let user = User() + user.firstName = "Donnie" + user.lastName = "Darko" + user.age = 17 + + do { + let encoded = try user.jsonEncodedString() + print(encoded) + + guard let user2 = try encoded.jsonDecode() as? User else { + return XCTAssert(false, "Invalid object \(encoded)") + } + + XCTAssert(user.firstName == user2.firstName) + XCTAssert(user.lastName == user2.lastName) + XCTAssert(user.age == user2.age) + } catch {} + } + + func testJSONEncodeDecode() { + + let srcAry: [[String:Any]] = [["i": -41451, "i2": 41451, "d": -42E+2, "t": true, "f": false, "n": nil as String?, "a":[1, 2, 3, 4]], ["another":"one"]] + var encoded = "" + var decoded: [Any]? + do { + + encoded = try srcAry.jsonEncodedString() + + } catch let e { + XCTAssert(false, "Exception while encoding JSON \(e)") + return + } + + do { + + decoded = try encoded.jsonDecode() as? [Any] + + } catch let e { + XCTAssert(false, "Exception while decoding JSON \(e)") + return + } + + XCTAssert(decoded != nil) + + let resAry = decoded! + + XCTAssert(srcAry.count == resAry.count) + + for index in 0.. () in +// let n = inn as? NetNamedPipe +// XCTAssertNotNil(n) +// +// do { +// try n?.sendFile(testFile) { +// (b: Bool) in +// +// XCTAssertTrue(b) +// +// n!.close() +// +// serverExpectation.fulfill() +// } +// } catch let e { +// XCTAssert(false, "Exception accepting connection: \(e)") +// serverExpectation.fulfill() +// } +// } +// +// try client.connect(address: sock, timeoutSeconds: 5) { +// (inn: NetTCP?) -> () in +// let n = inn as? NetNamedPipe +// XCTAssertNotNil(n) +// do { +// try n!.receiveFile { +// f in +// +// XCTAssertNotNil(f) +// do { +// let testDataRead = try f!.readSomeBytes(count: f!.size) +// if testDataRead.count > 0 { +// XCTAssertEqual(UTF8Encoding.encode(bytes: testDataRead), testContents) +// } else { +// XCTAssertTrue(false, "Got no data from received file") +// } +// f!.close() +// } catch let e { +// XCTAssert(false, "Exception in connection: \(e)") +// } +// clientExpectation.fulfill() +// } +// } catch let e { +// XCTAssert(false, "Exception in connection: \(e)") +// clientExpectation.fulfill() +// } +// } +// self.waitForExpectations(timeout: 10000) { +// _ in +// server.close() +// client.close() +// testFile.close() +// testFile.delete() +// } +// } catch PerfectError.networkError(let code, let msg) { +// XCTAssert(false, "Exception: \(code) \(msg)") +// } catch let e { +// XCTAssert(false, "Exception: \(e)") +// } +// } + + func testSysProcess() { +#if !Xcode // this always fails in Xcode but passes on the cli and on Linux. + // I think it's some interaction with the debugger. System call interrupted. + do { + let proc = try SysProcess("ls", args:["-l", "/"], env:[("PATH", "/usr/bin:/bin")]) + + XCTAssertTrue(proc.isOpen()) + XCTAssertNotNil(proc.stdin) + + let fileOut = proc.stdout! + let data = try fileOut.readSomeBytes(count: 4096) + + XCTAssertTrue(data.count > 0) + + let waitRes = try proc.wait() + + XCTAssert(0 == waitRes, "\(waitRes) \(UTF8Encoding.encode(bytes: data))") + + proc.close() + } catch { + XCTAssert(false, "Exception running SysProcess test: \(error)") + } +#endif + } + + func testStringByEncodingHTML() { + let src = "\"quoted\" '& ☃" + let res = src.stringByEncodingHTML + XCTAssertEqual(res, "<b>"quoted" '& ☃") + } + + func testStringByEncodingURL() { + let src = "This has \"weird\" characters & ßtuff" + let res = src.stringByEncodingURL + XCTAssertEqual(res, "This%20has%20%22weird%22%20characters%20&%20%C3%9Ftuff") + } + + func testStringByDecodingURL() { + let src = "This has \"weird\" characters & ßtuff" + let mid = src.stringByEncodingURL + guard let res = mid.stringByDecodingURL else { + XCTAssert(false, "Got nil String") + return + } + XCTAssert(res == src, "Bad URL decoding") + } + + func testStringByDecodingURL2() { + let src = "This is badly%PWencoded" + let res = src.stringByDecodingURL + + XCTAssert(res == nil, "Bad URL decoding") + } + + func testStringByReplacingString() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + let test = "ABCFEDGHIJKLMNOPQRSTUVWXYZABCFEDGHIJKLMNOPQRSTUVWXYZABCFEDGHIJKLMNOPQRSTUVWXYZ" + let find = "DEF" + let rep = "FED" + + let res = src.stringByReplacing(string: find, withString: rep) + + XCTAssert(res == test) + } + + func testStringByReplacingString2() { + + let src = "" + let find = "DEF" + let rep = "FED" + + let res = src.stringByReplacing(string: find, withString: rep) + + XCTAssert(res == src) + } + + func testStringByReplacingString3() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + let find = "" + let rep = "FED" + + let res = src.stringByReplacing(string: find, withString: rep) + + XCTAssert(res == src) + } + + func testSubstringTo() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + let res = src.substring(to: src.index(src.startIndex, offsetBy: 5)) + + XCTAssert(res == "ABCDE") + } + + func testRangeTo() { + + let src = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + let res = src.range(ofString: "DEF") + XCTAssert(res == src.index(src.startIndex, offsetBy: 3)...size + XCTAssert(i16 == bytes2.export16Bits()) + XCTAssert(bytes2.availableExportBytes == 1) + XCTAssert(i8 == bytes2.export8Bits()) + } +} + +extension PerfectLibTests { + static var allTests : [(String, (PerfectLibTests) -> () throws -> Void)] { + return [ + ("testJSONConvertibleObject1", testJSONConvertibleObject1), + ("testJSONConvertibleObject2", testJSONConvertibleObject2), + ("testJSONEncodeDecode", testJSONEncodeDecode), + ("testJSONDecodeUnicode", testJSONDecodeUnicode), + ("testSysProcess", testSysProcess), + ("testStringByEncodingHTML", testStringByEncodingHTML), + ("testStringByEncodingURL", testStringByEncodingURL), + ("testStringByDecodingURL", testStringByDecodingURL), + ("testStringByDecodingURL2", testStringByDecodingURL2), + ("testStringByReplacingString", testStringByReplacingString), + ("testStringByReplacingString2", testStringByReplacingString2), + ("testStringByReplacingString3", testStringByReplacingString3), + ("testSubstringTo", testSubstringTo), + ("testRangeTo", testRangeTo), + ("testSubstringWith", testSubstringWith), + + ("testDeletingPathExtension", testDeletingPathExtension), + ("testGetPathExtension", testGetPathExtension), + + ("testDirCreate", testDirCreate), + ("testDirCreateRel", testDirCreateRel), + ("testDirForEach", testDirForEach), + + ("testFilePerms", testFilePerms), + ("testDirPerms", testDirPerms), + + ("testBytesIO", testBytesIO) + ] + } +} diff --git a/Tests/PerfectLibTests/XCTestManifests.swift b/Tests/PerfectLibTests/XCTestManifests.swift new file mode 100644 index 00000000..6edb6a7c --- /dev/null +++ b/Tests/PerfectLibTests/XCTestManifests.swift @@ -0,0 +1,27 @@ +// +// XCTestManifests.swift +// +// Created by Kyle Jessup on 2015-10-19. +// Copyright © 2015 PerfectlySoft. All rights reserved. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import XCTest + +#if !os(OSX) +public func allTests() -> [XCTestCaseEntry] { + return [ + testCase(PerfectLibTests.allTests) + ] +} +#endif