blob: 3066406b4b54b936a37d1189e68c34406f2c890c [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
public init(bytes: UnsafeMutableRawPointer?, length: Int, copy: Bool = false, deallocator: ((UnsafeMutableRawPointer, Int) -> Void)? = nil) {
super.init()
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)
}
}
public override convenience init() {
let dummyPointer = unsafeBitCast(NSData.self, to: UnsafeMutableRawPointer.self)
self.init(bytes: dummyPointer, length: 0, copy: false, deallocator: nil)
}
public convenience init(bytes: UnsafeRawPointer?, length: Int) {
self.init(bytes: UnsafeMutableRawPointer(mutating: bytes), length: length, copy: true, deallocator: nil)
}
public convenience init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int) {
self.init(bytes: bytes, length: length, copy: false, deallocator: nil)
}
public convenience init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, freeWhenDone b: Bool) {
self.init(bytes: bytes, length: length, copy: false) { buffer, length in
if b {
free(buffer)
}
}
}
// NOTE: the deallocator block here is implicitly @escaping by virtue of it being optional
public convenience init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, deallocator: ((UnsafeMutableRawPointer, Int) -> Void)? = nil) {
self.init(bytes: bytes, length: length, copy: false, deallocator: deallocator)
}
public convenience init(contentsOfFile path: String, options readOptionsMask: ReadingOptions = []) throws {
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: readOptionsMask)
self.init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
}
public convenience init?(contentsOfFile path: String) {
do {
let readResult = try NSData.readBytesFromFileWithExtendedAttributes(path, options: [])
self.init(bytes: readResult.bytes, length: readResult.length, copy: false, deallocator: readResult.deallocator)
} catch {
return nil
}
}
public convenience init(data: Data) {
self.init(bytes:data._nsObject.bytes, length: data.count)
}
public convenience init(contentsOf url: URL, options readOptionsMask: ReadingOptions = []) throws {
if url.isFileURL {
try self.init(contentsOfFile: url.path, options: readOptionsMask)
} 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!
}
self.init(data: data)
}
}
public convenience init?(base64Encoded base64String: String, options: Base64DecodingOptions = []) {
let encodedBytes = Array(base64String.utf8)
guard let decodedBytes = NSData.base64DecodeBytes(encodedBytes, options: options) else {
return nil
}
self.init(bytes: decodedBytes, length: decodedBytes.count)
}
/* Create an NSData from a Base-64, UTF-8 encoded NSData. By default, returns nil when the input is not recognized as valid Base-64.
*/
public convenience 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
}
self.init(bytes: decodedBytes, length: decodedBytes.count)
}
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
open var length: Int {
return CFDataGetLength(_cfObject)
}
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))
}
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))>"
}
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 convenience init?(coder aDecoder: NSCoder) {
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
}
self.init(data: data._swiftObject)
} 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 }
self.init(data: r)
}
}
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
}
}
}
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)
}
}
}
/// 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
}
/// 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
}
/// Write the contents of the receiver to a location specified by the given file URL.
///
/// - parameter url: The location to which the receiver’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
open func getBytes(_ buffer: UnsafeMutableRawPointer, length: Int) {
let bytePtr = buffer.bindMemory(to: UInt8.self, capacity: length)
CFDataGetBytes(_cfObject, CFRangeMake(0, length), bytePtr)
}
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)
}
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)
}
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
}
}
/// 'block' is called once for each contiguous region of memory in the receiver (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
/// Create a Base-64 encoded String from the receiver's contents 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)
}
/// Create a Base-64, UTF-8 encoded Data from the receiver's contents 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) }
// 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)
}
public init() {
self.init(bytes: nil, length: 0)
}
public convenience init?(capacity: Int) {
self.init(bytes: nil, length: 0)
}
public convenience init?(length: Int) {
self.init(bytes: nil, length: 0)
self.length = length
}
// MARK: - Funnel Methods
open var mutableBytes: UnsafeMutableRawPointer {
return UnsafeMutableRawPointer(CFDataGetMutableBytePtr(_cfMutableObject))
}
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
open func append(_ bytes: UnsafeRawPointer, length: Int) {
let bytePtr = bytes.bindMemory(to: UInt8.self, capacity: length)
CFDataAppendBytes(_cfMutableObject, bytePtr, length)
}
open func append(_ other: Data) {
let otherLength = other.count
other.withUnsafeBytes {
append($0, length: otherLength)
}
}
open func increaseLength(by extraLength: Int) {
CFDataSetLength(_cfMutableObject, CFDataGetLength(_cfObject) + extraLength)
}
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)
}
open func resetBytes(in range: NSRange) {
bzero(mutableBytes.advanced(by: range.location), range.length)
}
open func setData(_ data: Data) {
length = data.count
data.withUnsafeBytes {
replaceBytes(in: NSMakeRange(0, length), withBytes: $0)
}
}
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)
}
}