├── UnitTests ├── en.lproj │ └── InfoPlist.strings ├── UnitTests-Prefix.pch ├── UnitTests-Info.plist └── SOExtendedAttributes_UnitTests.m ├── .gitignore ├── Documentation ├── net.standardorbit.SOExtendedAttributes.docset │ └── Contents │ │ ├── Resources │ │ ├── docSet.mom │ │ ├── docSet.toc │ │ ├── docSet.dsidx │ │ ├── docSet.skidx │ │ ├── docSet.tokencache │ │ ├── Documents │ │ │ ├── img │ │ │ │ ├── disclosure.png │ │ │ │ ├── disclosure_open.png │ │ │ │ ├── title_background.png │ │ │ │ ├── library_background.png │ │ │ │ └── button_bar_background.png │ │ │ ├── css │ │ │ │ ├── stylesPrint.css │ │ │ │ └── styles.css │ │ │ ├── hierarchy.html │ │ │ ├── index.html │ │ │ └── Categories │ │ │ │ └── NSURL+SOExtendedAttributes.html │ │ ├── Nodes.xml │ │ └── Tokens1.xml │ │ └── Info.plist ├── README.txt └── appledoc.sh ├── SOExtendedAttributes.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── SOUsefulFunctions.h ├── SOUsefulFunctions.m ├── SOExtendedAttributes.podspec ├── .hgtags ├── LICENSE.md ├── README.md ├── NSURL+SOExtendedAttributes.h └── NSURL+SOExtendedAttributes.m /UnitTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pbxuser 3 | *.perspective 4 | *.perspectivev3 5 | .DS_Store 6 | *.xcworkspace 7 | xcuserdata 8 | DerivedData/ 9 | .idea 10 | .hgtags 11 | 12 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.mom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.mom -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.toc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.toc -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.skidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.skidx -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.tokencache: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/docSet.tokencache -------------------------------------------------------------------------------- /SOExtendedAttributes.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/disclosure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/disclosure.png -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/disclosure_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/disclosure_open.png -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/title_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/title_background.png -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/library_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/library_background.png -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/button_bar_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/billgarrison/SOExtendedAttributes/HEAD/Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/img/button_bar_background.png -------------------------------------------------------------------------------- /UnitTests/UnitTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SOExtendedAttributes.UnitTests' target in the 'SOExtendedAttributes.UnitTests' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /SOUsefulFunctions.h: -------------------------------------------------------------------------------- 1 | // 2 | // SOUsefulFunctions.h 3 | // Fotovino 4 | // 5 | // Created by William Garrison on 1/23/12. 6 | // Copyright (c) 2012 Standard Orbit Software, LLC. All rights reserved. 7 | // 8 | 9 | 10 | 11 | #import 12 | 13 | /** @return A UUID string. 14 | 15 | Generates a UUID. 16 | */ 17 | NSString *SOGeneratedUUID(void); -------------------------------------------------------------------------------- /Documentation/README.txt: -------------------------------------------------------------------------------- 1 | 2 | You can regenerate an Xcode docset by running "sh appledoc.sh" from a shell. 3 | 4 | The appledoc tool must be installed on your system somewhere, including its templates. 5 | You can get it from GitHub at . 6 | 7 | The appledoc.sh script expects appledoc to be installed in its default location, /usr/local/bin/appledoc. 8 | 9 | May the force be with you. 10 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/css/stylesPrint.css: -------------------------------------------------------------------------------- 1 | 2 | header { 3 | display: none; 4 | } 5 | 6 | div.main-navigation, div.navigation-top { 7 | display: none; 8 | } 9 | 10 | div#overview_contents, div#contents.isShowingTOC, div#contents { 11 | overflow: visible; 12 | position: relative; 13 | top: 0px; 14 | border: none; 15 | left: 0; 16 | } 17 | #tocContainer.isShowingTOC { 18 | display: none; 19 | } 20 | nav { 21 | display: none; 22 | } -------------------------------------------------------------------------------- /SOUsefulFunctions.m: -------------------------------------------------------------------------------- 1 | // 2 | // SOUsefulFunctions.m 3 | // Fotovino 4 | // 5 | // Created by William Garrison on 1/23/12. 6 | // Copyright (c) 2012 Standard Orbit Software, LLC. All rights reserved. 7 | // 8 | 9 | #if ! __has_feature(objc_arc) 10 | #error This file must be compiled with ARC (set -fobjc_arc flag on file) 11 | #endif 12 | 13 | #import "SOUsefulFunctions.h" 14 | 15 | NSString *SOGeneratedUUID(void) 16 | { 17 | NSString *UUID = nil; 18 | 19 | CFUUIDRef uuidRef = CFUUIDCreate(NULL); 20 | if (uuidRef) 21 | { 22 | UUID = (__bridge_transfer NSString *) CFUUIDCreateString(NULL, uuidRef); 23 | CFRelease (uuidRef); 24 | } 25 | 26 | return UUID; 27 | } 28 | -------------------------------------------------------------------------------- /SOExtendedAttributes.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SOExtendedAttributes" 3 | s.version = "1.0.8" 4 | s.summary = "SOExtendedAttributes is a category on NSURL for manipulating the extended attributes of a file system object. BSD License." 5 | s.homepage = "https://github.com/billgarrison/SOExtendedAttributes" 6 | s.license = 'BSD' 7 | s.author = 'Bill Garrison' 8 | s.source = { 9 | :git => "https://github.com/billgarrison/SOExtendedAttributes.git", 10 | :tag => "#{s.version}" 11 | } 12 | s.ios.deployment_target = '5.0' 13 | s.osx.deployment_target = '10.6' 14 | s.source_files = 'NSURL+SOExtendedAttributes.{h,m}' 15 | s.requires_arc = true 16 | end 17 | -------------------------------------------------------------------------------- /UnitTests/UnitTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.standardorbit.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Nodes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SOExtendedAttributes 6 | index.html 7 | 8 | 9 | 10 | 11 | Categories 12 | index.html 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | NSURL(SOExtendedAttributes) 27 | Categories/NSURL+SOExtendedAttributes.html 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 476eec03bc844bd4a508d15acc6c7ba6589a5316 RELEASE_1_0 2 | 476eec03bc844bd4a508d15acc6c7ba6589a5316 RELEASE_1_0 3 | a342b60061ed3b02fae557608c81c1c2169ccdf6 RELEASE_1_0 4 | 58f2966a216b1fe2bd519242c1d0215d6441ec2b RELEASE_1_0_1 5 | 06fbb82ba6aec0eb8fccbf35526ea4224e680fe5 RELEASE_1_0_2 6 | 7fccfc58e89250e8921715c7e18f39a801ec6032 1.0.3 7 | 7fccfc58e89250e8921715c7e18f39a801ec6032 1.0.3 8 | dfd746dd6334cad2ea3a6153091f9a504fc4656d 1.0.3 9 | dfd746dd6334cad2ea3a6153091f9a504fc4656d 1.0.3 10 | bc85ce968fbc796acb46f8dace3cf1fa667a82b1 1.0.3 11 | e629b18e4a59aa213826d416a7b4bacb264c7e9a 1.0.4 12 | adb9c727a1eecfd4acc4d88ce6853766c264ae80 1.0.5 13 | adb9c727a1eecfd4acc4d88ce6853766c264ae80 1.0.5 14 | 14da5f011a9370576ec5d4eafd77bba93dc92e8e 1.0.5 15 | 14da5f011a9370576ec5d4eafd77bba93dc92e8e 1.0.5 16 | a4c79d21698c8054ef9344f425ffbb714c48a0c3 1.0.5 17 | ea78ac8d4fe9e7d2fd264699c9d8b6a9a00d1b68 1.0.6 18 | 00152dcfc4d80f713a9a5fbee1c7b04905507a69 1.0.7 19 | 98073f9e75082a008d613e95816d359a279e9e66 1.0.8 20 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | net.standardorbit.SOExtendedAttributes 9 | CFBundleName 10 | SOExtendedAttributes 11 | CFBundleShortVersionString 12 | 1.0 13 | CFBundleVersion 14 | 1.0 15 | 16 | 17 | DocSetDescription 18 | SOExtendedAttributes defines cateogry on NSURL for manipulating extended attributes on a file system object. 19 | 20 | DocSetFeedName 21 | SOExtendedAttributes Documentation 22 | 23 | DocSetMinimumXcodeVersion 24 | 3.0 25 | 26 | DocSetPublisherIdentifier 27 | net.standardorbit.documentation 28 | DocSetPublisherName 29 | Standard Orbit 30 | NSHumanReadableCopyright 31 | Copyright © 2013 Standard Orbit. All rights reserved. 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Standard Orbit Software, LLC. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | Neither the name of the Standard Orbit Software, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9 | -------------------------------------------------------------------------------- /Documentation/appledoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Generate an Xcode docset using appledoc 4 | # 5 | # I use Xcode-compatible variable names where possible to facilitate 6 | # running the script from an Xcode run script phase as well as a command line. 7 | # 8 | #set -x 9 | # 10 | set -e 11 | 12 | : ${APPLEDOC:=/usr/local/bin/appledoc} 13 | : ${APPLEDOC_OUTPUT:=./Documentation} 14 | : ${PROJECT_DIR:=../} 15 | : ${PROJECT_NAME:="SOExtendedAttributes"} 16 | : ${BUILD_VERSION:="1.0"} 17 | 18 | if [ -z ${APPLEDOC} ] || ! [ -x ${APPLEDOC} ]; then 19 | echo "error: can't find the appledoc binary at $APPLEDOC" 20 | exit -1 21 | fi 22 | 23 | # Set working directory to the project root 24 | # 25 | cd ${PROJECT_DIR} 26 | echo "working directory: $PWD" 27 | 28 | # Run the system's appledoc tool, specifying paths relative to the project root 29 | # 30 | printf "Using appledoc from %s\n" ${APPLEDOC} 31 | 32 | ${APPLEDOC} \ 33 | --project-name="SOExtendedAttributes" \ 34 | --project-version=${BUILD_VERSION} \ 35 | --project-company="Standard Orbit" \ 36 | --company-id="net.standardorbit" \ 37 | --logformat="xcode" \ 38 | --no-repeat-first-par \ 39 | --no-warn-invalid-crossref \ 40 | --ignore="Documentation" \ 41 | --ignore="UnitTests" \ 42 | --ignore="SOLoggerDemo" \ 43 | --keep-intermediate-files \ 44 | --create-docset \ 45 | --install-docset \ 46 | --docset-install-path="Documentation" \ 47 | --docset-bundle-name="SOExtendedAttributes" \ 48 | --docset-desc="SOExtendedAttributes defines cateogry on NSURL for manipulating extended attributes on a file system object." \ 49 | --print-settings \ 50 | --verbose=4 \ 51 | --output=${TMPDIR}appledoc-${PROJECT_NAME} \ 52 | --clean-output \ 53 | $PWD 54 | 55 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/hierarchy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SOExtendedAttributes Hierarchy 6 | 7 | 8 | 9 | 10 | 11 |
12 | 16 | 17 | 20 | 21 |
22 |
23 |
24 | 27 | 32 |
33 | 34 | 35 | 36 |
37 | 38 | 39 |

Category References

40 | 45 | 46 |
47 | 48 |
49 | 52 | 62 |
63 |
64 | 65 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SOExtendedAttributes Reference 6 | 7 | 8 | 9 | 10 | 11 |
12 | 16 | 17 | 20 | 21 |
22 |
23 |
24 | 27 | 32 |
33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 |

Category References

42 | 47 | 48 |
49 | 50 |
51 | 54 | 64 |
65 |
66 | 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOExtendedAttributes 2 | 3 | SOExtendedAttributes is a category on NSURL providing methods for manipulating extended attributes on a file system object. 4 | 5 | The implementation uses [listxattr(2)](x-man-page://listxattr), [getxattr(2)](x-man-page://getxattr), [setxattr(2)](x-man-page://setxattr), and [removexattr(2)](x-man-page://removexattr). 6 | 7 | 8 | **Introspection** 9 | 10 | - (BOOL) hasExtendedAttributeWithName:(NSString *)name; 11 | 12 | **Accessing attributes individually** 13 | 14 | - (id) valueOfExtendedAttributeWithName:(NSString *)name error:(NSError * __autoreleasing *)outError; 15 | - (BOOL) setExtendedAttributeValue:(id)value forName:(NSString *)name error:(NSError * __autoreleasing *)outError; 16 | 17 | **Accessing attributes in batch** 18 | 19 | - (NSDictionary *) extendedAttributesWithError:(NSError * __autoreleasing *)outError; 20 | - (BOOL) setExtendedAttributes:(NSDictionary *)attributes error:(NSError * __autoreleasing *)outError; 21 | 22 | **Removing an extended attribute** 23 | 24 | - (BOOL) removeExtendedAttributeWithName:(NSString *)name error:(NSError * __autoreleasing *)outError; 25 | 26 | 27 | **Use with iCloud Backup** 28 | 29 | Note 2013-06-03: see [Apple Tech QA 1719](http://developer.apple.com/library/ios/#qa/qa1719/) for the recommended way to mark a file for exclusion from iCloud backup. Hint: don't use the @"com.apple.MobileMeBackup" extended attribute. 30 | 31 | 32 | **Extended Attribute Values** 33 | 34 | An extended attribute value can be any Foundation object that can be serialized into a property list: 35 | 36 | - NSData 37 | - NSString 38 | - NSNumber 39 | - NSArray 40 | - NSDictionary 41 | - NSDate 42 | 43 | ##Known Issues, Stuff & Bother 44 | 45 | **Maximum Value Size** 46 | 47 | The maximum size of an extended attribute value? I'm not sure, but I believe it is a function of the node size in the HFS+ catalog or attributes tree. The Internet says anywhere from 3802 bytes to 128 KB. So the standard disclaimers apply: _Your mileage may vary. Void where prohibited. Do not operate heavy machinery._ 48 | 49 | Refs: 50 | [Google Search](http://www.google.com/?q=hfs%2B+extended+attributes+max+size) 51 | - [OSDir](http://osdir.com/ml/filesystem-dev/2009-07/msg00020.html) 52 | - [ArsTechnica 10.4 Review](http://arstechnica.com/apple/reviews/2005/04/macosx-10-4.ars/7) 53 | - [ArsTechnica 10.6 Review](http://arstechnica.com/apple/reviews/2009/08/mac-os-x-10-6.ars/3) 54 | - [MacFuse Release Notes](http://code.google.com/p/macfuse/source/browse/trunk/CHANGELOG.txt) 55 | 56 | **Non-file URLs** 57 | 58 | An `NSInternalInconsistencyException` is thrown if these methods are invoked on a non-file URL. 59 | 60 | **Resolving Symbolic links** 61 | 62 | These methods act on the explicitly given URL. If that URL points to a symbolic link, you'll be manipulating extended attributes on the symlink, not its original file. Use `-URLByResolvingSymlinksInPath` to get a URL pointing to the original file system item. 63 | 64 | **ARC** 65 | 66 | If your project is not using ARC, you will need to set a per-file build flag in Xcode on `NSURL+SOExtendedAttributes.m`: 67 | 68 | -fobc-arc 69 | 70 | ## Installation 71 | 72 | For either Mac OS X or iOS projects, add these files: 73 | 74 | NSURL+SOExtendedAttributes.h 75 | NSURL+SOExtendedAttributes.m 76 | 77 | ## Compatibility 78 | 79 | SOExtendedAttributes is compatible with Mac OS X 10.6+ and iOS 5. The clang compiler is required. The source file `NSURL+SOExtendedAttributes.m` must be compiled with ARC enabled. 80 | 81 | For an alternate Cocoa implementation compatible with Mac OS X 10.4 and greater, see [UKXattrMetadataStore](http://zathras.de/angelweb/sourcecode.htm). 82 | 83 | ## Credits 84 | 85 | Bill Garrison, initial creation. [@github](https://github.com/billgarrison), [@bitbucket](https://bitbucket.org/billgarrison) 86 | 87 | Uli Kusterer, for [UKXattrMetadataStore](http://zathras.de/angelweb/sourcecode.htm). 88 | 89 | Simon Tännler, for starting a Cocoapods spec. [@github](https://github.com/simontea). 90 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Tokens1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | //apple_ref/occ/cat/NSURL(SOExtendedAttributes) 7 | The SOExtendedAttributes category on NSURL enables retrieving and manipulating the extended attributes on a file system item. 8 | NSURL+SOExtendedAttributes.h 9 | 10 | 11 | 12 | 13 | 14 | 15 | //apple_ref/occ/intfm/NSURL(SOExtendedAttributes)/maximumExtendedAttributesSize 16 | The maximum size in bytes for an extended attribute on the receiver. 17 | NSURL+SOExtendedAttributes.h 18 | 19 | - (NSUInteger)maximumExtendedAttributesSize 20 | 21 | The maximum size in bytes for an extended attribute on the receiver. 22 | //api/name/maximumExtendedAttributesSize 23 | 24 | 25 | 26 | //apple_ref/occ/intfm/NSURL(SOExtendedAttributes)/extendedAttributesWithError: 27 | Returns the extended attributes of the file system item at this URL. 28 | NSURL+SOExtendedAttributes.h 29 | 30 | - (NSDictionary *)extendedAttributesWithError:(NSError *__autoreleasing *)outError 31 | 32 | 33 | outError 34 | If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting. 35 | 36 | 37 | An NSDictionary object that describes the extended attributes of the file system object, or nil if an error occurred. 38 | //api/name/extendedAttributesWithError: 39 | 40 | 41 | 42 | //apple_ref/occ/intfm/NSURL(SOExtendedAttributes)/setExtendedAttributes:error: 43 | Sets the extended attribute values for the given URL. 44 | NSURL+SOExtendedAttributes.h 45 | 46 | - (BOOL)setExtendedAttributes:(NSDictionary *)attributes error:(NSError *__autoreleasing *)outError 47 | 48 | 49 | attributes 50 | The extended attribute names and values to be set. All values be instances of NSData, NSString, NSArray, NSDictionary, NSDate or NSNumber. 51 | 52 | outError 53 | If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting. 54 | 55 | 56 | YES if all given attribute values were set. NO if there was an error setting one or more of the values. 57 | //api/name/setExtendedAttributes:error: 58 | 59 | 60 | 61 | //apple_ref/occ/intfm/NSURL(SOExtendedAttributes)/hasExtendedAttributeWithName: 62 | Returns YES if the file system item has the named attribute. 63 | NSURL+SOExtendedAttributes.h 64 | 65 | - (BOOL)hasExtendedAttributeWithName:(NSString *)name 66 | 67 | 68 | name 69 | The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty. 70 | 71 | 72 | YES if the named extended attribute is present on the URL; NO otherwise. 73 | //api/name/hasExtendedAttributeWithName: 74 | 75 | 76 | 77 | //apple_ref/occ/intfm/NSURL(SOExtendedAttributes)/valueOfExtendedAttributeWithName:error: 78 | Returns the value of the named extended attribute from this file system item. 79 | NSURL+SOExtendedAttributes.h 80 | 81 | - (id)valueOfExtendedAttributeWithName:(NSString *)name error:(NSError *__autoreleasing *)outError 82 | 83 | 84 | name 85 | The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty. 86 | 87 | outError 88 | A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you're not interesting in error reporting. 89 | 90 | 91 | An appropriate Foundation object holding the value, or nil if there was an error. 92 | //api/name/valueOfExtendedAttributeWithName:error: 93 | 94 | 95 | 96 | //apple_ref/occ/intfm/NSURL(SOExtendedAttributes)/setExtendedAttributeValue:forName:error: 97 | Set the value of the named extended attribute. 98 | NSURL+SOExtendedAttributes.h 99 | 100 | - (BOOL)setExtendedAttributeValue:(id)value forName:(NSString *)name error:(NSError *__autoreleasing *)outError 101 | 102 | 103 | value 104 | The value to be set. Must be an instance of NSData, NSString, NSArray, NSDictionary, NSDate or NSNumber. 105 | 106 | name 107 | The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty. 108 | 109 | outError 110 | A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you're not interesting in error reporting. 111 | 112 | 113 | YES if the given value was set; NO if there was an error. 114 | //api/name/setExtendedAttributeValue:forName:error: 115 | 116 | 117 | 118 | //apple_ref/occ/intfm/NSURL(SOExtendedAttributes)/removeExtendedAttributeWithName:error: 119 | Removes the named extended attribute from this file system item. 120 | NSURL+SOExtendedAttributes.h 121 | 122 | - (BOOL)removeExtendedAttributeWithName:(NSString *)name error:(NSError *__autoreleasing *)outError 123 | 124 | 125 | name 126 | The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty. 127 | 128 | outError 129 | A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you're not interesting in error reporting. 130 | 131 | 132 | YES if successfully removed or named attribute does not exist. NO if there was an error. 133 | //api/name/removeExtendedAttributeWithName:error: 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /NSURL+SOExtendedAttributes.h: -------------------------------------------------------------------------------- 1 | /* 2 | NSURL+SOExtendedAttributes 3 | 4 | Copyright 2013 Standard Orbit Software, LLC. All rights reserved. 5 | License at the bottom of the file. 6 | */ 7 | 8 | #import 9 | #import 10 | 11 | #if TARGET_OS_IPHONE 12 | #ifndef __IPHONE_5_0 13 | #warning "This project uses features only available in iOS SDK 5.0 and later." 14 | #endif 15 | #elif TARGET_OS_MAC 16 | #ifndef __MAC_10_7 17 | #warning "This project uses features only available in Mac OS X SDK 10.7 and later." 18 | #endif 19 | #endif 20 | 21 | 22 | /** 23 | The SOExtendedAttributes category on NSURL enables retrieving and manipulating the extended attributes on a file system item. 24 | 25 | These methods are valid only on file URLs. An NSInternalInconsistencyException is thrown if invoked an a non-file URL. 26 | 27 | Internally, they're implemented using [listxattr(2)](x-man-page://listxattr), [getxattr(2)](x-man-page://getxattr), [setxattr(2)](x-man-page://setxattr), and [removexattr(2)](x-man-page://removexattr). 28 | 29 | ** Compatibility ** 30 | 31 | SOExtendedAttributes is compatible with Mac OS X 10.7+ and iOS 5. The clang compiler is required. The source file `NSURL+SOExtendedAttributes.m` must be compiled with ARC enabled. For an alternate Cocoa implementation compatible with Mac OS X 10.4 and greater, see [UKXattrMetadataStore](http://zathras.de/angelweb/sourcecode.htm). 32 | 33 | ** Symbolic links ** 34 | 35 | These methods act on the explicitly given URL. If that URL is to a symbolic link, you'll be manipulating extended attributes on the symlink, not its original file. Use `-URLByResolvingSymlinksInPath` to obtain a URL for which points to the original file system item. 36 | 37 | ** Use with iCloud Backup ** 38 | 39 | Note 2013-06-03: see [Apple Tech QA 1719](http://developer.apple.com/library/ios/#qa/qa1719/) for the recommended way to mark a file for exclusion from iCloud backup. Hint: don't use the @"com.apple.MobileMeBackup" extended attribute. 40 | 41 | ** Error Reporting ** 42 | 43 | SOExtendedAttributes reports errors under the domain `SOExtendedAttributesErrorDomain`. When multiple errors occur on getting or setting extended attributes in a batch, those errors are collected in an NSArray and reported via error's -userInfo dictionary under `SOUnderlyingErrorsKey`. 44 | 45 | */ 46 | 47 | extern NSString * const iCloudDoNotBackupAttributeName; 48 | extern NSString * const SOExtendedAttributesErrorDomain; 49 | extern NSString * const SOUnderlyingErrorsKey; 50 | 51 | enum { 52 | SOExtendedAttributesValueCantBeSerialized = 1968, 53 | SOExtendedAttributesSetValueError, 54 | SOExtendedAttributesGetValueError, 55 | }; 56 | 57 | @interface NSURL (SOExtendedAttributes) 58 | 59 | /** 60 | Retrieves the extended attribute data with the given name. 61 | 62 | @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty. 63 | @param outError If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting. 64 | 65 | @return The retrieved extended attribute data, or nil if there was an error. 66 | 67 | @since 1.0.7 68 | */ 69 | - (NSData *) dataForExtendedAttribute:(NSString *)name error:(NSError * __autoreleasing *)outError; 70 | 71 | /** 72 | Sets an extended attribute with the given name and data value. 73 | 74 | The data value is used directly, without any further transformation. 75 | 76 | @param data The extended attribute data to be written. If nil, returns YES immediately. 77 | @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty. 78 | @param outError If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting. 79 | 80 | @return YES if the extended attribute was set. If NO, error will be reported via outError parameter. 81 | 82 | @since 1.0.7 83 | */ 84 | - (BOOL) setExtendedAttributeData:(NSData *)data name:(NSString *)name error:(NSError * __autoreleasing *)outError; 85 | 86 | 87 | /** @name Accessing attributes in batches */ 88 | 89 | /** Returns the extended attributes of the file system item at this URL. 90 | 91 | Return all extended attributes that the current user account has permission to access. Attributes will include the HFS compression extended attribute if present. 92 | 93 | @param outError If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting. 94 | @return An NSDictionary object that describes the extended attributes of the file system object, or nil if an error occurred. 95 | */ 96 | - (NSDictionary *) extendedAttributesWithError:(NSError * __autoreleasing *)outError; 97 | 98 | /** Sets the extended attribute values for the given URL. 99 | 100 | The attributes dictionary parameter may contain any object value that can be encoded as a property list. 101 | 102 | If the attributes dictionary holds a value object that cannot be encoded as a plist, an NSError with code `SOExtendedAttributesValueCantBeSerialized` is returned via the outError parameter. 103 | 104 | On error, one or more of the given extended attributes may have failed to be set. Any underlying errors are reported via the -userInfo dictionary as an NSArray under the key `SOUnderlyingErrorsKey`. 105 | 106 | @param attributes The extended attribute names and values to be set. All values be instances of NSData, NSString, NSArray, NSDictionary, NSDate or NSNumber. 107 | @param outError If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you're not interested in error reporting. 108 | @return YES if all given attribute values were set. NO if there was an error setting one or more of the values. 109 | */ 110 | - (BOOL) setExtendedAttributes:(NSDictionary *)attributes error:(NSError * __autoreleasing *)outError; 111 | 112 | 113 | /** @name Accessing attributes individually */ 114 | 115 | /** Returns YES if the file system item has the named attribute. 116 | 117 | Sometimes, you're only interested in the presence or absence of an extended attribute on a given URL. E.g. @"com.apple.MobileMeBackup". 118 | 119 | @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty. 120 | @return YES if the named extended attribute is present on the URL; NO otherwise. 121 | */ 122 | - (BOOL) hasExtendedAttributeWithName:(NSString *)name; 123 | 124 | /** Returns the value of the named extended attribute from this file system item. 125 | 126 | @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty. 127 | @param outError A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you're not interesting in error reporting. 128 | @return An appropriate Foundation object holding the value, or nil if there was an error. 129 | */ 130 | - (id) valueOfExtendedAttributeWithName:(NSString *)name error:(NSError * __autoreleasing *)outError; 131 | 132 | /** Set the value of the named extended attribute. 133 | 134 | @param value The value to be set. Must be an instance of NSData, NSString, NSArray, NSDictionary, NSDate or NSNumber. 135 | @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty. 136 | @param outError A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you're not interesting in error reporting. 137 | @return YES if the given value was set; NO if there was an error. 138 | */ 139 | - (BOOL) setExtendedAttributeValue:(id)value forName:(NSString *)name error:(NSError * __autoreleasing *)outError; 140 | 141 | 142 | /** @name Removing an extended attribute */ 143 | 144 | /** Removes the named extended attribute from this file system item. 145 | 146 | @param name The name of the extended attribute. Throws `NSInvalidArgumentException` if name is nil or empty. 147 | @param outError A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you're not interesting in error reporting. 148 | @return YES if successfully removed or named attribute does not exist. NO if there was an error. 149 | */ 150 | - (BOOL) removeExtendedAttributeWithName:(NSString *)name error:(NSError * __autoreleasing *)outError; 151 | 152 | 153 | @end 154 | 155 | /* 156 | Copyright (c) 2012-2014, Standard Orbit Software, LLC. All rights reserved. 157 | 158 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 159 | 160 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 161 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 162 | Neither the name of the Standard Orbit Software, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 163 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 164 | */ 165 | -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; 3 | font-size: 13px; 4 | } 5 | 6 | code { 7 | font-family: Courier, Consolas, monospace; 8 | font-size: 13px; 9 | color: #666; 10 | } 11 | 12 | pre { 13 | font-family: Courier, Consolas, monospace; 14 | font-size: 13px; 15 | line-height: 18px; 16 | tab-interval: 0.5em; 17 | border: 1px solid #C7CFD5; 18 | background-color: #F1F5F9; 19 | color: #666; 20 | padding: 0.3em 1em; 21 | } 22 | 23 | ul { 24 | list-style-type: square; 25 | } 26 | 27 | li { 28 | margin-bottom: 10px; 29 | } 30 | 31 | a { 32 | text-decoration: none; 33 | color: #36C; 34 | } 35 | 36 | a:hover { 37 | text-decoration: underline; 38 | color: #36C; 39 | } 40 | 41 | h2 { 42 | border-bottom: 1px solid #8391A8; 43 | color: #3C4C6C; 44 | font-size: 187%; 45 | font-weight: normal; 46 | margin-top: 1.75em; 47 | padding-bottom: 2px; 48 | } 49 | 50 | /* @group Common page elements */ 51 | 52 | #top_header { 53 | height: 91px; 54 | left: 0; 55 | min-width: 598px; 56 | position: absolute; 57 | right: 0; 58 | top: 0; 59 | z-index: 900; 60 | } 61 | 62 | #footer { 63 | clear: both; 64 | padding-top: 20px; 65 | text-align: center; 66 | } 67 | 68 | #contents, #overview_contents { 69 | border-top: 1px solid #2B334F; 70 | position: absolute; 71 | top: 91px; 72 | left: 0; 73 | right: 0; 74 | bottom: 0; 75 | overflow-x: hidden; 76 | overflow-y: auto; 77 | padding-left: 2em; 78 | padding-right: 2em; 79 | padding-top: 1em; 80 | min-width: 550px; 81 | } 82 | 83 | #contents.isShowingTOC { 84 | left: 230px; 85 | min-width: 320px; 86 | } 87 | 88 | .copyright { 89 | font-size: 12px; 90 | } 91 | 92 | .generator { 93 | font-size: 11px; 94 | } 95 | 96 | .main-navigation ul li { 97 | display: inline; 98 | margin-left: 15px; 99 | list-style: none; 100 | } 101 | 102 | .navigation-top { 103 | clear: both; 104 | float: right; 105 | } 106 | 107 | .navigation-bottom { 108 | clear: both; 109 | float: right; 110 | margin-top: 20px; 111 | margin-bottom: -10px; 112 | } 113 | 114 | .open > .disclosure { 115 | background-image: url("../img/disclosure_open.png"); 116 | } 117 | 118 | .disclosure { 119 | background: url("../img/disclosure.png") no-repeat scroll 0 0; 120 | } 121 | 122 | .disclosure, .nodisclosure { 123 | display: inline-block; 124 | height: 8px; 125 | margin-right: 5px; 126 | position: relative; 127 | width: 9px; 128 | } 129 | 130 | /* @end */ 131 | 132 | /* @group Header */ 133 | 134 | #top_header #library { 135 | background: url("../img/library_background.png") repeat-x 0 0 #485E78; 136 | background-color: #ccc; 137 | height: 35px; 138 | font-size: 115%; 139 | } 140 | 141 | #top_header #library #libraryTitle { 142 | color: #FFFFFF; 143 | margin-left: 15px; 144 | text-shadow: 0 -1px 0 #485E78; 145 | top: 8px; 146 | position: absolute; 147 | } 148 | 149 | #top_header #library #developerHome { 150 | color: #92979E; 151 | right: 15px; 152 | top: 8px; 153 | position: absolute; 154 | } 155 | 156 | #top_header #library a:hover { 157 | text-decoration: none; 158 | } 159 | 160 | #top_header #title { 161 | background: url("../img/title_background.png") repeat-x 0 0 #8A98A9; 162 | border-bottom: 1px solid #B6B6B6; 163 | height: 25px; 164 | overflow: hidden; 165 | } 166 | 167 | #top_header h1 { 168 | font-size: 115%; 169 | font-weight: normal; 170 | margin: 0; 171 | padding: 3px 0 2px; 172 | text-align: center; 173 | text-shadow: 0 1px 0 #D5D5D5; 174 | white-space: nowrap; 175 | } 176 | 177 | #headerButtons { 178 | background-color: #D8D8D8; 179 | background-image: url("../img/button_bar_background.png"); 180 | border-bottom: 1px solid #EDEDED; 181 | border-top: 1px solid #2B334F; 182 | font-size: 8pt; 183 | height: 28px; 184 | left: 0; 185 | list-style: none outside none; 186 | margin: 0; 187 | overflow: hidden; 188 | padding: 0; 189 | position: absolute; 190 | right: 0; 191 | top: 61px; 192 | } 193 | 194 | #headerButtons li { 195 | background-repeat: no-repeat; 196 | display: inline; 197 | margin-top: 0; 198 | margin-bottom: 0; 199 | padding: 0; 200 | } 201 | 202 | #toc_button button { 203 | border-color: #ACACAC; 204 | border-style: none solid none none; 205 | border-width: 0 1px 0 0; 206 | height: 28px; 207 | margin: 0; 208 | padding-left: 30px; 209 | text-align: left; 210 | width: 230px; 211 | } 212 | 213 | li#jumpto_button { 214 | left: 230px; 215 | margin-left: 0; 216 | position: absolute; 217 | } 218 | 219 | li#jumpto_button select { 220 | height: 22px; 221 | margin: 5px 2px 0 10px; 222 | max-width: 300px; 223 | } 224 | 225 | /* @end */ 226 | 227 | /* @group Table of contents */ 228 | 229 | #tocContainer.isShowingTOC { 230 | border-right: 1px solid #ACACAC; 231 | display: block; 232 | overflow-x: hidden; 233 | overflow-y: auto; 234 | padding: 0; 235 | } 236 | 237 | #tocContainer { 238 | background-color: #E4EBF7; 239 | border-top: 1px solid #2B334F; 240 | bottom: 0; 241 | display: none; 242 | left: 0; 243 | overflow: hidden; 244 | position: absolute; 245 | top: 91px; 246 | width: 229px; 247 | } 248 | 249 | #tocContainer > ul#toc { 250 | font-size: 11px; 251 | margin: 0; 252 | padding: 12px 0 18px; 253 | width: 209px; 254 | -moz-user-select: none; 255 | -webkit-user-select: none; 256 | user-select: none; 257 | } 258 | 259 | #tocContainer > ul#toc > li { 260 | margin: 0; 261 | padding: 0 0 7px 30px; 262 | text-indent: -15px; 263 | } 264 | 265 | #tocContainer > ul#toc > li > .sectionName a { 266 | color: #000000; 267 | font-weight: bold; 268 | } 269 | 270 | #tocContainer > ul#toc > li > .sectionName a:hover { 271 | text-decoration: none; 272 | } 273 | 274 | #tocContainer > ul#toc li.children > ul { 275 | display: none; 276 | height: 0; 277 | } 278 | 279 | #tocContainer > ul#toc > li > ul { 280 | margin: 0; 281 | padding: 0; 282 | } 283 | 284 | #tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { 285 | margin-left: 0; 286 | margin-bottom: 0; 287 | padding-left: 15px; 288 | } 289 | 290 | #tocContainer > ul#toc > li ul { 291 | list-style: none; 292 | margin-right: 0; 293 | padding-right: 0; 294 | } 295 | 296 | #tocContainer > ul#toc li.children.open > ul { 297 | display: block; 298 | height: auto; 299 | margin-left: -15px; 300 | padding-left: 0; 301 | } 302 | 303 | #tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { 304 | margin-left: 0; 305 | padding-left: 15px; 306 | } 307 | 308 | #tocContainer li ul li { 309 | margin-top: 0.583em; 310 | overflow: hidden; 311 | text-overflow: ellipsis; 312 | white-space: nowrap; 313 | } 314 | 315 | #tocContainer li ul li span.sectionName { 316 | white-space: normal; 317 | } 318 | 319 | #tocContainer > ul#toc > li > ul > li > .sectionName a { 320 | font-weight: bold; 321 | } 322 | 323 | #tocContainer > ul#toc > li > ul a { 324 | color: #4F4F4F; 325 | } 326 | 327 | /* @end */ 328 | 329 | /* @group Index formatting */ 330 | 331 | .index-title { 332 | font-size: 13px; 333 | font-weight: normal; 334 | } 335 | 336 | .index-column { 337 | float: left; 338 | width: 30%; 339 | min-width: 200px; 340 | font-size: 11px; 341 | } 342 | 343 | .index-column ul { 344 | margin: 8px 0 0 0; 345 | padding: 0; 346 | list-style: none; 347 | } 348 | 349 | .index-column ul li { 350 | margin: 0 0 3px 0; 351 | padding: 0; 352 | } 353 | 354 | .hierarchy-column { 355 | min-width: 400px; 356 | } 357 | 358 | .hierarchy-column ul { 359 | margin: 3px 0 0 15px; 360 | } 361 | 362 | .hierarchy-column ul li { 363 | list-style-type: square; 364 | } 365 | 366 | /* @end */ 367 | 368 | /* @group Common formatting elements */ 369 | 370 | .title { 371 | font-weight: normal; 372 | font-size: 215%; 373 | margin-top:0; 374 | } 375 | 376 | .subtitle { 377 | font-weight: normal; 378 | font-size: 180%; 379 | color: #3C4C6C; 380 | border-bottom: 1px solid #5088C5; 381 | } 382 | 383 | .subsubtitle { 384 | font-weight: normal; 385 | font-size: 145%; 386 | height: 0.7em; 387 | } 388 | 389 | .warning { 390 | border: 1px solid #5088C5; 391 | background-color: #F0F3F7; 392 | margin-bottom: 0.5em; 393 | padding: 0.3em 0.8em; 394 | } 395 | 396 | .bug { 397 | border: 1px solid #000; 398 | background-color: #ffffcc; 399 | margin-bottom: 0.5em; 400 | padding: 0.3em 0.8em; 401 | } 402 | 403 | .deprecated { 404 | color: #F60425; 405 | } 406 | 407 | /* @end */ 408 | 409 | /* @group Common layout */ 410 | 411 | .section { 412 | margin-top: 3em; 413 | } 414 | 415 | /* @end */ 416 | 417 | /* @group Object specification section */ 418 | 419 | .section-specification { 420 | margin-left: 2.5em; 421 | margin-right: 2.5em; 422 | font-size: 12px; 423 | } 424 | 425 | .section-specification table { 426 | border-top: 1px solid #d6e0e5; 427 | } 428 | 429 | .section-specification td { 430 | vertical-align: top; 431 | border-bottom: 1px solid #d6e0e5; 432 | padding: .6em; 433 | } 434 | 435 | .section-specification .specification-title { 436 | font-weight: bold; 437 | } 438 | 439 | /* @end */ 440 | 441 | /* @group Tasks section */ 442 | 443 | .task-list { 444 | list-style-type: none; 445 | padding-left: 0px; 446 | } 447 | 448 | .task-list li { 449 | margin-bottom: 3px; 450 | } 451 | 452 | .task-item-suffix { 453 | color: #996; 454 | font-size: 12px; 455 | font-style: italic; 456 | margin-left: 0.5em; 457 | } 458 | 459 | span.tooltip span.tooltip { 460 | font-size: 1.0em; 461 | display: none; 462 | padding: 0.3em; 463 | border: 1px solid #aaa; 464 | background-color: #fdfec8; 465 | color: #000; 466 | text-align: left; 467 | } 468 | 469 | span.tooltip:hover span.tooltip { 470 | display: block; 471 | position: absolute; 472 | margin-left: 2em; 473 | } 474 | 475 | /* @end */ 476 | 477 | /* @group Method section */ 478 | 479 | .section-method { 480 | margin-top: 2.3em; 481 | } 482 | 483 | .method-title { 484 | margin-bottom: 1.5em; 485 | } 486 | 487 | .method-subtitle { 488 | margin-top: 0.7em; 489 | margin-bottom: 0.2em; 490 | } 491 | 492 | .method-subsection p { 493 | margin-top: 0.4em; 494 | margin-bottom: 0.8em; 495 | } 496 | 497 | .method-declaration { 498 | margin-top:1.182em; 499 | margin-bottom:.909em; 500 | } 501 | 502 | .method-declaration code { 503 | font:14px Courier, Consolas, monospace; 504 | color:#000; 505 | } 506 | 507 | .declaration { 508 | color: #000; 509 | } 510 | 511 | .argument-def { 512 | margin-top: 0.3em; 513 | margin-bottom: 0.3em; 514 | } 515 | 516 | .argument-def dd { 517 | margin-left: 1.25em; 518 | } 519 | 520 | .see-also-section ul { 521 | list-style-type: none; 522 | padding-left: 0px; 523 | margin-top: 0; 524 | } 525 | 526 | .see-also-section li { 527 | margin-bottom: 3px; 528 | } 529 | 530 | .declared-in-ref { 531 | color: #666; 532 | } 533 | 534 | /* @end */ 535 | 536 | -------------------------------------------------------------------------------- /SOExtendedAttributes.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F212A54E14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = F212A54D14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.m */; }; 11 | F212A55114CDC58800B65EA9 /* SOExtendedAttributes_UnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F212A55014CDC58800B65EA9 /* SOExtendedAttributes_UnitTests.m */; }; 12 | F212A55614CDC5B600B65EA9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F212A55414CDC5B600B65EA9 /* InfoPlist.strings */; }; 13 | F2191C1814CE06AA004557C9 /* SOUsefulFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = F2191C1614CE06AA004557C9 /* SOUsefulFunctions.m */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXFileReference section */ 17 | F212A53314CDC35900B65EA9 /* SOExtendedAttributes.UnitTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SOExtendedAttributes.UnitTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | F212A53614CDC35900B65EA9 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 19 | F212A54C14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+SOExtendedAttributes.h"; sourceTree = ""; }; 20 | F212A54D14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+SOExtendedAttributes.m"; sourceTree = ""; }; 21 | F212A55014CDC58800B65EA9 /* SOExtendedAttributes_UnitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SOExtendedAttributes_UnitTests.m; path = UnitTests/SOExtendedAttributes_UnitTests.m; sourceTree = SOURCE_ROOT; }; 22 | F212A55214CDC5A900B65EA9 /* UnitTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "UnitTests-Info.plist"; path = "UnitTests/UnitTests-Info.plist"; sourceTree = SOURCE_ROOT; }; 23 | F212A55314CDC5A900B65EA9 /* UnitTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UnitTests-Prefix.pch"; path = "UnitTests/UnitTests-Prefix.pch"; sourceTree = SOURCE_ROOT; }; 24 | F212A55514CDC5B600B65EA9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = UnitTests/en.lproj/InfoPlist.strings; sourceTree = SOURCE_ROOT; }; 25 | F212A55914CDC65900B65EA9 /* appledoc.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = appledoc.sh; sourceTree = ""; }; 26 | F212A55A14CDC65900B65EA9 /* net.standardorbit.SOExtendedAttributes.docset */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = net.standardorbit.SOExtendedAttributes.docset; sourceTree = ""; }; 27 | F212A55B14CDC65900B65EA9 /* README.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.txt; sourceTree = ""; }; 28 | F2191C1614CE06AA004557C9 /* SOUsefulFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SOUsefulFunctions.m; sourceTree = SOURCE_ROOT; }; 29 | F2191C1714CE06AA004557C9 /* SOUsefulFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SOUsefulFunctions.h; sourceTree = SOURCE_ROOT; }; 30 | F26FC50518222B4A00627018 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = text; path = README.md; sourceTree = SOURCE_ROOT; }; 31 | F2CBB2B914E8EEE300937A53 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | F212A52F14CDC35900B65EA9 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | ); 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXFrameworksBuildPhase section */ 43 | 44 | /* Begin PBXGroup section */ 45 | F212A51F14CDC2D900B65EA9 = { 46 | isa = PBXGroup; 47 | children = ( 48 | F212A54C14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.h */, 49 | F212A54D14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.m */, 50 | F212A53E14CDC35900B65EA9 /* UnitTests */, 51 | F212A55814CDC65900B65EA9 /* Documentation */, 52 | F212A53514CDC35900B65EA9 /* Frameworks */, 53 | F212A53414CDC35900B65EA9 /* Products */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | F212A53414CDC35900B65EA9 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | F212A53314CDC35900B65EA9 /* SOExtendedAttributes.UnitTests.octest */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | F212A53514CDC35900B65EA9 /* Frameworks */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | F2CBB2B914E8EEE300937A53 /* Foundation.framework */, 69 | F212A53614CDC35900B65EA9 /* SenTestingKit.framework */, 70 | F212A53A14CDC35900B65EA9 /* Other Frameworks */, 71 | ); 72 | name = Frameworks; 73 | sourceTree = ""; 74 | }; 75 | F212A53A14CDC35900B65EA9 /* Other Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | ); 79 | name = "Other Frameworks"; 80 | sourceTree = ""; 81 | }; 82 | F212A53E14CDC35900B65EA9 /* UnitTests */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | F212A55014CDC58800B65EA9 /* SOExtendedAttributes_UnitTests.m */, 86 | F212A53F14CDC35900B65EA9 /* Supporting Files */, 87 | ); 88 | name = UnitTests; 89 | path = SOExtendedAttributes.UnitTests; 90 | sourceTree = ""; 91 | }; 92 | F212A53F14CDC35900B65EA9 /* Supporting Files */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | F2191C1614CE06AA004557C9 /* SOUsefulFunctions.m */, 96 | F2191C1714CE06AA004557C9 /* SOUsefulFunctions.h */, 97 | F212A55214CDC5A900B65EA9 /* UnitTests-Info.plist */, 98 | F212A55314CDC5A900B65EA9 /* UnitTests-Prefix.pch */, 99 | F212A55414CDC5B600B65EA9 /* InfoPlist.strings */, 100 | ); 101 | name = "Supporting Files"; 102 | sourceTree = ""; 103 | }; 104 | F212A55814CDC65900B65EA9 /* Documentation */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | F26FC50518222B4A00627018 /* README.md */, 108 | F212A55914CDC65900B65EA9 /* appledoc.sh */, 109 | F212A55A14CDC65900B65EA9 /* net.standardorbit.SOExtendedAttributes.docset */, 110 | F212A55B14CDC65900B65EA9 /* README.txt */, 111 | ); 112 | path = Documentation; 113 | sourceTree = ""; 114 | }; 115 | /* End PBXGroup section */ 116 | 117 | /* Begin PBXNativeTarget section */ 118 | F212A53214CDC35900B65EA9 /* SOExtendedAttributes.UnitTests */ = { 119 | isa = PBXNativeTarget; 120 | buildConfigurationList = F212A54A14CDC35900B65EA9 /* Build configuration list for PBXNativeTarget "SOExtendedAttributes.UnitTests" */; 121 | buildPhases = ( 122 | F212A52E14CDC35900B65EA9 /* Sources */, 123 | F212A52F14CDC35900B65EA9 /* Frameworks */, 124 | F212A53014CDC35900B65EA9 /* Resources */, 125 | F212A53114CDC35900B65EA9 /* ShellScript */, 126 | ); 127 | buildRules = ( 128 | ); 129 | dependencies = ( 130 | ); 131 | name = SOExtendedAttributes.UnitTests; 132 | productName = SOExtendedAttributes.UnitTests; 133 | productReference = F212A53314CDC35900B65EA9 /* SOExtendedAttributes.UnitTests.octest */; 134 | productType = "com.apple.product-type.bundle"; 135 | }; 136 | /* End PBXNativeTarget section */ 137 | 138 | /* Begin PBXProject section */ 139 | F212A52114CDC2D900B65EA9 /* Project object */ = { 140 | isa = PBXProject; 141 | attributes = { 142 | LastUpgradeCheck = 0420; 143 | ORGANIZATIONNAME = "Standard Orbit Software, LLC"; 144 | }; 145 | buildConfigurationList = F212A52414CDC2D900B65EA9 /* Build configuration list for PBXProject "SOExtendedAttributes" */; 146 | compatibilityVersion = "Xcode 3.2"; 147 | developmentRegion = English; 148 | hasScannedForEncodings = 0; 149 | knownRegions = ( 150 | en, 151 | ); 152 | mainGroup = F212A51F14CDC2D900B65EA9; 153 | productRefGroup = F212A53414CDC35900B65EA9 /* Products */; 154 | projectDirPath = ""; 155 | projectRoot = ""; 156 | targets = ( 157 | F212A53214CDC35900B65EA9 /* SOExtendedAttributes.UnitTests */, 158 | ); 159 | }; 160 | /* End PBXProject section */ 161 | 162 | /* Begin PBXResourcesBuildPhase section */ 163 | F212A53014CDC35900B65EA9 /* Resources */ = { 164 | isa = PBXResourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | F212A55614CDC5B600B65EA9 /* InfoPlist.strings in Resources */, 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | /* End PBXResourcesBuildPhase section */ 172 | 173 | /* Begin PBXShellScriptBuildPhase section */ 174 | F212A53114CDC35900B65EA9 /* ShellScript */ = { 175 | isa = PBXShellScriptBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | ); 179 | inputPaths = ( 180 | ); 181 | outputPaths = ( 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | shellPath = /bin/sh; 185 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 186 | }; 187 | /* End PBXShellScriptBuildPhase section */ 188 | 189 | /* Begin PBXSourcesBuildPhase section */ 190 | F212A52E14CDC35900B65EA9 /* Sources */ = { 191 | isa = PBXSourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | F212A54E14CDC3D100B65EA9 /* NSURL+SOExtendedAttributes.m in Sources */, 195 | F212A55114CDC58800B65EA9 /* SOExtendedAttributes_UnitTests.m in Sources */, 196 | F2191C1814CE06AA004557C9 /* SOUsefulFunctions.m in Sources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXSourcesBuildPhase section */ 201 | 202 | /* Begin PBXVariantGroup section */ 203 | F212A55414CDC5B600B65EA9 /* InfoPlist.strings */ = { 204 | isa = PBXVariantGroup; 205 | children = ( 206 | F212A55514CDC5B600B65EA9 /* en */, 207 | ); 208 | name = InfoPlist.strings; 209 | sourceTree = ""; 210 | }; 211 | /* End PBXVariantGroup section */ 212 | 213 | /* Begin XCBuildConfiguration section */ 214 | F212A52614CDC2D900B65EA9 /* Debug */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | CLANG_ENABLE_OBJC_ARC = YES; 218 | GCC_VERSION = ""; 219 | MACOSX_DEPLOYMENT_TARGET = ""; 220 | SDKROOT = macosx; 221 | WARNING_CFLAGS = "-Wall"; 222 | }; 223 | name = Debug; 224 | }; 225 | F212A52714CDC2D900B65EA9 /* Release */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | GCC_VERSION = ""; 230 | MACOSX_DEPLOYMENT_TARGET = ""; 231 | SDKROOT = macosx; 232 | WARNING_CFLAGS = "-Wall"; 233 | }; 234 | name = Release; 235 | }; 236 | F212A54814CDC35900B65EA9 /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 241 | COPY_PHASE_STRIP = NO; 242 | FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; 243 | GCC_C_LANGUAGE_STANDARD = "compiler-default"; 244 | GCC_DYNAMIC_NO_PIC = NO; 245 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 246 | GCC_OPTIMIZATION_LEVEL = 0; 247 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 248 | GCC_PREFIX_HEADER = "UnitTests/UnitTests-Prefix.pch"; 249 | GCC_PREPROCESSOR_DEFINITIONS = ( 250 | "DEBUG=1", 251 | "$(inherited)", 252 | ); 253 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 254 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 257 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 258 | GCC_WARN_UNUSED_VARIABLE = YES; 259 | INFOPLIST_FILE = "UnitTests/UnitTests-Info.plist"; 260 | ONLY_ACTIVE_ARCH = YES; 261 | OTHER_LDFLAGS = ( 262 | "-framework", 263 | Foundation, 264 | "-framework", 265 | SenTestingKit, 266 | ); 267 | PRODUCT_NAME = "$(TARGET_NAME)"; 268 | WRAPPER_EXTENSION = octest; 269 | }; 270 | name = Debug; 271 | }; 272 | F212A54914CDC35900B65EA9 /* Release */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | ALWAYS_SEARCH_USER_PATHS = NO; 276 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 277 | COPY_PHASE_STRIP = YES; 278 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 279 | FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; 280 | GCC_C_LANGUAGE_STANDARD = "compiler-default"; 281 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 282 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 283 | GCC_PREFIX_HEADER = "UnitTests/UnitTests-Prefix.pch"; 284 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 286 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 287 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | INFOPLIST_FILE = "UnitTests/UnitTests-Info.plist"; 290 | OTHER_LDFLAGS = ( 291 | "-framework", 292 | Foundation, 293 | "-framework", 294 | SenTestingKit, 295 | ); 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | WRAPPER_EXTENSION = octest; 298 | }; 299 | name = Release; 300 | }; 301 | /* End XCBuildConfiguration section */ 302 | 303 | /* Begin XCConfigurationList section */ 304 | F212A52414CDC2D900B65EA9 /* Build configuration list for PBXProject "SOExtendedAttributes" */ = { 305 | isa = XCConfigurationList; 306 | buildConfigurations = ( 307 | F212A52614CDC2D900B65EA9 /* Debug */, 308 | F212A52714CDC2D900B65EA9 /* Release */, 309 | ); 310 | defaultConfigurationIsVisible = 0; 311 | defaultConfigurationName = Release; 312 | }; 313 | F212A54A14CDC35900B65EA9 /* Build configuration list for PBXNativeTarget "SOExtendedAttributes.UnitTests" */ = { 314 | isa = XCConfigurationList; 315 | buildConfigurations = ( 316 | F212A54814CDC35900B65EA9 /* Debug */, 317 | F212A54914CDC35900B65EA9 /* Release */, 318 | ); 319 | defaultConfigurationIsVisible = 0; 320 | defaultConfigurationName = Release; 321 | }; 322 | /* End XCConfigurationList section */ 323 | }; 324 | rootObject = F212A52114CDC2D900B65EA9 /* Project object */; 325 | } 326 | -------------------------------------------------------------------------------- /UnitTests/SOExtendedAttributes_UnitTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SOExtendedAttributes_UnitTests.m 3 | // SOExtendedAttributes.UnitTests 4 | // 5 | // Created by William Garrison on 1/23/12. 6 | // Copyright (c) 2012 Standard Orbit Software, LLC. All rights reserved. 7 | // 8 | 9 | #import "NSURL+SOExtendedAttributes.h" 10 | #import "SOUsefulFunctions.h" 11 | #include 12 | 13 | @interface SOExtendedAttributes_UnitTests : SenTestCase 14 | @end 15 | 16 | @implementation SOExtendedAttributes_UnitTests 17 | { 18 | NSURL *targetURL; 19 | } 20 | 21 | #pragma mark Fixture 22 | 23 | - (BOOL) createTestURLForTest:(SEL)testSelector 24 | { 25 | BOOL didCreate = NO; 26 | 27 | targetURL = [NSURL fileURLWithPathComponents:[NSArray arrayWithObjects:NSTemporaryDirectory(), NSStringFromSelector(testSelector), SOGeneratedUUID(), nil]]; 28 | 29 | if (targetURL) 30 | { 31 | /* First create the intermediate parent directory */ 32 | didCreate = [[NSFileManager defaultManager] createDirectoryAtURL:[targetURL URLByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; 33 | 34 | /* Then try creating an empty test file. */ 35 | if (didCreate) { 36 | didCreate = [[NSFileManager defaultManager] createFileAtPath:[targetURL path] contents:[NSData data] attributes:nil]; 37 | } 38 | } 39 | 40 | return didCreate; 41 | } 42 | 43 | 44 | - (void) setUp 45 | { 46 | [super setUp]; 47 | } 48 | 49 | - (void) tearDown 50 | { 51 | if (targetURL) { 52 | if ( ![[NSFileManager defaultManager] removeItemAtPath:[targetURL path] error:nil]) { 53 | NSLog (@"Couldn't cleanup test file: %@", targetURL); 54 | } 55 | } 56 | 57 | [super tearDown]; 58 | } 59 | 60 | #pragma mark - Error Reporting Tests 61 | 62 | - (void) testCollectedErrrors 63 | { 64 | /* Test that underlying errors generated from xattr are collected and reported properly. */ 65 | 66 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 67 | NSError *error = nil; 68 | 69 | NSString *excessivelyLongName1 = @"Loremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliqua.Utenimadminimveniamwangchung"; 70 | NSString *excessivelyLongName2 = @"Loremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliqua.Utenimadminimveniamwangchungscooby"; 71 | NSString *excessivelyLongName3 = @"Loremipsumdolorsitametconsecteturadipisicingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliqua.Utenimadminimveniamwangchungshaggy"; 72 | 73 | NSDictionary *badlyNamedAttribs = [NSDictionary dictionaryWithObjectsAndKeys: 74 | @"LexLuthor" ,excessivelyLongName1, 75 | @"Magneto", excessivelyLongName2, 76 | @"KhanNoonianSingh", excessivelyLongName3, 77 | nil 78 | ]; 79 | 80 | BOOL didAdd = [targetURL setExtendedAttributes:badlyNamedAttribs error:&error]; 81 | 82 | NSLog (@"error: %@", error); 83 | 84 | STAssertFalse (didAdd, @"Expected failure"); 85 | STAssertNotNil (error, @"Expected an error report"); 86 | STAssertTrue ( [[[error userInfo] objectForKey:SOUnderlyingErrorsKey] isKindOfClass:[NSArray class]], @"Expected array of collected errors."); 87 | STAssertTrue ( [[[error userInfo] objectForKey:SOUnderlyingErrorsKey] count] > 0, @"Expected multiple errors to be collected into an array."); 88 | } 89 | 90 | #pragma mark 91 | #pragma mark Batch Attribute Tests 92 | 93 | - (void) testAddRetrieveBatchOfAttributes 94 | { 95 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 96 | NSError *error = nil; 97 | 98 | NSMutableDictionary *testAttributes = [NSMutableDictionary dictionary]; 99 | [testAttributes setObject:@"Groucho" forKey:@"Favorite Mood"]; 100 | [testAttributes setObject:@"Harpo" forKey:@"Name of high school"]; 101 | [testAttributes setObject:@"Chico" forKey:@"City in California"]; 102 | [testAttributes setObject:[NSDate date] forKey:@"Birthday"]; 103 | 104 | /* Test batch add */ 105 | BOOL didAdd = [targetURL setExtendedAttributes:testAttributes error:&error]; 106 | STAssertTrue (didAdd, @"%@", error); 107 | 108 | /* Test batch retrieve */ 109 | error = nil; 110 | NSDictionary *retrievedAttributes = [targetURL extendedAttributesWithError:&error]; 111 | STAssertNotNil (retrievedAttributes, @"postcondition violated"); 112 | STAssertTrue ([retrievedAttributes isEqualToDictionary:testAttributes], @"postcondition violated"); 113 | } 114 | 115 | - (void) testHasAttributes 116 | { 117 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 118 | NSError *error = nil; 119 | 120 | /* Create a test file */ 121 | 122 | 123 | 124 | NSMutableDictionary *testAttributes = [NSMutableDictionary dictionary]; 125 | [testAttributes setObject:@"Groucho" forKey:@"Favorite Mood"]; 126 | [testAttributes setObject:@"Harpo" forKey:@"Name of high school"]; 127 | [testAttributes setObject:@"Chico" forKey:@"City in California"]; 128 | [testAttributes setObject:[NSDate date] forKey:@"Birthday"]; 129 | 130 | BOOL didAdd = [targetURL setExtendedAttributes:testAttributes error:&error]; 131 | STAssertTrue (didAdd, @"%@", error); 132 | 133 | STAssertTrue ([targetURL hasExtendedAttributeWithName:@"Favorite Mood"], @"postcondition violated"); 134 | STAssertTrue ([targetURL hasExtendedAttributeWithName:@"Birthday"], @"postcondition violated"); 135 | STAssertFalse ([targetURL hasExtendedAttributeWithName:@"Total Eclipse of the Heart"], @"postcondition violated"); 136 | } 137 | 138 | #pragma mark - Getter Tests 139 | 140 | - (void) testGetNonexistentAttribute 141 | { 142 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 143 | NSError *error = nil; 144 | 145 | /* Ask for value of nonexistent extended attribute */ 146 | id xattrValue = [targetURL valueOfExtendedAttributeWithName:@"geordi.laforge" error:&error]; 147 | 148 | STAssertNil (xattrValue, @"Expected nil value for nonexistent extended attribute"); 149 | STAssertTrue (error.code == ENOATTR, @"Expected 'attribute not found' error"); 150 | } 151 | 152 | #pragma mark - Removal Tests 153 | 154 | - (void) testRemoveNonexistentAttribute 155 | { 156 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 157 | NSError *error = nil; 158 | 159 | /* Removing non-existent attribs is OK. */ 160 | BOOL didRemove = [targetURL removeExtendedAttributeWithName:@"Jughead" error:&error]; 161 | STAssertTrue (didRemove, @"%@", error); 162 | } 163 | 164 | - (void) testAddRemoveSingleAttribute 165 | { 166 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 167 | 168 | NSError *error = nil; 169 | NSString *attribName = @"net.standardorbit.latinPlaceholderText"; 170 | id attribValue = @"Lorem ipsum dolor sit amet"; 171 | 172 | /* Test setting an extended attribute value */ 173 | 174 | STAssertTrue ( [targetURL setExtendedAttributeValue:attribValue forName:attribName error:&error], @"%@", error); 175 | 176 | /* Test retrieving the extended attribute value */ 177 | 178 | id retrievedValue = [targetURL valueOfExtendedAttributeWithName:attribName error:&error]; 179 | STAssertNotNil (retrievedValue, @"%@", error); 180 | 181 | /* Remove the extended attribute */ 182 | 183 | BOOL didRemove = [targetURL removeExtendedAttributeWithName:attribName error:&error]; 184 | STAssertTrue (didRemove, @"%@; %@", error, [error userInfo]); 185 | } 186 | 187 | #pragma mark - 188 | #pragma mark Single Attribute Tests 189 | 190 | - (void) testStringAttribute 191 | { 192 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 193 | NSError *error = nil; 194 | 195 | 196 | NSString *flog = @"flog"; 197 | BOOL didSet = [targetURL setExtendedAttributeValue:flog forName:@"flogger" error:&error]; 198 | STAssertTrue (didSet, @"%@", error); 199 | 200 | error = nil; 201 | id retrievedValue = [targetURL valueOfExtendedAttributeWithName:@"flogger" error:&error]; 202 | STAssertNotNil (retrievedValue, @"%@",error); 203 | 204 | STAssertTrue ([retrievedValue isKindOfClass:[NSString class]], @"postcondition violated"); 205 | STAssertTrue ([flog isEqualToString:retrievedValue], @"postcondition violated"); 206 | } 207 | 208 | - (void) testArrayAttribute 209 | { 210 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 211 | NSError *error = nil; 212 | 213 | 214 | NSArray *colors = [NSArray arrayWithObjects:@"red", @"orange", @"yellow", @"green", @"blue", @"violet", nil]; 215 | STAssertTrue ([targetURL setExtendedAttributeValue:colors forName:@"colors" error:&error], @"%@", error); 216 | 217 | error = nil; 218 | id retrievedValue = [targetURL valueOfExtendedAttributeWithName:@"colors" error:&error]; 219 | STAssertNotNil (retrievedValue, @"%@", error); 220 | STAssertTrue ([retrievedValue isKindOfClass:[NSArray class]], @"postcondition violated"); 221 | STAssertTrue ([colors isEqualToArray:retrievedValue], @"postcondition violated"); 222 | } 223 | 224 | - (void) testDictionaryAttribute 225 | { 226 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 227 | NSError *error = nil; 228 | 229 | NSMutableDictionary *movieInfo = [NSMutableDictionary dictionary]; 230 | [movieInfo setObject:@"Star Wars" forKey:@"title"]; 231 | [movieInfo setObject:@"George Lucas" forKey:@"director"]; 232 | 233 | NSArray *talent = [NSArray arrayWithObjects:@"Mark Hamill", @"Carrie Fisher", @"Harrison Ford", nil]; 234 | [movieInfo setObject:talent forKey:@"talent"]; 235 | 236 | STAssertTrue ([targetURL setExtendedAttributeValue:movieInfo forName:@"movieInfo" error:&error], @"%@", error); 237 | 238 | error = nil; 239 | id retrievedValue = [targetURL valueOfExtendedAttributeWithName:@"movieInfo" error:&error]; 240 | STAssertNotNil (retrievedValue, @"%@", error); 241 | STAssertTrue ([retrievedValue isKindOfClass:[NSDictionary class]], @"postcondition violated"); 242 | STAssertTrue ([movieInfo isEqualToDictionary:retrievedValue], @"postcondition violated"); 243 | } 244 | 245 | - (void) testNumberAttribute 246 | { 247 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 248 | NSError *error = nil; 249 | 250 | /* Create a test file */ 251 | 252 | id testNumber = [NSNumber numberWithFloat:6.28]; 253 | 254 | STAssertTrue ([targetURL setExtendedAttributeValue:testNumber forName:@"number" error:&error], @"%@", error); 255 | 256 | error = nil; 257 | id retrievedValue = [targetURL valueOfExtendedAttributeWithName:@"number" error:&error]; 258 | STAssertNotNil (retrievedValue, @"%@", error); 259 | STAssertTrue ([retrievedValue isKindOfClass:[NSNumber class]], @"postcondition violated"); 260 | STAssertTrue ([testNumber isEqualToNumber:retrievedValue], @"postcondition violated"); 261 | } 262 | 263 | - (void) testNullAttribute 264 | { 265 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 266 | NSError *error = nil; 267 | 268 | id testNull = [NSNull null]; 269 | 270 | STAssertFalse ([targetURL setExtendedAttributeValue:testNull forName:@"null" error:&error], @"%@", error); 271 | STAssertTrue ([error code] == SOExtendedAttributesValueCantBeSerialized, @"postcondition violated"); 272 | } 273 | 274 | - (void) testBooleanYes 275 | { 276 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 277 | NSError *error = nil; 278 | 279 | 280 | id testBoolean = (id)kCFBooleanTrue; 281 | STAssertTrue ([targetURL setExtendedAttributeValue:testBoolean forName:@"boolean" error:&error], @"%@", error); 282 | 283 | error = nil; 284 | id retrievedValue = [targetURL valueOfExtendedAttributeWithName:@"boolean" error:&error]; 285 | STAssertNotNil (retrievedValue, @"%@", error); 286 | STAssertTrue ([retrievedValue isKindOfClass:[NSNumber class]], @"postcondition violated"); 287 | STAssertTrue ([testBoolean isEqualToNumber:retrievedValue], @"postcondition violated"); 288 | } 289 | 290 | - (void) testBooleanNo 291 | { 292 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 293 | NSError *error = nil; 294 | 295 | /* Create a test file */ 296 | 297 | 298 | 299 | id testBoolean = (id)kCFBooleanFalse; 300 | STAssertTrue ([targetURL setExtendedAttributeValue:testBoolean forName:@"boolean" error:&error], @"%@", error); 301 | 302 | error = nil; 303 | id retrievedValue = [targetURL valueOfExtendedAttributeWithName:@"boolean" error:&error]; 304 | STAssertNotNil (retrievedValue, @"%@", error); 305 | STAssertTrue ([retrievedValue isKindOfClass:[NSNumber class]], @"postcondition violated"); 306 | STAssertTrue ([testBoolean isEqualToNumber:retrievedValue], @"postcondition violated"); 307 | } 308 | 309 | #pragma mark 310 | #pragma mark Bad Parameter Tests 311 | 312 | - (void) testAttributeNameTooLong 313 | { 314 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 315 | NSError *error = nil; 316 | 317 | /* Create a test file */ 318 | 319 | 320 | 321 | NSArray *wordList = [NSArray arrayWithObjects:@"red", @"orange", @"yellow", @"green", @"blue", @"violet", nil]; 322 | NSMutableString *WayTooLongName = [NSMutableString string]; 323 | while ([WayTooLongName length] <= XATTR_MAXNAMELEN) 324 | { 325 | [WayTooLongName appendString:[wordList objectAtIndex:(arc4random() % [wordList count])]]; 326 | } 327 | 328 | STAssertFalse ([targetURL setExtendedAttributeValue:@"something" forName:WayTooLongName error:&error], @"%@", error); 329 | STAssertTrue ([error code] == ENAMETOOLONG, @"postcondition violated"); 330 | } 331 | 332 | 333 | - (void) testAddAttributeEmptyName 334 | { 335 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 336 | NSError *error = nil; 337 | 338 | // test empty name 339 | STAssertThrows ([targetURL setExtendedAttributeValue:nil forName:@"" error:&error], @"expected param exception"); 340 | 341 | // Test nil name 342 | error = nil; 343 | STAssertThrows ([targetURL setExtendedAttributeValue:nil forName:nil error:&error], @"expected param exception"); 344 | } 345 | 346 | - (void) testAccessAttributeEmptyName 347 | { 348 | NSError *error = nil; 349 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 350 | 351 | // test empty name 352 | STAssertThrows ([targetURL valueOfExtendedAttributeWithName:@"" error:&error], @"expected param exception"); 353 | 354 | // Test nil name 355 | error = nil; 356 | STAssertThrows ([targetURL valueOfExtendedAttributeWithName:nil error:&error], @"expected param exception"); 357 | } 358 | 359 | - (void) testRemoveAttributeEmptyName 360 | { 361 | NSError *error = nil; 362 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 363 | 364 | // test empty name 365 | STAssertThrows ([targetURL removeExtendedAttributeWithName:@"" error:&error], @"expected param exception"); 366 | 367 | // Test nil name 368 | error = nil; 369 | STAssertThrows ([targetURL removeExtendedAttributeWithName:nil error:&error], @"expected param exception"); 370 | 371 | } 372 | 373 | - (void) testHasAttributeWithEmptyName 374 | { 375 | NSError *error = nil; 376 | STAssertTrue([self createTestURLForTest:_cmd], @"Couldn't create test file"); 377 | 378 | STAssertFalse ([targetURL hasExtendedAttributeWithName:@""], @"expected no report of attribute with empty name."); 379 | 380 | // Test nil name 381 | error = nil; 382 | STAssertFalse ([targetURL hasExtendedAttributeWithName:nil], @"expected no report of attribute with nil name"); 383 | } 384 | 385 | - (void) testNonFileURL 386 | { 387 | NSURL *url = [NSURL URLWithString:@"http://www.apple.com"]; 388 | 389 | STAssertThrows ([url extendedAttributesWithError:NULL], @"expected NSInternalConsistencyExpection"); 390 | STAssertThrows ([url setExtendedAttributes:[NSDictionary dictionary] error:NULL], @"expected NSInternalConsistencyExpection"); 391 | 392 | STAssertThrows ([url hasExtendedAttributeWithName:@"bob"], @"expected NSInternalConsistencyExpection"); 393 | 394 | STAssertThrows ([url setExtendedAttributeValue:nil forName:@"bob" error:NULL], @"expected NSInternalConsistencyExpection"); 395 | STAssertThrows ([url valueOfExtendedAttributeWithName:@"bob" error:NULL], @"expected NSInternalConsistencyExpection"); 396 | 397 | STAssertThrows ([url removeExtendedAttributeWithName:@"bob" error:NULL], @"expected NSInternalConsistencyExpection"); 398 | } 399 | 400 | 401 | @end 402 | -------------------------------------------------------------------------------- /NSURL+SOExtendedAttributes.m: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | NSURL+SOExtendedAttributes 4 | 5 | Copyright 2012-2014 Standard Orbit Software, LLC. All rights reserved. 6 | License at the bottom of the file. 7 | */ 8 | 9 | #if ! __has_feature(objc_arc) 10 | #error This file must be compiled with ARC (set -fobjc_arc flag on file) 11 | #endif 12 | 13 | #import 14 | #import "NSURL+SOExtendedAttributes.h" 15 | #import 16 | #include 17 | 18 | NSString * const iCloudDoNotBackupAttributeName = @"com.apple.MobileBackup"; 19 | NSString * const SOExtendedAttributesErrorDomain = @"SOExtendedAttributesErrorDomain"; 20 | NSString * const SOUnderlyingErrorsKey = @"SOUnderlyingErrors"; 21 | NSString * const SOExtendedAttributeNameKey = @"SOExtendedAttributeName"; 22 | 23 | /* Use default options with xattr API that don't resolve symlinks and show the HFS compression extended attribute. */ 24 | 25 | static int xattrDefaultOptions = XATTR_NOFOLLOW | XATTR_SHOWCOMPRESSION; 26 | 27 | /* Make an NSError from the global errno and optionally the url */ 28 | static inline NSError *SOPOSIXErrorForURL(NSURL *url) 29 | { 30 | int posixErr = errno; 31 | NSString *errDesc; 32 | 33 | switch (posixErr) 34 | { 35 | case ENAMETOOLONG: 36 | errDesc = @"Attribute name too long"; 37 | break; 38 | 39 | case E2BIG: 40 | errDesc = @"Attribute too big"; 41 | break; 42 | 43 | default: 44 | errDesc = [NSString stringWithUTF8String: strerror(posixErr)]; 45 | break; 46 | } 47 | 48 | 49 | NSMutableDictionary *errInfo = [NSMutableDictionary dictionary]; 50 | [errInfo setObject:errDesc forKey:NSLocalizedDescriptionKey]; 51 | 52 | if (url) { 53 | [errInfo setObject:url forKey:NSURLErrorKey]; 54 | } 55 | 56 | return [NSError errorWithDomain:NSPOSIXErrorDomain code:posixErr userInfo:errInfo]; 57 | } 58 | 59 | @implementation NSURL (SOExtendedAttributes) 60 | 61 | - (NSArray *) namesOfExtendedAttributesWithError:(NSError * __autoreleasing *)outError 62 | { 63 | if (![self isFileURL]) [NSException raise:NSInternalInconsistencyException format:@"%s only valid on file URLs", __PRETTY_FUNCTION__]; 64 | 65 | NSMutableArray *attributeNames = [[NSMutableArray alloc] init]; 66 | 67 | @autoreleasepool 68 | { 69 | /* Get attributes names. Bail with nil dictionary if problem */ 70 | 71 | const char *itemPath = [[self path] fileSystemRepresentation]; 72 | 73 | /* Get the size of the attributes list, then extract each name as an NSString. */ 74 | 75 | void *namesBuffer = NULL; 76 | 77 | ssize_t bufferSize = listxattr (itemPath, NULL, SIZE_MAX, xattrDefaultOptions); 78 | if (bufferSize > 0) 79 | { 80 | namesBuffer = calloc(1, bufferSize); 81 | bufferSize = listxattr (itemPath, namesBuffer, bufferSize, xattrDefaultOptions ); 82 | } 83 | 84 | /* No names? Bail now with empty list. */ 85 | if (bufferSize == 0) 86 | { 87 | if (namesBuffer) free(namesBuffer); 88 | return attributeNames; 89 | } 90 | 91 | /* Problemo? Bail now with the POSIX error. */ 92 | if (bufferSize == -1) 93 | { 94 | if (namesBuffer) free(namesBuffer); 95 | 96 | attributeNames = nil; 97 | if (outError) *outError = SOPOSIXErrorForURL(self); 98 | return nil; 99 | } 100 | 101 | /* Parse the name buffer for attribute names. 102 | 103 | Iterate the buffer character by character, looking for a NULL byte. 104 | When found, collect the range of bytes into an NSString and cache in our names list. 105 | */ 106 | 107 | uintptr_t ptr_startOfBuffer = (uintptr_t)namesBuffer; 108 | uintptr_t ptr_startOfName = ptr_startOfBuffer; 109 | 110 | for (ssize_t x = 0; x < bufferSize; x++ ) 111 | { 112 | /* Advance current byte pointer */ 113 | 114 | uintptr_t ptr_currentByte = ptr_startOfBuffer + x; 115 | 116 | /* Check for the end of a name */ 117 | 118 | if ( ptr_currentByte && *((char *)ptr_currentByte) == 0x0 ) 119 | { 120 | /* Collect the attribute name */ 121 | 122 | NSString *name = [[NSString alloc] initWithUTF8String:(char *)ptr_startOfName]; 123 | [attributeNames addObject: name]; 124 | name = nil; 125 | 126 | /* Reset the start of name pointer */ 127 | 128 | ptr_startOfName = ptr_currentByte + 1; 129 | } 130 | } 131 | 132 | /* Free the memory! */ 133 | free(namesBuffer); namesBuffer = NULL; 134 | } 135 | 136 | return attributeNames; 137 | } 138 | 139 | - (NSData *) dataForExtendedAttribute:(NSString *)name error:(NSError * __autoreleasing *)outError 140 | { 141 | if (!name || [name isEqualToString:@""]) { 142 | [NSException raise:NSInvalidArgumentException format:@"extended attribute name cannot be empty"]; 143 | } 144 | 145 | /* First query for the size of the attribute value, then pull it into an NSData is possible */ 146 | 147 | const char *itemPath = [[self path] fileSystemRepresentation]; 148 | void *valueDataBuffer = NULL; 149 | ssize_t dataSize = getxattr (itemPath, [name UTF8String], NULL, SIZE_MAX, 0, xattrDefaultOptions); 150 | if (dataSize != -1) { 151 | valueDataBuffer = calloc(1, dataSize); 152 | dataSize = getxattr (itemPath, [name UTF8String], valueDataBuffer, dataSize, 0, xattrDefaultOptions ); 153 | } 154 | 155 | /* */ 156 | 157 | NSData *xattrData = nil; 158 | 159 | if (dataSize == -1) { 160 | /* Clean up memory */ 161 | if (valueDataBuffer) { 162 | free(valueDataBuffer); valueDataBuffer = NULL; 163 | } 164 | 165 | if (outError) { 166 | NSError *posixError = SOPOSIXErrorForURL(self); 167 | NSMutableDictionary *augmentedErrorInfo = [[posixError userInfo] mutableCopy]; 168 | augmentedErrorInfo[SOExtendedAttributeNameKey] = name; 169 | *outError = [NSError errorWithDomain:[posixError domain] code:[posixError code] userInfo:augmentedErrorInfo]; 170 | } 171 | 172 | } else { 173 | xattrData = [[NSData alloc] initWithBytesNoCopy:valueDataBuffer length:dataSize freeWhenDone:YES]; 174 | } 175 | 176 | return xattrData; 177 | } 178 | 179 | - (BOOL) setExtendedAttributeData:(NSData *)data name:(NSString *)name error:(NSError * __autoreleasing *)outError 180 | { 181 | if (!name || [name isEqualToString:@""]) { 182 | [NSException raise:NSInvalidArgumentException format:@"extended attribute name cannot be empty"]; 183 | } 184 | 185 | /* Set data as extended attribute value */ 186 | 187 | int err = setxattr ( [[self path] fileSystemRepresentation], [name UTF8String], [data bytes], [data length], 0, XATTR_NOFOLLOW); 188 | if (err != 0) 189 | { 190 | if (outError) { 191 | NSError *posixError = SOPOSIXErrorForURL(self); 192 | NSMutableDictionary *augmentedErrorInfo = [[posixError userInfo] mutableCopy]; 193 | augmentedErrorInfo[SOExtendedAttributeNameKey] = name; 194 | *outError = [NSError errorWithDomain:[posixError domain] code:[posixError code] userInfo:augmentedErrorInfo]; 195 | } 196 | } 197 | 198 | return (err == 0); 199 | } 200 | 201 | #pragma mark - 202 | #pragma mark Batch Attributes 203 | 204 | - (NSDictionary *) extendedAttributesWithError:(NSError * __autoreleasing *)outError 205 | { 206 | /* Get names of all extended attributes associated with this URL. */ 207 | 208 | NSArray *attributeNames = [self namesOfExtendedAttributesWithError:outError]; 209 | if (attributeNames == nil) return nil; 210 | 211 | /* Collect the value for each found extended attribute. */ 212 | 213 | NSMutableDictionary *collectedAttributes = [[NSMutableDictionary alloc] initWithCapacity:[attributeNames count]]; 214 | NSMutableArray *collectedErrors = [NSMutableArray array]; 215 | 216 | for (NSString *name in attributeNames) 217 | { 218 | NSError *error = nil; 219 | id value = [self valueOfExtendedAttributeWithName:name error:&error]; 220 | 221 | /* Collect the value or collect the error. */ 222 | 223 | if (value) { 224 | [collectedAttributes setObject:value forKey:name]; 225 | } 226 | else if (error) { 227 | [collectedErrors addObject:error]; 228 | break; 229 | } 230 | 231 | } 232 | 233 | /* Did we get any errors? */ 234 | 235 | BOOL hasErrors = [collectedErrors count] > 0; 236 | if (hasErrors && outError) 237 | { 238 | collectedAttributes = nil; 239 | 240 | NSMutableDictionary *errInfo = [NSMutableDictionary dictionary]; 241 | [errInfo setObject:NSLocalizedString(@"Failed to get one or more extended attribute values", @"Error message description for SOExtendedAttributesGetValueError") forKey:NSLocalizedDescriptionKey]; 242 | [errInfo setObject:self forKey:NSURLErrorKey]; 243 | [errInfo setObject:collectedErrors forKey:SOUnderlyingErrorsKey]; 244 | *outError = [NSError errorWithDomain:SOExtendedAttributesErrorDomain code:SOExtendedAttributesGetValueError userInfo:errInfo]; 245 | } 246 | 247 | return collectedAttributes; 248 | } 249 | 250 | - (BOOL) setExtendedAttributes:(NSDictionary *)attributes error:(NSError * __autoreleasing *)outError 251 | { 252 | if (![self isFileURL]) [NSException raise:NSInternalInconsistencyException format:@"%s only valid on file URLs", __PRETTY_FUNCTION__]; 253 | 254 | /* It is OK but silly to pass in empty attributes. */ 255 | 256 | if ([attributes count] == 0) return YES; 257 | 258 | /* Attempt to set all attribute values in the dictionary. Any individual errors are collected and returned as a group. */ 259 | 260 | __block NSMutableArray *collectedErrors = nil; 261 | if (outError) { 262 | collectedErrors = [NSMutableArray arrayWithCapacity:[attributes count]]; 263 | } 264 | [attributes enumerateKeysAndObjectsUsingBlock:^(id name, id value, BOOL *stop) { 265 | NSError *error = nil; 266 | 267 | /* Preflight passed value for binary plist serialization. MUST do this because NSPropertyListSerialization doesn't ALWAYS return an error when something goes wrong, such as attempting to serialize an NSNull. */ 268 | 269 | if ( ! [NSPropertyListSerialization propertyList:value isValidForFormat:NSPropertyListBinaryFormat_v1_0]) 270 | { 271 | if (collectedErrors) { 272 | NSMutableDictionary *errInfo = [NSMutableDictionary dictionary]; 273 | [errInfo setObject:name forKey:SOExtendedAttributeNameKey]; 274 | [errInfo setObject:[NSString stringWithFormat:@"Value of class %@ cannot be serialized into a plist", NSStringFromClass([value class])] forKey:NSLocalizedDescriptionKey]; 275 | [errInfo setObject:value forKey:@"value"]; 276 | error = [NSError errorWithDomain:SOExtendedAttributesErrorDomain code:SOExtendedAttributesValueCantBeSerialized userInfo:errInfo]; 277 | [collectedErrors addObject:error]; 278 | } 279 | } else { 280 | 281 | /* Serialize value to binary plist */ 282 | 283 | NSError *dataError = nil; 284 | NSData *data = [NSPropertyListSerialization dataWithPropertyList:value format:NSPropertyListBinaryFormat_v1_0 options:0 error:&dataError]; 285 | if (data == nil) { 286 | if (collectedErrors) { 287 | NSMutableDictionary *augmentedErrorInfo = [[dataError userInfo] mutableCopy]; 288 | [augmentedErrorInfo setObject:name forKey:SOExtendedAttributeNameKey]; 289 | error = [NSError errorWithDomain:[dataError domain] code:[dataError code] userInfo:augmentedErrorInfo]; 290 | [collectedErrors addObject:error]; 291 | } 292 | } 293 | else { 294 | 295 | /* Set data as extended attribute value */ 296 | NSError *xattrError = nil; 297 | BOOL didSet = [self setExtendedAttributeData:data name:name error:&xattrError]; 298 | if (!didSet && xattrError && collectedErrors) { 299 | [collectedErrors addObject:xattrError]; 300 | } 301 | } 302 | } 303 | 304 | }]; 305 | 306 | /* Did we get any errors? */ 307 | 308 | BOOL hasErrors = [collectedErrors count] > 0; 309 | if (hasErrors && outError) 310 | { 311 | if ([collectedErrors count] == 1) { 312 | *outError = [collectedErrors lastObject]; 313 | } else { 314 | /* Bundle up multiple collected errors using SOUnderlyingErrorsKey */ 315 | NSMutableDictionary *errInfo = [NSMutableDictionary dictionary]; 316 | [errInfo setObject:NSLocalizedString(@"Failed to set one or more extended attributes.", @"Error message description for SOExtendedAttributesSetValueError.") 317 | forKey:NSLocalizedDescriptionKey]; 318 | [errInfo setObject:self forKey:NSURLErrorKey]; 319 | [errInfo setObject:collectedErrors forKey:SOUnderlyingErrorsKey]; 320 | *outError = [NSError errorWithDomain:SOExtendedAttributesErrorDomain code:SOExtendedAttributesSetValueError userInfo:errInfo]; 321 | } 322 | return NO; 323 | } 324 | 325 | /* Got here? No errors. */ 326 | 327 | return YES; 328 | } 329 | 330 | #pragma mark - Individual Attributes 331 | 332 | - (BOOL) hasExtendedAttributeWithName:(NSString *)name 333 | { 334 | if (!name || [name isEqualToString:@""]) return NO; 335 | 336 | NSError *error = nil; 337 | NSArray *attributeNames = [self namesOfExtendedAttributesWithError:&error]; 338 | 339 | if (!attributeNames) 340 | { 341 | NSLog (@"ERROR: Could not get list of attributes names: %@; %@", error, [error userInfo]); 342 | } 343 | return [attributeNames containsObject:name]; 344 | } 345 | 346 | - (id) valueOfExtendedAttributeWithName:(NSString *)name error:(NSError * __autoreleasing *)outError 347 | { 348 | if (![self isFileURL]) [NSException raise:NSInternalInconsistencyException format:@"%s only valid on file URLs", __PRETTY_FUNCTION__]; 349 | if (!name || [name isEqualToString:@""]) [NSException raise:NSInvalidArgumentException format:@"%s name parameter can't be nil", __PRETTY_FUNCTION__]; 350 | 351 | id retrievedValue = nil; 352 | 353 | NSData *data = [self dataForExtendedAttribute:name error:outError]; 354 | if (data) { 355 | retrievedValue = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:outError]; 356 | } 357 | 358 | return retrievedValue; 359 | } 360 | 361 | - (BOOL) setExtendedAttributeValue:(id)value forName:(NSString *)name error:(NSError * __autoreleasing *)outError 362 | { 363 | BOOL didSet = NO; 364 | if (value == nil) { 365 | didSet = [self removeExtendedAttributeWithName:name error:outError]; 366 | } else { 367 | didSet = [self setExtendedAttributes:@{name : value} error:outError]; 368 | } 369 | return didSet; 370 | } 371 | 372 | - (BOOL) removeExtendedAttributeWithName:(NSString *)name error:(NSError * __autoreleasing *)outError 373 | { 374 | if (![self isFileURL]) [NSException raise:NSInternalInconsistencyException format:@"%s only valid on file URLs", __PRETTY_FUNCTION__]; 375 | if (!name || [name isEqualToString:@""]) [NSException raise:NSInvalidArgumentException format:@"%s name parameter can't be nil", __PRETTY_FUNCTION__]; 376 | 377 | int err = removexattr([[self path] fileSystemRepresentation], [name UTF8String], xattrDefaultOptions); 378 | if (err != 0) 379 | { 380 | /* Ignore any ENOATTR error ('attribute not found'), but capture and return all others. */ 381 | if (errno != ENOATTR) 382 | { 383 | if (outError) { 384 | NSError *posixError = SOPOSIXErrorForURL(self); 385 | NSMutableDictionary *augmentedErrorInfo = [[posixError userInfo] mutableCopy]; 386 | [augmentedErrorInfo setObject:name forKey:SOExtendedAttributeNameKey]; 387 | *outError = [NSError errorWithDomain:[posixError domain] code:[posixError code] userInfo:augmentedErrorInfo]; 388 | } 389 | return NO; 390 | } 391 | } 392 | 393 | return YES; 394 | } 395 | 396 | @end 397 | 398 | /* 399 | Copyright (c) 2012-2014, Standard Orbit Software, LLC. All rights reserved. 400 | 401 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 402 | 403 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 404 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 405 | Neither the name of the Standard Orbit Software, LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 406 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 407 | */ -------------------------------------------------------------------------------- /Documentation/net.standardorbit.SOExtendedAttributes.docset/Contents/Resources/Documents/Categories/NSURL+SOExtendedAttributes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSURL(SOExtendedAttributes) Category Reference 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 18 | 19 | 22 | 66 |
67 | 114 |
115 |
116 | 117 | 123 | 128 |
129 | 130 |
131 | 132 | 133 | 134 | 135 |
Declared inNSURL+SOExtendedAttributes.h
NSURL+SOExtendedAttributes.m
136 | 137 | 138 | 139 | 140 |
141 | 142 |

Overview

143 |

The SOExtendedAttributes category on NSURL enables retrieving and manipulating the extended attributes on a file system item.

144 | 145 |

These methods are valid only on file URLs. An NSInternalInconsistencyException is thrown if invoked an a non-file URL.

146 | 147 |

Internally, they’re implemented using listxattr(2), getxattr(2), setxattr(2), and removexattr(2).

148 | 149 |

Compatibility

150 | 151 |

SOExtendedAttributes is compatible with Mac OS X 10.6+ and iOS 5. The clang compiler is required. The source file NSURL+SOExtendedAttributes.m must be compiled with ARC enabled. For an alternate Cocoa implementation compatible with Mac OS X 10.4 and greater, see UKXattrMetadataStore.

152 | 153 |

Symbolic links

154 | 155 |

These methods act on the explicitly given URL. If that URL is to a symbolic link, you’ll be manipulating extended attributes on the symlink, not its original file. Use -URLByResolvingSymlinksInPath to obtain a URL for which points to the original file system item.

156 | 157 |

Use with iCloud Backup

158 | 159 |

Note 2013-06-03: see Apple Tech QA 1719 for the recommended way to mark a file for exclusion from iCloud backup. Hint: don’t use the @“com.apple.MobileMeBackup” extended attribute.

160 | 161 |

Error Reporting

162 | 163 |

SOExtendedAttributes reports errors under the domain SOExtendedAttributesErrorDomain. When multiple errors occur on getting or setting extended attributes in a batch, those errors are collected in an NSArray and reported via error’s -userInfo dictionary under SOUnderlyingErrorsKey.

164 |
165 | 166 | 167 | 168 | 169 | 170 |
171 | 172 |

Tasks

173 | 174 | 175 | 176 |

Other Methods

177 | 178 | 188 | 189 | 190 | 191 |

Accessing attributes in batches

192 | 193 | 210 | 211 | 212 | 213 |

Accessing attributes individually

214 | 215 | 239 | 240 | 241 | 242 |

Removing an extended attribute

243 | 244 | 254 | 255 |
256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 |
266 | 267 |

Instance Methods

268 | 269 |
270 | 271 |

extendedAttributesWithError:

272 | 273 | 274 | 275 |
276 |

Returns the extended attributes of the file system item at this URL.

277 |
278 | 279 | 280 |
- (NSDictionary *)extendedAttributesWithError:(NSError *__autoreleasing *)outError
281 | 282 | 283 |
284 |

Parameters

285 | 286 |
287 |
outError
288 |

If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you’re not interested in error reporting.

289 |
290 | 291 |
292 | 293 | 294 | 295 |
296 |

Return Value

297 |

An NSDictionary object that describes the extended attributes of the file system object, or nil if an error occurred.

298 |
299 | 300 | 301 | 302 | 303 | 304 |
305 |

Discussion

306 |

Return all extended attributes that the current user account has permission to access. Attributes will include the HFS compression extended attribute if present.

307 |
308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 |
316 |

Declared In

317 | NSURL+SOExtendedAttributes.h
318 |
319 | 320 | 321 |
322 | 323 |
324 | 325 |

hasExtendedAttributeWithName:

326 | 327 | 328 | 329 |
330 |

Returns YES if the file system item has the named attribute.

331 |
332 | 333 | 334 |
- (BOOL)hasExtendedAttributeWithName:(NSString *)name
335 | 336 | 337 |
338 |

Parameters

339 | 340 |
341 |
name
342 |

The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty.

343 |
344 | 345 |
346 | 347 | 348 | 349 |
350 |

Return Value

351 |

YES if the named extended attribute is present on the URL; NO otherwise.

352 |
353 | 354 | 355 | 356 | 357 | 358 |
359 |

Discussion

360 |

Sometimes, you’re only interested in the presence or absence of an extended attribute on a given URL. E.g. @“com.apple.MobileMeBackup”.

361 |
362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 |
370 |

Declared In

371 | NSURL+SOExtendedAttributes.h
372 |
373 | 374 | 375 |
376 | 377 |
378 | 379 |

maximumExtendedAttributesSize

380 | 381 | 382 | 383 |
384 |

The maximum size in bytes for an extended attribute on the receiver.

385 |
386 | 387 | 388 |
- (NSUInteger)maximumExtendedAttributesSize
389 | 390 | 391 | 392 | 393 |
394 |

Return Value

395 |

The maximum size in bytes for an extended attribute on the receiver.

396 |
397 | 398 | 399 | 400 | 401 | 402 |
403 |

Discussion

404 |

Uses pathconf(2) to determine the maximum number of bytes that an extended attribute can hold. This is a computed and approximate value because the system does not report a maximum extended attribute size directly; instead, it reports the number of bits by the file system object to used to hold the maximum extended attribute size.

405 | 406 |

The reported maximum extended attribute size for HFS+ on Mac OS X 10.8 is 128KB (131072 bytes), but experimentally, it is actually 50 bytes less: 131022 bytes.

407 |
408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 |
416 |

Declared In

417 | NSURL+SOExtendedAttributes.h
418 |
419 | 420 | 421 |
422 | 423 |
424 | 425 |

removeExtendedAttributeWithName:error:

426 | 427 | 428 | 429 |
430 |

Removes the named extended attribute from this file system item.

431 |
432 | 433 | 434 |
- (BOOL)removeExtendedAttributeWithName:(NSString *)name error:(NSError *__autoreleasing *)outError
435 | 436 | 437 |
438 |

Parameters

439 | 440 |
441 |
name
442 |

The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty.

443 |
444 | 445 |
446 |
outError
447 |

A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you’re not interesting in error reporting.

448 |
449 | 450 |
451 | 452 | 453 | 454 |
455 |

Return Value

456 |

YES if successfully removed or named attribute does not exist. NO if there was an error.

457 |
458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 |
470 |

Declared In

471 | NSURL+SOExtendedAttributes.h
472 |
473 | 474 | 475 |
476 | 477 |
478 | 479 |

setExtendedAttributeValue:forName:error:

480 | 481 | 482 | 483 |
484 |

Set the value of the named extended attribute.

485 |
486 | 487 | 488 |
- (BOOL)setExtendedAttributeValue:(id)value forName:(NSString *)name error:(NSError *__autoreleasing *)outError
489 | 490 | 491 |
492 |

Parameters

493 | 494 |
495 |
value
496 |

The value to be set. Must be an instance of NSData, NSString, NSArray, NSDictionary, NSDate or NSNumber.

497 |
498 | 499 |
500 |
name
501 |

The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty.

502 |
503 | 504 |
505 |
outError
506 |

A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you’re not interesting in error reporting.

507 |
508 | 509 |
510 | 511 | 512 | 513 |
514 |

Return Value

515 |

YES if the given value was set; NO if there was an error.

516 |
517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 |
529 |

Declared In

530 | NSURL+SOExtendedAttributes.h
531 |
532 | 533 | 534 |
535 | 536 |
537 | 538 |

setExtendedAttributes:error:

539 | 540 | 541 | 542 |
543 |

Sets the extended attribute values for the given URL.

544 |
545 | 546 | 547 |
- (BOOL)setExtendedAttributes:(NSDictionary *)attributes error:(NSError *__autoreleasing *)outError
548 | 549 | 550 |
551 |

Parameters

552 | 553 |
554 |
attributes
555 |

The extended attribute names and values to be set. All values be instances of NSData, NSString, NSArray, NSDictionary, NSDate or NSNumber.

556 |
557 | 558 |
559 |
outError
560 |

If an error occurs, upon return contains an NSError object that describes the problem. Pass NULL if you’re not interested in error reporting.

561 |
562 | 563 |
564 | 565 | 566 | 567 |
568 |

Return Value

569 |

YES if all given attribute values were set. NO if there was an error setting one or more of the values.

570 |
571 | 572 | 573 | 574 | 575 | 576 |
577 |

Discussion

578 |

The attributes dictionary parameter may contain any object value that can be encoded as a property list.

579 | 580 |

If the attributes dictionary holds a value object that cannot be encoded as a plist, an NSError with code SOExtendedAttributesValueCantBeSerialized is returned via the outError parameter.

581 | 582 |

On error, one or more of the given extended attributes may have failed to be set. Any underlying errors are reported via the -userInfo dictionary as an NSArray under the key SOUnderlyingErrorsKey.

583 |
584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 |
592 |

Declared In

593 | NSURL+SOExtendedAttributes.h
594 |
595 | 596 | 597 |
598 | 599 |
600 | 601 |

valueOfExtendedAttributeWithName:error:

602 | 603 | 604 | 605 |
606 |

Returns the value of the named extended attribute from this file system item.

607 |
608 | 609 | 610 |
- (id)valueOfExtendedAttributeWithName:(NSString *)name error:(NSError *__autoreleasing *)outError
611 | 612 | 613 |
614 |

Parameters

615 | 616 |
617 |
name
618 |

The name of the extended attribute. Throws NSInvalidArgumentException if name is nil or empty.

619 |
620 | 621 |
622 |
outError
623 |

A pointer to an error object. On return, if an error has occurred, this pointer references an actual error object containing the error information. Pass NULL if you’re not interesting in error reporting.

624 |
625 | 626 |
627 | 628 | 629 | 630 |
631 |

Return Value

632 |

An appropriate Foundation object holding the value, or nil if there was an error.

633 |
634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 |
646 |

Declared In

647 | NSURL+SOExtendedAttributes.h
648 |
649 | 650 | 651 |
652 | 653 |
654 | 655 | 656 |
657 | 663 | 672 |
673 |
674 | 757 | 758 | --------------------------------------------------------------------------------