blob: 833b4370ed200b5184005315d471d63ddabf707f [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
import CoreFoundation
#if os(OSX) || os(iOS)
import Darwin
#elseif os(Linux) || CYGWIN
import Glibc
#endif
#if DEPLOYMENT_ENABLE_LIBDISPATCH
import Dispatch
#endif
extension NSData {
public struct ReadingOptions : OptionSet {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let mappedIfSafe = ReadingOptions(rawValue: UInt(1 << 0))
public static let uncached = ReadingOptions(rawValue: UInt(1 << 1))
public static let alwaysMapped = ReadingOptions(rawValue: UInt(1 << 2))
}
public struct WritingOptions : OptionSet {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let atomic = WritingOptions(rawValue: UInt(1 << 0))
public static let withoutOverwriting = WritingOptions(rawValue: UInt(1 << 1))
}
public struct SearchOptions : OptionSet {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let backwards = SearchOptions(rawValue: UInt(1 << 0))
public static let anchored = SearchOptions(rawValue: UInt(1 << 1))
}
public struct Base64EncodingOptions : OptionSet {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let lineLength64Characters = Base64EncodingOptions(rawValue: UInt(1 << 0))
public static let lineLength76Characters = Base64EncodingOptions(rawValue: UInt(1 << 1))
public static let endLineWithCarriageReturn = Base64EncodingOptions(rawValue: UInt(1 << 4))
public static let endLineWithLineFeed = Base64EncodingOptions(rawValue: UInt(1 << 5))
}
public struct Base64DecodingOptions : OptionSet {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let ignoreUnknownCharacters = Base64DecodingOptions(rawValue: UInt(1 << 0))
}
}
private final class _NSDataDeallocator {
var handler: (UnsafeMutableRawPointer, Int) -> Void = {_,_ in }
}
private let __kCFMutable: CFOptionFlags = 0x01
private let __kCFGrowable: CFOptionFlags = 0x02
private let __kCFMutableVarietyMask: CFOptionFlags = 0x03
private let __kCFBytesInline: CFOptionFlags = 0x04
private let __kCFUseAllocator: CFOptionFlags = 0x08
private let __kCFDontDeallocate: CFOptionFlags = 0x10
private let __kCFAllocatesCollectable: CFOptionFlags = 0x20
open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
typealias CFType = CFData
private var _base = _CFInfo(typeID: CFDataGetTypeID())
private var _length: CFIndex = 0
private var _capacity: CFIndex = 0
private var _deallocator: UnsafeMutableRawPointer? = nil // for CF only
private var _deallocHandler: _NSDataDeallocator? = _NSDataDeallocator() // for Swift
private var _bytes: UnsafeMutablePointer<UInt8>? = nil
internal var _cfObject: CFType {
if type(of: self) === NSData.self || type(of: self) === NSMutableData.self {
return unsafeBitCast(self, to: CFType.self)
} else {
let bytePtr = self.bytes.bindMemory(to: UInt8.self, capacity: self.length)
return CFDataCreate(kCFAllocatorSystemDefault, bytePtr, self.length)
}
}
internal func _providesConcreteBacking() -> Bool {
return type(of: self) === NSData.self || type(of: self) === NSMutableData.self
}
override open var _cfTypeID: CFTypeID {
return CFDataGetTypeID()
}
// NOTE: the deallocator block here is implicitly @escaping by virtue of it being optional
private func _init(bytes: UnsafeMutableRawPointer?, length: Int, copy: Bool = false, deallocator: ((UnsafeMutableRawPointer, Int) -> Void)? = nil) {
let options : CFOptionFlags = (type(of: self) == NSMutableData.self) ? __kCFMutable | __kCFGrowable : 0x0
let bytePtr = bytes?.bindMemory(to: UInt8.self, capacity: length)
if copy {
_CFDataInit(unsafeBitCast(self, to: CFMutableData.self), options, length, bytePtr, length, false)
if let handler = deallocator {
handler(bytes!, length)
}
} else {
if let handler = deallocator {
_deallocHandler!.handler = handler
}
// The data initialization should flag that CF should not deallocate which leaves the handler a chance to deallocate instead
_CFDataInit(unsafeBitCast(self, to: CFMutableData.self), options | __kCFDontDeallocate, length, bytePtr, length, true)
}
}
fileprivate init(bytes: UnsafeMutableRawPointer?, length: Int, copy: Bool = false, deallocator: ((UnsafeMutableRawPointer, Int) -> Void)? = nil) {
super.init()
_init(bytes: bytes, length: length, copy: copy, deallocator: deallocator)
}
public override init() {
super.init()
_init(bytes: nil, length: 0)
}
/// Initializes a data object filled with a given number of bytes copied from a given buffer.
public init(bytes: UnsafeRawPointer?, length: Int) {
super.init()
_init(bytes: UnsafeMutableRawPointer(mutating: bytes), length: length, copy: true)
}
/// Initializes a data object filled with a given number of bytes of data from a given buffer.
public init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int) {
super.init()
_init(bytes: bytes, length: length)
}
/// Initializes a data object filled with a given number of bytes of data from a given buffer.
public init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, freeWhenDone: Bool) {
super.init()
_init(bytes: bytes, length: length, copy: false) { buffer, length in
if freeWhenDone {
free(buffer)
}
}
}
/// Initializes a data object filled with a given number of bytes of data from a given buffer, with a custom deallocator block.
/// NOTE: the deallocator block here is implicitly @escaping by virtue of it being optional
public init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, deallocator: ((UnsafeMutableRawPointer, Int) -> Void)? = nil) {
super.init()
_init(bytes: bytes, length: length, copy: false, deallocator: deallocator)
}
/// Initializes a data object with the contents of the file at a given path.
public init(contentsOfFile path: String, options readOptionsMask: ReadingOptions = []) throws {
super.init()
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: readOptionsMask)
_init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
}
/// Initializes a data object with the contents of the file at a given path.
public init?(contentsOfFile path: String) {
do {
super.init()
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: [])
_init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
} catch {
return nil
}
}
/// Initializes a data object with the contents of another data object.
public init(data: Data) {
super.init()
_init(bytes: UnsafeMutableRawPointer(mutating: data._nsObject.bytes), length: length, copy: true)
}
/// Initializes a data object with the data from the location specified by a given URL.
public init(contentsOf url: URL, options readOptionsMask: ReadingOptions = []) throws {
super.init()
try _contentsOf(url: url, options: readOptionsMask)
}
/// Initializes a data object with the data from the location specified by a given URL.
public init?(contentsOf url: URL) {
super.init()
do {
try _contentsOf(url: url)
} catch {
return nil
}
}
/// Initializes a data object with the data from the location specified by a given URL.
private func _contentsOf(url: URL, options readOptionsMask: ReadingOptions = []) throws {
if url.isFileURL {
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(url.path, options: readOptionsMask)
_init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
} else {
let session = URLSession(configuration: URLSessionConfiguration.default)
let cond = NSCondition()
var resError: Error?
var resData: Data?
let task = session.dataTask(with: url, completionHandler: { data, response, error in
resData = data
resError = error
cond.broadcast()
})
task.resume()
cond.wait()
guard let data = resData else {
throw resError!
}
_init(bytes: UnsafeMutableRawPointer(mutating: data._nsObject.bytes), length: length, copy: true)
}
}
/// Initializes a data object with the given Base64 encoded string.
public init?(base64Encoded base64String: String, options: Base64DecodingOptions = []) {
let encodedBytes = Array(base64String.utf8)
guard let decodedBytes = NSData.base64DecodeBytes(encodedBytes, options: options) else {
return nil
}
super.init()
_init(bytes: UnsafeMutableRawPointer(mutating: decodedBytes), length: decodedBytes.count, copy: true)
}
/// Initializes a data object with the given Base64 encoded data.
public init?(base64Encoded base64Data: Data, options: Base64DecodingOptions = []) {
var encodedBytes = [UInt8](repeating: 0, count: base64Data.count)
base64Data._nsObject.getBytes(&encodedBytes, length: encodedBytes.count)
guard let decodedBytes = NSData.base64DecodeBytes(encodedBytes, options: options) else {
return nil
}
super.init()
_init(bytes: UnsafeMutableRawPointer(mutating: decodedBytes), length: decodedBytes.count, copy: true)
}
deinit {
if let allocatedBytes = _bytes {
_deallocHandler?.handler(allocatedBytes, _length)
}
if type(of: self) === NSData.self || type(of: self) === NSMutableData.self {
_CFDeinit(self._cfObject)
}
}
// MARK: - Funnel methods
/// The number of bytes contained by the data object.
open var length: Int {
return CFDataGetLength(_cfObject)
}
/// A pointer to the data object's contents.
open var bytes: UnsafeRawPointer {
guard let bytePtr = CFDataGetBytePtr(_cfObject) else {
//This could occure on empty data being encoded.
//TODO: switch with nil when signature is fixed
return UnsafeRawPointer(bitPattern: 0x7f00dead)! //would not result in 'nil unwrapped optional'
}
return UnsafeRawPointer(bytePtr)
}
// MARK: - NSObject methods
open override var hash: Int {
return Int(bitPattern: CFHash(_cfObject))
}
/// Returns a Boolean value indicating whether this data object is the same as another.
open override func isEqual(_ value: Any?) -> Bool {
if let data = value as? Data {
return isEqual(to: data)
} else if let data = value as? NSData {
return isEqual(to: data._swiftObject)
}
#if DEPLOYMENT_ENABLE_LIBDISPATCH
if let data = value as? DispatchData {
if data.count != length {
return false
}
return data.withUnsafeBytes { (bytes2: UnsafePointer<UInt8>) -> Bool in
let bytes1 = bytes
return memcmp(bytes1, bytes2, length) == 0
}
}
#endif
return false
}
open func isEqual(to other: Data) -> Bool {
if length != other.count {
return false
}
return other.withUnsafeBytes { (bytes2: UnsafePointer<UInt8>) -> Bool in
let bytes1 = bytes
return memcmp(bytes1, bytes2, length) == 0
}
}
open override func copy() -> Any {
return copy(with: nil)
}
open func copy(with zone: NSZone? = nil) -> Any {
return self
}
open override func mutableCopy() -> Any {
return mutableCopy(with: nil)
}
open func mutableCopy(with zone: NSZone? = nil) -> Any {
return NSMutableData(bytes: UnsafeMutableRawPointer(mutating: bytes), length: length, copy: true, deallocator: nil)
}
private func byteDescription(limit: Int? = nil) -> String {
var s = ""
var i = 0
while i < self.length {
if i > 0 && i % 4 == 0 {
// if there's a limit, and we're at the barrier where we'd add the ellipses, don't add a space.
if let limit = limit, self.length > limit && i == self.length - (limit / 2) { /* do nothing */ }
else { s += " " }
}
let byte = bytes.load(fromByteOffset: i, as: UInt8.self)
var byteStr = String(byte, radix: 16, uppercase: false)
if byte <= 0xf { byteStr = "0\(byteStr)" }
s += byteStr
// if we've hit the midpoint of the limit, skip to the last (limit / 2) bytes.
if let limit = limit, self.length > limit && i == (limit / 2) - 1 {
s += " ... "
i = self.length - (limit / 2)
} else {
i += 1
}
}
return s
}
override open var debugDescription: String {
return "<\(byteDescription(limit: 1024))>"
}
/// A string that contains a hexadecimal representation of the data object’s contents in a property list format.
override open var description: String {
return "<\(byteDescription())>"
}
// MARK: - NSCoding methods
open func encode(with aCoder: NSCoder) {
if let aKeyedCoder = aCoder as? NSKeyedArchiver {
aKeyedCoder._encodePropertyList(self, forKey: "NS.data")
} else {
let bytePtr = self.bytes.bindMemory(to: UInt8.self, capacity: self.length)
aCoder.encodeBytes(bytePtr, length: self.length)
}
}
public required init?(coder aDecoder: NSCoder) {
super.init()
guard aDecoder.allowsKeyedCoding else {
preconditionFailure("Unkeyed coding is unsupported.")
}
if type(of: aDecoder) == NSKeyedUnarchiver.self || aDecoder.containsValue(forKey: "NS.data") {
guard let data = aDecoder._decodePropertyListForKey("NS.data") as? NSData else {
return nil
}
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
} else {
let result : Data? = aDecoder.withDecodedUnsafeBufferPointer(forKey: "NS.bytes") {
guard let buffer = $0 else { return nil }
return Data(buffer: buffer)
}
guard let r = result else { return nil }
_init(bytes: UnsafeMutableRawPointer(mutating: r._nsObject.bytes), length: r.count, copy: true)
}
}
public static var supportsSecureCoding: Bool {
return true
}
// MARK: - IO
internal struct NSDataReadResult {
var bytes: UnsafeMutableRawPointer
var length: Int
var deallocator: ((_ buffer: UnsafeMutableRawPointer, _ length: Int) -> Void)?
}
internal static func readBytesFromFileWithExtendedAttributes(_ path: String, options: ReadingOptions) throws -> NSDataReadResult {
let fd = _CFOpenFile(path, O_RDONLY)
if fd < 0 {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
defer {
close(fd)
}
var info = stat()
let ret = withUnsafeMutablePointer(to: &info) { infoPointer -> Bool in
if fstat(fd, infoPointer) < 0 {
return false
}
return true
}
if !ret {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
let length = Int(info.st_size)
if length == 0 && (info.st_mode & S_IFMT == S_IFREG) {
return try readZeroSizeFile(fd)
}
if options.contains(.alwaysMapped) {
let data = mmap(nil, length, PROT_READ, MAP_PRIVATE, fd, 0)
// Swift does not currently expose MAP_FAILURE
if data != UnsafeMutableRawPointer(bitPattern: -1) {
return NSDataReadResult(bytes: data!, length: length) { buffer, length in
munmap(buffer, length)
}
}
}
let data = malloc(length)!
var remaining = Int(info.st_size)
var total = 0
while remaining > 0 {
let amt = read(fd, data.advanced(by: total), remaining)
if amt < 0 {
break
}
remaining -= amt
total += amt
}
if remaining != 0 {
free(data)
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
return NSDataReadResult(bytes: data, length: length) { buffer, length in
free(buffer)
}
}
internal static func readZeroSizeFile(_ fd: Int32) throws -> NSDataReadResult {
let blockSize = 1024 * 1024 // 1MB
var data: UnsafeMutableRawPointer? = nil
var bytesRead = 0
var amt = 0
repeat {
data = realloc(data, bytesRead + blockSize)
amt = read(fd, data!.advanced(by: bytesRead), blockSize)
// Dont continue on EINTR or EAGAIN as the file position may not
// have changed, see read(2).
if amt < 0 {
free(data!)
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
bytesRead += amt
} while amt > 0
if bytesRead == 0 {
free(data!)
data = malloc(0)
} else {
data = realloc(data, bytesRead) // shrink down the allocated block.
}
return NSDataReadResult(bytes: data!, length: bytesRead) { buffer, length in
free(buffer)
}
}
internal func makeTemporaryFile(inDirectory dirPath: String) throws -> (Int32, String) {
let template = dirPath._nsObject.appendingPathComponent("tmp.XXXXXX")
let maxLength = Int(PATH_MAX) + 1
var buf = [Int8](repeating: 0, count: maxLength)
let _ = template._nsObject.getFileSystemRepresentation(&buf, maxLength: maxLength)
let fd = mkstemp(&buf)
if fd == -1 {
throw _NSErrorWithErrno(errno, reading: false, path: dirPath)
}
let pathResult = FileManager.default.string(withFileSystemRepresentation:buf, length: Int(strlen(buf)))
return (fd, pathResult)
}
internal class func write(toFileDescriptor fd: Int32, path: String? = nil, buf: UnsafeRawPointer, length: Int) throws {
var bytesRemaining = length
while bytesRemaining > 0 {
var bytesWritten : Int
repeat {
#if os(OSX) || os(iOS)
bytesWritten = Darwin.write(fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
#elseif os(Linux) || os(Android) || CYGWIN
bytesWritten = Glibc.write(fd, buf.advanced(by: length - bytesRemaining), bytesRemaining)
#endif
} while (bytesWritten < 0 && errno == EINTR)
if bytesWritten <= 0 {
throw _NSErrorWithErrno(errno, reading: false, path: path)
} else {
bytesRemaining -= bytesWritten
}
}
}
/// Writes the data object's bytes to the file specified by a given path.
open func write(toFile path: String, options writeOptionsMask: WritingOptions = []) throws {
var fd : Int32
var mode : mode_t? = nil
let useAuxiliaryFile = writeOptionsMask.contains(.atomic)
var auxFilePath : String? = nil
if useAuxiliaryFile {
// Preserve permissions.
var info = stat()
if lstat(path, &info) == 0 {
mode = mode_t(info.st_mode)
} else if errno != ENOENT && errno != ENAMETOOLONG {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
let (newFD, path) = try self.makeTemporaryFile(inDirectory: path._nsObject.deletingLastPathComponent)
fd = newFD
auxFilePath = path
fchmod(fd, 0o666)
} else {
var flags = O_WRONLY | O_CREAT | O_TRUNC
if writeOptionsMask.contains(.withoutOverwriting) {
flags |= O_EXCL
}
fd = _CFOpenFileWithMode(path, flags, 0o666)
}
if fd == -1 {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
defer {
close(fd)
}
try self.enumerateByteRangesUsingBlockRethrows { (buf, range, stop) in
if range.length > 0 {
do {
try NSData.write(toFileDescriptor: fd, path: path, buf: buf, length: range.length)
if fsync(fd) < 0 {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
} catch let err {
if let auxFilePath = auxFilePath {
do {
try FileManager.default.removeItem(atPath: auxFilePath)
} catch _ {}
}
throw err
}
}
}
if let auxFilePath = auxFilePath {
if rename(auxFilePath, path) != 0 {
do {
try FileManager.default.removeItem(atPath: auxFilePath)
} catch _ {}
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
if let mode = mode {
chmod(path, mode)
}
}
}
/// Writes the data object's bytes to the file specified by a given path.
/// NOTE: the 'atomically' flag is ignored if the url is not of a type the supports atomic writes
open func write(toFile path: String, atomically useAuxiliaryFile: Bool) -> Bool {
do {
try write(toFile: path, options: useAuxiliaryFile ? .atomic : [])
} catch {
return false
}
return true
}
/// Writes the data object's bytes to the location specified by a given URL.
/// NOTE: the 'atomically' flag is ignored if the url is not of a type the supports atomic writes
open func write(to url: URL, atomically: Bool) -> Bool {
if url.isFileURL {
return write(toFile: url.path, atomically: atomically)
}
return false
}
/// Writes the data object's bytes to the location specified by a given URL.
///
/// - parameter url: The location to which the data objects's contents will be written.
/// - parameter writeOptionsMask: An option set specifying file writing options.
///
/// - throws: This method returns Void and is marked with the `throws` keyword to indicate that it throws an error in the event of failure.
///
/// This method is invoked in a `try` expression and the caller is responsible for handling any errors in the `catch` clauses of a `do` statement, as described in [Error Handling](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42) in [The Swift Programming Language](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097) and [Error Handling](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10) in [Using Swift with Cocoa and Objective-C](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216).
open func write(to url: URL, options writeOptionsMask: WritingOptions = []) throws {
guard url.isFileURL else {
let userInfo = [NSLocalizedDescriptionKey : "The folder at “\(url)” does not exist or is not a file URL.", // NSLocalizedString() not yet available
NSURLErrorKey : url.absoluteString] as Dictionary<String, Any>
throw NSError(domain: NSCocoaErrorDomain, code: 4, userInfo: userInfo)
}
try write(toFile: url.path, options: writeOptionsMask)
}
// MARK: - Bytes
/// Copies a number of bytes from the start of the data object into a given buffer.
open func getBytes(_ buffer: UnsafeMutableRawPointer, length: Int) {
let bytePtr = buffer.bindMemory(to: UInt8.self, capacity: length)
CFDataGetBytes(_cfObject, CFRangeMake(0, length), bytePtr)
}
/// Copies a range of bytes from the data object into a given buffer.
open func getBytes(_ buffer: UnsafeMutableRawPointer, range: NSRange) {
let bytePtr = buffer.bindMemory(to: UInt8.self, capacity: range.length)
CFDataGetBytes(_cfObject, CFRangeMake(range.location, range.length), bytePtr)
}
/// Returns a new data object containing the data object's bytes that fall within the limits specified by a given range.
open func subdata(with range: NSRange) -> Data {
if range.length == 0 {
return Data()
}
if range.location == 0 && range.length == self.length {
return Data(referencing: self)
}
let p = self.bytes.advanced(by: range.location).bindMemory(to: UInt8.self, capacity: range.length)
return Data(bytes: p, count: range.length)
}
/// Finds and returns the range of the first occurrence of the given data, within the given range, subject to given options.
open func range(of dataToFind: Data, options mask: SearchOptions = [], in searchRange: NSRange) -> NSRange {
let dataToFind = dataToFind._nsObject
guard dataToFind.length > 0 else {return NSRange(location: NSNotFound, length: 0)}
guard let searchRange = Range(searchRange) else {fatalError("invalid range")}
precondition(searchRange.upperBound <= self.length, "range outside the bounds of data")
let basePtr = self.bytes.bindMemory(to: UInt8.self, capacity: self.length)
let baseData = UnsafeBufferPointer<UInt8>(start: basePtr, count: self.length)[searchRange]
let searchPtr = dataToFind.bytes.bindMemory(to: UInt8.self, capacity: dataToFind.length)
let search = UnsafeBufferPointer<UInt8>(start: searchPtr, count: dataToFind.length)
let location : Int?
let anchored = mask.contains(.anchored)
if mask.contains(.backwards) {
location = NSData.searchSubSequence(search.reversed(), inSequence: baseData.reversed(),anchored : anchored).map {$0.base-search.count}
} else {
location = NSData.searchSubSequence(search, inSequence: baseData,anchored : anchored)
}
return location.map {NSRange(location: $0, length: search.count)} ?? NSRange(location: NSNotFound, length: 0)
}
private static func searchSubSequence<T : Collection, T2 : Sequence>(_ subSequence : T2, inSequence seq: T,anchored : Bool) -> T.Index? where T.Iterator.Element : Equatable, T.Iterator.Element == T2.Iterator.Element {
for index in seq.indices {
if seq.suffix(from: index).starts(with: subSequence) {
return index
}
if anchored {return nil}
}
return nil
}
internal func enumerateByteRangesUsingBlockRethrows(_ block: (UnsafeRawPointer, NSRange, UnsafeMutablePointer<Bool>) throws -> Void) throws {
var err : Swift.Error? = nil
self.enumerateBytes() { (buf, range, stop) -> Void in
do {
try block(buf, range, stop)
} catch let e {
err = e
}
}
if let err = err {
throw err
}
}
/// Enumerates each range of bytes in the data object using a block.
/// 'block' is called once for each contiguous region of memory in the data object (once total for contiguous NSDatas),
/// until either all bytes have been enumerated, or the 'stop' parameter is set to true.
open func enumerateBytes(_ block: (UnsafeRawPointer, NSRange, UnsafeMutablePointer<Bool>) -> Void) {
var stop = false
withUnsafeMutablePointer(to: &stop) { stopPointer in
if (stopPointer.pointee) {
return
}
block(bytes, NSMakeRange(0, length), stopPointer)
}
}
// MARK: - Base64 Methods
/// Creates a Base64 encoded String from the data object using the given options.
open func base64EncodedString(options: Base64EncodingOptions = []) -> String {
var decodedBytes = [UInt8](repeating: 0, count: self.length)
getBytes(&decodedBytes, length: decodedBytes.count)
let encodedBytes = NSData.base64EncodeBytes(decodedBytes, options: options)
let characters = encodedBytes.map { Character(UnicodeScalar($0)) }
return String(characters)
}
/// Creates a Base64, UTF-8 encoded Data from the data object using the given options.
open func base64EncodedData(options: Base64EncodingOptions = []) -> Data {
var decodedBytes = [UInt8](repeating: 0, count: self.length)
getBytes(&decodedBytes, length: decodedBytes.count)
let encodedBytes = NSData.base64EncodeBytes(decodedBytes, options: options)
return Data(bytes: encodedBytes, count: encodedBytes.count)
}
/// The ranges of ASCII characters that are used to encode data in Base64.
private static let base64ByteMappings: [Range<UInt8>] = [
65 ..< 91, // A-Z
97 ..< 123, // a-z
48 ..< 58, // 0-9
43 ..< 44, // +
47 ..< 48, // /
]
/**
Padding character used when the number of bytes to encode is not divisible by 3
*/
private static let base64Padding : UInt8 = 61 // =
/**
This method takes a byte with a character from Base64-encoded string
and gets the binary value that the character corresponds to.
- parameter byte: The byte with the Base64 character.
- returns: Base64DecodedByte value containing the result (Valid , Invalid, Padding)
*/
private enum Base64DecodedByte {
case valid(UInt8)
case invalid
case padding
}
private static func base64DecodeByte(_ byte: UInt8) -> Base64DecodedByte {
guard byte != base64Padding else {return .padding}
var decodedStart: UInt8 = 0
for range in base64ByteMappings {
if range.contains(byte) {
let result = decodedStart + (byte - range.lowerBound)
return .valid(result)
}
decodedStart += range.upperBound - range.lowerBound
}
return .invalid
}
/**
This method takes six bits of binary data and encodes it as a character
in Base64.
The value in the byte must be less than 64, because a Base64 character
can only represent 6 bits.
- parameter byte: The byte to encode
- returns: The ASCII value for the encoded character.
*/
private static func base64EncodeByte(_ byte: UInt8) -> UInt8 {
assert(byte < 64)
var decodedStart: UInt8 = 0
for range in base64ByteMappings {
let decodedRange = decodedStart ..< decodedStart + (range.upperBound - range.lowerBound)
if decodedRange.contains(byte) {
return range.lowerBound + (byte - decodedStart)
}
decodedStart += range.upperBound - range.lowerBound
}
return 0
}
/**
This method decodes Base64-encoded data.
If the input contains any bytes that are not valid Base64 characters,
this will return nil.
- parameter bytes: The Base64 bytes
- parameter options: Options for handling invalid input
- returns: The decoded bytes.
*/
private static func base64DecodeBytes(_ bytes: [UInt8], options: Base64DecodingOptions = []) -> [UInt8]? {
var decodedBytes = [UInt8]()
decodedBytes.reserveCapacity((bytes.count/3)*2)
var currentByte : UInt8 = 0
var validCharacterCount = 0
var paddingCount = 0
var index = 0
for base64Char in bytes {
let value : UInt8
switch base64DecodeByte(base64Char) {
case .valid(let v):
value = v
validCharacterCount += 1
case .invalid:
if options.contains(.ignoreUnknownCharacters) {
continue
} else {
return nil
}
case .padding:
paddingCount += 1
continue
}
//padding found in the middle of the sequence is invalid
if paddingCount > 0 {
return nil
}
switch index%4 {
case 0:
currentByte = (value << 2)
case 1:
currentByte |= (value >> 4)
decodedBytes.append(currentByte)
currentByte = (value << 4)
case 2:
currentByte |= (value >> 2)
decodedBytes.append(currentByte)
currentByte = (value << 6)
case 3:
currentByte |= value
decodedBytes.append(currentByte)
default:
fatalError()
}
index += 1
}
guard (validCharacterCount + paddingCount)%4 == 0 else {
//invalid character count
return nil
}
return decodedBytes
}
/**
This method encodes data in Base64.
- parameter bytes: The bytes you want to encode
- parameter options: Options for formatting the result
- returns: The Base64-encoding for those bytes.
*/
private static func base64EncodeBytes(_ bytes: [UInt8], options: Base64EncodingOptions = []) -> [UInt8] {
var result = [UInt8]()
result.reserveCapacity((bytes.count/3)*4)
let lineOptions : (lineLength : Int, separator : [UInt8])? = {
let lineLength: Int
if options.contains(.lineLength64Characters) { lineLength = 64 }
else if options.contains(.lineLength76Characters) { lineLength = 76 }
else {
return nil
}
var separator = [UInt8]()
if options.contains(.endLineWithCarriageReturn) { separator.append(13) }
if options.contains(.endLineWithLineFeed) { separator.append(10) }
//if the kind of line ending to insert is not specified, the default line ending is Carriage Return + Line Feed.
if separator.isEmpty { separator = [13,10] }
return (lineLength,separator)
}()
var currentLineCount = 0
let appendByteToResult : (UInt8) -> Void = {
result.append($0)
currentLineCount += 1
if let options = lineOptions, currentLineCount == options.lineLength {
result.append(contentsOf: options.separator)
currentLineCount = 0
}
}
var currentByte : UInt8 = 0
for (index,value) in bytes.enumerated() {
switch index%3 {
case 0:
currentByte = (value >> 2)
appendByteToResult(NSData.base64EncodeByte(currentByte))
currentByte = ((value << 6) >> 2)
case 1:
currentByte |= (value >> 4)
appendByteToResult(NSData.base64EncodeByte(currentByte))
currentByte = ((value << 4) >> 2)
case 2:
currentByte |= (value >> 6)
appendByteToResult(NSData.base64EncodeByte(currentByte))
currentByte = ((value << 2) >> 2)
appendByteToResult(NSData.base64EncodeByte(currentByte))
default:
fatalError()
}
}
//add padding
switch bytes.count%3 {
case 0: break //no padding needed
case 1:
appendByteToResult(NSData.base64EncodeByte(currentByte))
appendByteToResult(self.base64Padding)
appendByteToResult(self.base64Padding)
case 2:
appendByteToResult(NSData.base64EncodeByte(currentByte))
appendByteToResult(self.base64Padding)
default:
fatalError()
}
return result
}
}
// MARK: -
extension NSData : _CFBridgeable, _SwiftBridgeable {
typealias SwiftType = Data
internal var _swiftObject: SwiftType { return Data(referencing: self) }
}
extension Data : _NSBridgeable, _CFBridgeable {
typealias CFType = CFData
typealias NSType = NSData
internal var _cfObject: CFType { return _nsObject._cfObject }
internal var _nsObject: NSType { return _bridgeToObjectiveC() }
}
extension CFData : _NSBridgeable, _SwiftBridgeable {
typealias NSType = NSData
typealias SwiftType = Data
internal var _nsObject: NSType { return unsafeBitCast(self, to: NSType.self) }
internal var _swiftObject: SwiftType { return Data(referencing: self._nsObject) }
}
// MARK: -
open class NSMutableData : NSData {
internal var _cfMutableObject: CFMutableData { return unsafeBitCast(self, to: CFMutableData.self) }
public override init() {
super.init(bytes: nil, length: 0)
}
// NOTE: the deallocator block here is implicitly @escaping by virtue of it being optional
public override init(bytes: UnsafeMutableRawPointer?, length: Int, copy: Bool = false, deallocator: (/*@escaping*/ (UnsafeMutableRawPointer, Int) -> Void)? = nil) {
super.init(bytes: bytes, length: length, copy: copy, deallocator: deallocator)
}
/// Initializes a data object filled with a given number of bytes copied from a given buffer.
public override init(bytes: UnsafeRawPointer?, length: Int) {
super.init(bytes: UnsafeMutableRawPointer(mutating: bytes), length: length, copy: true, deallocator: nil)
}
/// Returns an initialized mutable data object capable of holding the specified number of bytes.
public init?(capacity: Int) {
super.init(bytes: nil, length: 0)
}
/// Initializes and returns a mutable data object containing a given number of zeroed bytes.
public init?(length: Int) {
super.init(bytes: nil, length: 0)
self.length = length
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: - Funnel Methods
/// A pointer to the data contained by the mutable data object.
open var mutableBytes: UnsafeMutableRawPointer {
return UnsafeMutableRawPointer(CFDataGetMutableBytePtr(_cfMutableObject))
}
/// The number of bytes contained in the mutable data object.
open override var length: Int {
get {
return CFDataGetLength(_cfObject)
}
set {
CFDataSetLength(_cfMutableObject, newValue)
}
}
// MARK: - NSObject
open override func copy(with zone: NSZone? = nil) -> Any {
return NSData(bytes: bytes, length: length)
}
// MARK: - Mutability
/// Appends to the data object a given number of bytes from a given buffer.
open func append(_ bytes: UnsafeRawPointer, length: Int) {
let bytePtr = bytes.bindMemory(to: UInt8.self, capacity: length)
CFDataAppendBytes(_cfMutableObject, bytePtr, length)
}
/// Appends the content of another data object to the data object.
open func append(_ other: Data) {
let otherLength = other.count
other.withUnsafeBytes {
append($0, length: otherLength)
}
}
/// Increases the length of the data object by a given number of bytes.
open func increaseLength(by extraLength: Int) {
CFDataSetLength(_cfMutableObject, CFDataGetLength(_cfObject) + extraLength)
}
/// Replaces with a given set of bytes a given range within the contents of the data object.
open func replaceBytes(in range: NSRange, withBytes bytes: UnsafeRawPointer) {
let bytePtr = bytes.bindMemory(to: UInt8.self, capacity: length)
CFDataReplaceBytes(_cfMutableObject, CFRangeMake(range.location, range.length), bytePtr, length)
}
/// Replaces with zeroes the contents of the data object in a given range.
open func resetBytes(in range: NSRange) {
bzero(mutableBytes.advanced(by: range.location), range.length)
}
/// Replaces the entire contents of the data object with the contents of another data object.
open func setData(_ data: Data) {
length = data.count
data.withUnsafeBytes {
replaceBytes(in: NSMakeRange(0, length), withBytes: $0)
}
}
/// Replaces with a given set of bytes a given range within the contents of the data object.
open func replaceBytes(in range: NSRange, withBytes replacementBytes: UnsafeRawPointer?, length replacementLength: Int) {
let bytePtr = replacementBytes?.bindMemory(to: UInt8.self, capacity: replacementLength)
CFDataReplaceBytes(_cfMutableObject, CFRangeMake(range.location, range.length), bytePtr, replacementLength)
}
}
extension NSData {
internal func _isCompact() -> Bool {
var regions = 0
enumerateBytes { (_, _, stop) in
regions += 1
if regions > 1 {
stop.pointee = true
}
}
return regions <= 1
}
}
extension NSData : _StructTypeBridgeable {
public typealias _StructType = Data
public func _bridgeToSwift() -> Data {
return Data._unconditionallyBridgeFromObjectiveC(self)
}
}