blob: f38558b5738a2d21934147d4f107f0abd1bb0b0c [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
import Dispatch
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 __kCFBytesInline: CFOptionFlags = 2
private let __kCFUseAllocator: CFOptionFlags = 3
private let __kCFDontDeallocate: CFOptionFlags = 4
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()
data.withUnsafeBytes {
_init(bytes: UnsafeMutableRawPointer(mutating: $0.baseAddress), length: $0.count, 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()
let (data, _) = try NSData.contentsOf(url: url, options: readOptionsMask)
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
}
/// Initializes a data object with the data from the location specified by a given URL.
public init?(contentsOf url: URL) {
super.init()
do {
let (data, _) = try NSData.contentsOf(url: url)
_init(bytes: UnsafeMutableRawPointer(mutating: data.bytes), length: data.length, copy: true)
} catch {
return nil
}
}
internal static func contentsOf(url: URL, options readOptionsMask: ReadingOptions = []) throws -> (result: NSData, textEncodingNameIfAvailable: String?) {
if url.isFileURL {
return try url.withUnsafeFileSystemRepresentation { (fsRep) -> (result: NSData, textEncodingNameIfAvailable: String?) in
let data = try NSData.readBytesFromFileWithExtendedAttributes(String(cString: fsRep!), options: readOptionsMask)
return (data.toNSData(), nil)
}
} else {
return try _NSNonfileURLContentLoader.current.contentsOf(url: url)
}
}
/// 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 {
requireFunnelOverridden()
return CFDataGetLength(_cfObject)
}
/// A pointer to the data object's contents.
open var bytes: UnsafeRawPointer {
requireFunnelOverridden()
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: _CFNonObjCHash(_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 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
}
}
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)!
func toNSData() -> NSData {
if bytes == nil {
return NSData()
}
return NSData(bytesNoCopy: bytes!, length: length, deallocator: deallocator)
}
func toData() -> Data {
guard let bytes = bytes else {
return Data()
}
return Data(bytesNoCopy: bytes, count: length, deallocator: Data.Deallocator.custom(deallocator))
}
}
internal static func readBytesFromFileWithExtendedAttributes(_ path: String, options: ReadingOptions) throws -> NSDataReadResult {
guard let handle = FileHandle(path: path, flags: O_RDONLY, createMode: 0) else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: nil)
}
let result = try handle._readDataOfLength(Int.max, untilEOF: true)
return result
}
/// Writes the data object's bytes to the file specified by a given path.
open func write(toFile path: String, options writeOptionsMask: WritingOptions = []) throws {
func doWrite(_ fh: FileHandle) throws {
try self.enumerateByteRangesUsingBlockRethrows { (buf, range, stop) in
if range.length > 0 {
try fh._writeBytes(buf: buf, length: range.length)
}
}
try fh.synchronize()
}
let fm = FileManager.default
// The destination file path may not exist so provide a default file permissions of RW user only
let permissions = (try? fm._permissionsOfItem(atPath: path)) ?? 0o600
if writeOptionsMask.contains(.atomic) {
let (newFD, auxFilePath) = try _NSCreateTemporaryFile(path)
let fh = FileHandle(fileDescriptor: newFD, closeOnDealloc: true)
do {
try doWrite(fh)
// Moving a file on Windows (via _NSCleanupTemporaryFile)
// requires that there be no open handles to the file
fh.closeFile()
try _NSCleanupTemporaryFile(auxFilePath, path)
try fm.setAttributes([.posixPermissions: NSNumber(value: permissions)], ofItemAtPath: path)
} catch {
let savedErrno = errno
try? fm.removeItem(atPath: auxFilePath)
throw _NSErrorWithErrno(savedErrno, reading: false, path: path)
}
} else {
var flags = O_WRONLY | O_CREAT | O_TRUNC
if writeOptionsMask.contains(.withoutOverwriting) {
flags |= O_EXCL
}
guard let fh = FileHandle(path: path, flags: flags, createMode: permissions) else {
throw _NSErrorWithErrno(errno, reading: false, path: path)
}
try doWrite(fh)
}
}
/// 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) {
if funnelsAreAbstract {
let actualCount = Swift.min(length, self.length)
let sourceBuffer = UnsafeRawBufferPointer(start: bytes, count: actualCount)
let destinationBuffer = UnsafeMutableRawBufferPointer(start: buffer, count: actualCount)
sourceBuffer.copyBytes(to: destinationBuffer)
} else {
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) {
if funnelsAreAbstract {
precondition(range.location >= 0 && range.length >= 0)
let actualCount = Swift.min(range.length, self.length - range.location)
let sourceBuffer = UnsafeRawBufferPointer(start: bytes.advanced(by: range.location), count: actualCount)
let destinationBuffer = UnsafeMutableRawBufferPointer(start: buffer, count: actualCount)
sourceBuffer.copyBytes(to: destinationBuffer)
} else {
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 {
err = error
}
}
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, NSRange(location: 0, length: 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
fileprivate 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)
}
public override init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int) {
super.init(bytesNoCopy: bytes, length: length)
}
public override init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, deallocator: ((UnsafeMutableRawPointer, Int) -> Void)? = nil) {
super.init(bytesNoCopy: bytes, length: length, deallocator: deallocator)
}
public override init(bytesNoCopy bytes: UnsafeMutableRawPointer, length: Int, freeWhenDone: Bool) {
super.init(bytesNoCopy: bytes, length: length, freeWhenDone: freeWhenDone)
}
public override init(data: Data) {
super.init(data: data)
}
public override init?(contentsOfFile path: String) {
super.init(contentsOfFile: path)
}
public override init(contentsOfFile path: String, options: NSData.ReadingOptions = []) throws {
try super.init(contentsOfFile: path, options: options)
}
public override init?(contentsOf url: URL) {
super.init(contentsOf: url)
}
public override init(contentsOf url: URL, options: NSData.ReadingOptions = []) throws {
try super.init(contentsOf: url, options: options)
}
public override init?(base64Encoded base64Data: Data, options: NSData.Base64DecodingOptions = []) {
super.init(base64Encoded: base64Data, options: options)
}
public override init?(base64Encoded base64Data: String, options: NSData.Base64DecodingOptions = []) {
super.init(base64Encoded: base64Data, options: options)
}
// MARK: - Funnel Methods
/// A pointer to the data contained by the mutable data object.
open var mutableBytes: UnsafeMutableRawPointer {
requireFunnelOverridden()
return UnsafeMutableRawPointer(CFDataGetMutableBytePtr(_cfMutableObject))
}
/// The number of bytes contained in the mutable data object.
open override var length: Int {
get {
requireFunnelOverridden()
return CFDataGetLength(_cfObject)
}
set {
requireFunnelOverridden()
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) {
guard length > 0 else { return }
if funnelsAreAbstract {
self.length += length
UnsafeRawBufferPointer(start: bytes, count: length).copyBytes(to: UnsafeMutableRawBufferPointer(start: mutableBytes, count: length))
} else {
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) {
if funnelsAreAbstract {
self.length += extraLength
} else {
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) {
if funnelsAreAbstract {
replaceBytes(in: range, withBytes: bytes, length: range.length)
} else {
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) {
memset(mutableBytes.advanced(by: range.location), 0, 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: NSRange(location: 0, length: 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) {
precondition(range.location + range.length <= self.length)
if funnelsAreAbstract {
let delta = replacementLength - range.length
if delta != 0 {
let originalLength = self.length
self.length += delta
if delta > 0 {
UnsafeRawBufferPointer(start: mutableBytes.advanced(by: range.location), count: originalLength).copyBytes(to: UnsafeMutableRawBufferPointer(start: mutableBytes.advanced(by: range.location + range.length), count: originalLength))
}
}
UnsafeRawBufferPointer(start: replacementBytes, count: replacementLength).copyBytes(to: UnsafeMutableRawBufferPointer(start: mutableBytes.advanced(by: range.location), count: replacementLength))
} else {
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)
}
}
internal func _CFSwiftDataCreateCopy(_ data: CFTypeRef) -> Unmanaged<AnyObject> {
return Unmanaged<AnyObject>.passRetained((data as! NSData).copy() as! NSObject)
}
internal func _CFSwiftDataGetLength(_ data: CFTypeRef) -> CFIndex {
return (data as! NSData).length
}
internal func _CFSwiftDataGetBytesPtr(_ data: CFTypeRef) -> UnsafeRawPointer? {
return (data as! NSData).bytes
}
internal func _CFSwiftDataGetMutableBytesPtr(_ data: CFTypeRef) -> UnsafeMutableRawPointer? {
return (data as! NSMutableData).mutableBytes
}
internal func _CFSwiftDataGetBytes(_ data: CFTypeRef, _ range: CFRange, _ buffer: UnsafeMutableRawPointer) -> Void {
(data as! NSData).getBytes(buffer, range: NSMakeRange(range.location, range.length))
}
internal func _CFSwiftDataSetLength(_ data: CFTypeRef, _ newLength: CFIndex) {
(data as! NSMutableData).length = newLength
}
internal func _CFSwiftDataIncreaseLength(_ data: CFTypeRef, _ extraLength: CFIndex) {
(data as! NSMutableData).increaseLength(by: extraLength)
}
internal func _CFSwiftDataAppendBytes(_ data: CFTypeRef, _ buffer: UnsafeRawPointer, length: CFIndex) {
(data as! NSMutableData).append(buffer, length: length)
}
internal func _CFSwiftDataReplaceBytes(_ data: CFTypeRef, _ range: CFRange, _ buffer: UnsafeRawPointer?, _ count: CFIndex) {
(data as! NSMutableData).replaceBytes(in: NSMakeRange(range.location, range.length), withBytes: buffer, length: count)
}
extension NSData {
var funnelsAreAbstract: Bool {
return type(of: self) != NSData.self && type(of: self) != NSMutableData.self
}
func requireFunnelOverridden() {
if funnelsAreAbstract {
NSRequiresConcreteImplementation()
}
}
}