├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── Example ├── Example.xcodeproj │ └── project.pbxproj └── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── FavIcon.podspec ├── FavIcon.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── FavIcon-iOS.xcscheme │ ├── FavIcon-iOSTests.xcscheme │ ├── FavIcon-macOS.xcscheme │ ├── FavIcon-macOSTests.xcscheme │ └── xcschememanagement.plist ├── FavIcon.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── Sources ├── FavIcon │ ├── ContentTypes.swift │ ├── Detection.swift │ ├── Download.swift │ ├── FavIcon.h │ ├── FavIcon.swift │ ├── HTML.swift │ ├── Icon.swift │ ├── IconType.swift │ └── XML.swift └── Info.plist └── Tests ├── FavIconTests ├── BrowserConfig.xml ├── DetectionTests.swift ├── DownloadTests.swift ├── FavIconTests.swift ├── HTML.html ├── HTMLTests.swift └── Manifest.json └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/swift,xcode 2 | 3 | ### Swift ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | .build 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | 24 | ## Other 25 | *.xccheckout 26 | *.moved-aside 27 | *.xcuserstate 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 39 | # 40 | # Pods/ 41 | 42 | # Carthage 43 | # 44 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 45 | Carthage/Checkouts 46 | Carthage/Build 47 | 48 | 49 | ### Xcode ### 50 | # Xcode 51 | # 52 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 53 | 54 | ## Build generated 55 | build/ 56 | DerivedData 57 | 58 | ## Various settings 59 | *.pbxuser 60 | !default.pbxuser 61 | *.mode1v3 62 | !default.mode1v3 63 | *.mode2v3 64 | !default.mode2v3 65 | *.perspectivev3 66 | !default.perspectivev3 67 | xcuserdata 68 | 69 | ## Other 70 | *.xccheckout 71 | *.moved-aside 72 | *.xcuserstate 73 | .DS_Store 74 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | line_length: 110 2 | type_body_length: 3 | - 300 4 | - 400 5 | excluded: 6 | - Carthage 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode11.4 3 | script: "make test" 4 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 352CDCD8202EFC8100F296CD /* FavIcon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 352CDCD9202EFC8100F296CD /* FavIcon.framework */; }; 11 | 352CDCF9202F017600F296CD /* FavIcon.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 352CDCD9202EFC8100F296CD /* FavIcon.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 3530973F202EFB3E006B32D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3530973E202EFB3E006B32D9 /* AppDelegate.swift */; }; 13 | 35309741202EFB3E006B32D9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35309740202EFB3E006B32D9 /* ViewController.swift */; }; 14 | 35309744202EFB3E006B32D9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35309742202EFB3E006B32D9 /* Main.storyboard */; }; 15 | 35309746202EFB3E006B32D9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 35309745202EFB3E006B32D9 /* Assets.xcassets */; }; 16 | 35309749202EFB3E006B32D9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 35309747202EFB3E006B32D9 /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 352CDCFA202F017700F296CD /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | 352CDCF9202F017600F296CD /* FavIcon.framework in Embed Frameworks */, 27 | ); 28 | name = "Embed Frameworks"; 29 | runOnlyForDeploymentPostprocessing = 0; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 352CDCD9202EFC8100F296CD /* FavIcon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FavIcon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 3530973B202EFB3E006B32D9 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 3530973E202EFB3E006B32D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 35309740202EFB3E006B32D9 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 38 | 35309743202EFB3E006B32D9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 35309745202EFB3E006B32D9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 35309748202EFB3E006B32D9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | 3530974A202EFB3E006B32D9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 35309738202EFB3E006B32D9 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 352CDCD8202EFC8100F296CD /* FavIcon.framework in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 352CDCD7202EFC8100F296CD /* Frameworks */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 352CDCD9202EFC8100F296CD /* FavIcon.framework */, 60 | ); 61 | name = Frameworks; 62 | sourceTree = ""; 63 | }; 64 | 35309732202EFB3E006B32D9 = { 65 | isa = PBXGroup; 66 | children = ( 67 | 3530973D202EFB3E006B32D9 /* Example */, 68 | 3530973C202EFB3E006B32D9 /* Products */, 69 | 352CDCD7202EFC8100F296CD /* Frameworks */, 70 | ); 71 | sourceTree = ""; 72 | }; 73 | 3530973C202EFB3E006B32D9 /* Products */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 3530973B202EFB3E006B32D9 /* Example.app */, 77 | ); 78 | name = Products; 79 | sourceTree = ""; 80 | }; 81 | 3530973D202EFB3E006B32D9 /* Example */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 3530973E202EFB3E006B32D9 /* AppDelegate.swift */, 85 | 35309740202EFB3E006B32D9 /* ViewController.swift */, 86 | 35309742202EFB3E006B32D9 /* Main.storyboard */, 87 | 35309745202EFB3E006B32D9 /* Assets.xcassets */, 88 | 35309747202EFB3E006B32D9 /* LaunchScreen.storyboard */, 89 | 3530974A202EFB3E006B32D9 /* Info.plist */, 90 | ); 91 | path = Example; 92 | sourceTree = ""; 93 | }; 94 | /* End PBXGroup section */ 95 | 96 | /* Begin PBXNativeTarget section */ 97 | 3530973A202EFB3E006B32D9 /* Example */ = { 98 | isa = PBXNativeTarget; 99 | buildConfigurationList = 3530974D202EFB3E006B32D9 /* Build configuration list for PBXNativeTarget "Example" */; 100 | buildPhases = ( 101 | 35309737202EFB3E006B32D9 /* Sources */, 102 | 35309738202EFB3E006B32D9 /* Frameworks */, 103 | 35309739202EFB3E006B32D9 /* Resources */, 104 | 352CDCFA202F017700F296CD /* Embed Frameworks */, 105 | ); 106 | buildRules = ( 107 | ); 108 | dependencies = ( 109 | ); 110 | name = Example; 111 | productName = Example; 112 | productReference = 3530973B202EFB3E006B32D9 /* Example.app */; 113 | productType = "com.apple.product-type.application"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | 35309733202EFB3E006B32D9 /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastSwiftUpdateCheck = 0920; 122 | LastUpgradeCheck = 0930; 123 | ORGANIZATIONNAME = "Leon Breedt"; 124 | TargetAttributes = { 125 | 3530973A202EFB3E006B32D9 = { 126 | CreatedOnToolsVersion = 9.2; 127 | LastSwiftMigration = 1100; 128 | ProvisioningStyle = Automatic; 129 | }; 130 | }; 131 | }; 132 | buildConfigurationList = 35309736202EFB3E006B32D9 /* Build configuration list for PBXProject "Example" */; 133 | compatibilityVersion = "Xcode 8.0"; 134 | developmentRegion = en; 135 | hasScannedForEncodings = 0; 136 | knownRegions = ( 137 | en, 138 | Base, 139 | ); 140 | mainGroup = 35309732202EFB3E006B32D9; 141 | productRefGroup = 3530973C202EFB3E006B32D9 /* Products */; 142 | projectDirPath = ""; 143 | projectRoot = ""; 144 | targets = ( 145 | 3530973A202EFB3E006B32D9 /* Example */, 146 | ); 147 | }; 148 | /* End PBXProject section */ 149 | 150 | /* Begin PBXResourcesBuildPhase section */ 151 | 35309739202EFB3E006B32D9 /* Resources */ = { 152 | isa = PBXResourcesBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | 35309749202EFB3E006B32D9 /* LaunchScreen.storyboard in Resources */, 156 | 35309746202EFB3E006B32D9 /* Assets.xcassets in Resources */, 157 | 35309744202EFB3E006B32D9 /* Main.storyboard in Resources */, 158 | ); 159 | runOnlyForDeploymentPostprocessing = 0; 160 | }; 161 | /* End PBXResourcesBuildPhase section */ 162 | 163 | /* Begin PBXSourcesBuildPhase section */ 164 | 35309737202EFB3E006B32D9 /* Sources */ = { 165 | isa = PBXSourcesBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | 35309741202EFB3E006B32D9 /* ViewController.swift in Sources */, 169 | 3530973F202EFB3E006B32D9 /* AppDelegate.swift in Sources */, 170 | ); 171 | runOnlyForDeploymentPostprocessing = 0; 172 | }; 173 | /* End PBXSourcesBuildPhase section */ 174 | 175 | /* Begin PBXVariantGroup section */ 176 | 35309742202EFB3E006B32D9 /* Main.storyboard */ = { 177 | isa = PBXVariantGroup; 178 | children = ( 179 | 35309743202EFB3E006B32D9 /* Base */, 180 | ); 181 | name = Main.storyboard; 182 | sourceTree = ""; 183 | }; 184 | 35309747202EFB3E006B32D9 /* LaunchScreen.storyboard */ = { 185 | isa = PBXVariantGroup; 186 | children = ( 187 | 35309748202EFB3E006B32D9 /* Base */, 188 | ); 189 | name = LaunchScreen.storyboard; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXVariantGroup section */ 193 | 194 | /* Begin XCBuildConfiguration section */ 195 | 3530974B202EFB3E006B32D9 /* Debug */ = { 196 | isa = XCBuildConfiguration; 197 | buildSettings = { 198 | ALWAYS_SEARCH_USER_PATHS = NO; 199 | CLANG_ANALYZER_NONNULL = YES; 200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 202 | CLANG_CXX_LIBRARY = "libc++"; 203 | CLANG_ENABLE_MODULES = YES; 204 | CLANG_ENABLE_OBJC_ARC = YES; 205 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 206 | CLANG_WARN_BOOL_CONVERSION = YES; 207 | CLANG_WARN_COMMA = YES; 208 | CLANG_WARN_CONSTANT_CONVERSION = YES; 209 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 211 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 212 | CLANG_WARN_EMPTY_BODY = YES; 213 | CLANG_WARN_ENUM_CONVERSION = YES; 214 | CLANG_WARN_INFINITE_RECURSION = YES; 215 | CLANG_WARN_INT_CONVERSION = YES; 216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 218 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 221 | CLANG_WARN_STRICT_PROTOTYPES = YES; 222 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | CODE_SIGN_IDENTITY = "iPhone Developer"; 227 | COPY_PHASE_STRIP = NO; 228 | DEBUG_INFORMATION_FORMAT = dwarf; 229 | ENABLE_STRICT_OBJC_MSGSEND = YES; 230 | ENABLE_TESTABILITY = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu11; 232 | GCC_DYNAMIC_NO_PIC = NO; 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | GCC_OPTIMIZATION_LEVEL = 0; 235 | GCC_PREPROCESSOR_DEFINITIONS = ( 236 | "DEBUG=1", 237 | "$(inherited)", 238 | ); 239 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 240 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 241 | GCC_WARN_UNDECLARED_SELECTOR = YES; 242 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 243 | GCC_WARN_UNUSED_FUNCTION = YES; 244 | GCC_WARN_UNUSED_VARIABLE = YES; 245 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 246 | MTL_ENABLE_DEBUG_INFO = YES; 247 | ONLY_ACTIVE_ARCH = YES; 248 | SDKROOT = iphoneos; 249 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 250 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 251 | }; 252 | name = Debug; 253 | }; 254 | 3530974C202EFB3E006B32D9 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 277 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 279 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 280 | CLANG_WARN_STRICT_PROTOTYPES = YES; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 283 | CLANG_WARN_UNREACHABLE_CODE = YES; 284 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 285 | CODE_SIGN_IDENTITY = "iPhone Developer"; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu11; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | SDKROOT = iphoneos; 301 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 302 | VALIDATE_PRODUCT = YES; 303 | }; 304 | name = Release; 305 | }; 306 | 3530974E202EFB3E006B32D9 /* Debug */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | CODE_SIGN_STYLE = Automatic; 311 | DEVELOPMENT_TEAM = EX3M59X54A; 312 | INFOPLIST_FILE = Example/Info.plist; 313 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 314 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 315 | PRODUCT_BUNDLE_IDENTIFIER = io.sector42.Example; 316 | PRODUCT_NAME = "$(TARGET_NAME)"; 317 | SWIFT_VERSION = 5.0; 318 | TARGETED_DEVICE_FAMILY = "1,2"; 319 | }; 320 | name = Debug; 321 | }; 322 | 3530974F202EFB3E006B32D9 /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 326 | CODE_SIGN_STYLE = Automatic; 327 | DEVELOPMENT_TEAM = EX3M59X54A; 328 | INFOPLIST_FILE = Example/Info.plist; 329 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = io.sector42.Example; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SWIFT_VERSION = 5.0; 334 | TARGETED_DEVICE_FAMILY = "1,2"; 335 | }; 336 | name = Release; 337 | }; 338 | /* End XCBuildConfiguration section */ 339 | 340 | /* Begin XCConfigurationList section */ 341 | 35309736202EFB3E006B32D9 /* Build configuration list for PBXProject "Example" */ = { 342 | isa = XCConfigurationList; 343 | buildConfigurations = ( 344 | 3530974B202EFB3E006B32D9 /* Debug */, 345 | 3530974C202EFB3E006B32D9 /* Release */, 346 | ); 347 | defaultConfigurationIsVisible = 0; 348 | defaultConfigurationName = Release; 349 | }; 350 | 3530974D202EFB3E006B32D9 /* Build configuration list for PBXNativeTarget "Example" */ = { 351 | isa = XCConfigurationList; 352 | buildConfigurations = ( 353 | 3530974E202EFB3E006B32D9 /* Debug */, 354 | 3530974F202EFB3E006B32D9 /* Release */, 355 | ); 356 | defaultConfigurationIsVisible = 0; 357 | defaultConfigurationName = Release; 358 | }; 359 | /* End XCConfigurationList section */ 360 | }; 361 | rootObject = 35309733202EFB3E006B32D9 /* Project object */; 362 | } 363 | -------------------------------------------------------------------------------- /Example/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import UIKit 19 | 20 | @UIApplicationMain 21 | class AppDelegate: UIResponder, UIApplicationDelegate { 22 | var window: UIWindow? 23 | 24 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 25 | return true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example/Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example/Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | $(DEVELOPMENT_LANGUAGE) 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleVersion 25 | 1 26 | LSRequiresIPhoneOS 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import UIKit 19 | import FavIcon 20 | 21 | class ViewController: UIViewController { 22 | @IBOutlet private weak var imageView: UIImageView! 23 | @IBOutlet private weak var statusLabel: UILabel! 24 | 25 | let url = "https://youtube.com" 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | statusLabel.text = "Loading..." 31 | do { 32 | try FavIcon.downloadPreferred(url, width: 200, height: 200) { result in 33 | if case let .success(image) = result { 34 | self.statusLabel.text = "Loaded (\(image.size.width)x\(image.size.height))" 35 | self.imageView.image = image 36 | } else if case let .failure(error) = result { 37 | self.statusLabel.text = "Failed: \(error.localizedDescription)." 38 | print("failed to download preferred favicon for \(self.url): \(error)") 39 | } 40 | } 41 | } catch let error { 42 | statusLabel.text = "Failed: \(error.localizedDescription)." 43 | print("failed to download preferred favicon for \(self.url): \(error)") 44 | } 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /FavIcon.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'FavIcon' 3 | s.version = '3.1.0' 4 | s.summary = 'A library for downloading website icons' 5 | s.homepage = 'https://github.com/leonbreedt/FavIcon' 6 | s.license = { :type => 'Apache', :file => 'LICENSE' } 7 | s.author = { 'Leon Breedt' => 'leon@sector42.io' } 8 | 9 | s.source = { 10 | :git => "https://github.com/leonbreedt/FavIcon.git", 11 | :tag => "#{s.version}" 12 | } 13 | 14 | s.source_files = 'Sources/**/*.swift' 15 | 16 | s.ios.deployment_target = "9.0" 17 | s.osx.deployment_target = "10.10" 18 | s.tvos.deployment_target = "9.0" 19 | s.watchos.deployment_target = "2.0" 20 | 21 | s.requires_arc = true 22 | 23 | s.pod_target_xcconfig = { 24 | 'SWIFT_VERSION' => '5.0', 25 | 'SWIFT_WHOLE_MODULE_OPTIMIZATION' => 'YES', 26 | } 27 | end 28 | -------------------------------------------------------------------------------- /FavIcon.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3519DB722021C3A80078D172 /* ContentTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6C2021C3A80078D172 /* ContentTypes.swift */; }; 11 | 3519DB732021C3A80078D172 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6D2021C3A80078D172 /* Icon.swift */; }; 12 | 3519DB742021C3A80078D172 /* IconType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6E2021C3A80078D172 /* IconType.swift */; }; 13 | 3519DB752021C3A80078D172 /* Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6F2021C3A80078D172 /* Download.swift */; }; 14 | 3519DB762021C3A80078D172 /* FavIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB702021C3A80078D172 /* FavIcon.swift */; }; 15 | 3519DB7E2021D0CB0078D172 /* HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB7D2021D0CB0078D172 /* HTML.swift */; }; 16 | 353096F020230594006B32D9 /* HTMLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096EF20230594006B32D9 /* HTMLTests.swift */; }; 17 | 353096F220230727006B32D9 /* HTML.html in Resources */ = {isa = PBXBuildFile; fileRef = 353096EE2023057A006B32D9 /* HTML.html */; }; 18 | 353096F520230991006B32D9 /* Detection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096F320230935006B32D9 /* Detection.swift */; }; 19 | 353096F820231018006B32D9 /* XML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096F620230F6F006B32D9 /* XML.swift */; }; 20 | 353096FB2023122F006B32D9 /* Manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 353096F92023122E006B32D9 /* Manifest.json */; }; 21 | 353096FC2023122F006B32D9 /* BrowserConfig.xml in Resources */ = {isa = PBXBuildFile; fileRef = 353096FA2023122E006B32D9 /* BrowserConfig.xml */; }; 22 | 353096FE20231263006B32D9 /* DetectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096FD20231263006B32D9 /* DetectionTests.swift */; }; 23 | 35309713202EE4EA006B32D9 /* ContentTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6C2021C3A80078D172 /* ContentTypes.swift */; }; 24 | 35309714202EE4EE006B32D9 /* Detection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096F320230935006B32D9 /* Detection.swift */; }; 25 | 35309715202EE4F1006B32D9 /* Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6F2021C3A80078D172 /* Download.swift */; }; 26 | 35309716202EE4F7006B32D9 /* FavIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB702021C3A80078D172 /* FavIcon.swift */; }; 27 | 35309717202EE4FB006B32D9 /* HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB7D2021D0CB0078D172 /* HTML.swift */; }; 28 | 35309718202EE4FF006B32D9 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6D2021C3A80078D172 /* Icon.swift */; }; 29 | 35309719202EE502006B32D9 /* IconType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3519DB6E2021C3A80078D172 /* IconType.swift */; }; 30 | 3530971A202EE505006B32D9 /* XML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096F620230F6F006B32D9 /* XML.swift */; }; 31 | 35309724202EE5B5006B32D9 /* FavIcon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3530970B202EE427006B32D9 /* FavIcon.framework */; }; 32 | 3530972A202EE6B8006B32D9 /* DetectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096FD20231263006B32D9 /* DetectionTests.swift */; }; 33 | 3530972C202EE6BE006B32D9 /* DownloadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* DownloadTests.swift */; }; 34 | 3530972D202EE6C4006B32D9 /* FavIconTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359BE1D02021A81400D20A30 /* FavIconTests.swift */; }; 35 | 3530972E202EE6C7006B32D9 /* HTMLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 353096EF20230594006B32D9 /* HTMLTests.swift */; }; 36 | 3530972F202EE6D3006B32D9 /* BrowserConfig.xml in Resources */ = {isa = PBXBuildFile; fileRef = 353096FA2023122E006B32D9 /* BrowserConfig.xml */; }; 37 | 35309730202EE6D3006B32D9 /* Manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 353096F92023122E006B32D9 /* Manifest.json */; }; 38 | 35309731202EE6D3006B32D9 /* HTML.html in Resources */ = {isa = PBXBuildFile; fileRef = 353096EE2023057A006B32D9 /* HTML.html */; }; 39 | 359BE1D22021A8F600D20A30 /* FavIconTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359BE1D02021A81400D20A30 /* FavIconTests.swift */; }; 40 | OBJ_29 /* DownloadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* DownloadTests.swift */; }; 41 | OBJ_31 /* FavIcon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "FavIcon::FavIcon::Product" /* FavIcon.framework */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXContainerItemProxy section */ 45 | 35309725202EE5B5006B32D9 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = OBJ_1 /* Project object */; 48 | proxyType = 1; 49 | remoteGlobalIDString = 3530970A202EE427006B32D9; 50 | remoteInfo = "FavIcon-iOS"; 51 | }; 52 | 359BE1CA201F1F7500D20A30 /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = OBJ_1 /* Project object */; 55 | proxyType = 1; 56 | remoteGlobalIDString = "FavIcon::FavIcon"; 57 | remoteInfo = FavIcon; 58 | }; 59 | /* End PBXContainerItemProxy section */ 60 | 61 | /* Begin PBXFileReference section */ 62 | 3519DB6C2021C3A80078D172 /* ContentTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentTypes.swift; sourceTree = ""; }; 63 | 3519DB6D2021C3A80078D172 /* Icon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = ""; }; 64 | 3519DB6E2021C3A80078D172 /* IconType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconType.swift; sourceTree = ""; }; 65 | 3519DB6F2021C3A80078D172 /* Download.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Download.swift; sourceTree = ""; }; 66 | 3519DB702021C3A80078D172 /* FavIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FavIcon.swift; sourceTree = ""; }; 67 | 3519DB712021C3A80078D172 /* FavIcon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FavIcon.h; sourceTree = ""; }; 68 | 3519DB7D2021D0CB0078D172 /* HTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTML.swift; sourceTree = ""; }; 69 | 353096EE2023057A006B32D9 /* HTML.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = HTML.html; sourceTree = ""; }; 70 | 353096EF20230594006B32D9 /* HTMLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLTests.swift; sourceTree = ""; }; 71 | 353096F320230935006B32D9 /* Detection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Detection.swift; sourceTree = ""; }; 72 | 353096F620230F6F006B32D9 /* XML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XML.swift; sourceTree = ""; }; 73 | 353096F92023122E006B32D9 /* Manifest.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Manifest.json; sourceTree = ""; }; 74 | 353096FA2023122E006B32D9 /* BrowserConfig.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = BrowserConfig.xml; sourceTree = ""; }; 75 | 353096FD20231263006B32D9 /* DetectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionTests.swift; sourceTree = ""; }; 76 | 3530970B202EE427006B32D9 /* FavIcon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FavIcon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | 3530971F202EE5B5006B32D9 /* FavIconTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FavIconTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 78 | 359BE1D02021A81400D20A30 /* FavIconTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavIconTests.swift; sourceTree = ""; }; 79 | 35A00D4C22AFC4FD0093E161 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 80 | "FavIcon::FavIcon::Product" /* FavIcon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FavIcon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 81 | "FavIcon::FavIconTests::Product" /* FavIconTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = FavIconTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 82 | OBJ_13 /* DownloadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTests.swift; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | 35309707202EE427006B32D9 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | 3530971C202EE5B5006B32D9 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | 35309724202EE5B5006B32D9 /* FavIcon.framework in Frameworks */, 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | OBJ_30 /* Frameworks */ = { 102 | isa = PBXFrameworksBuildPhase; 103 | buildActionMask = 0; 104 | files = ( 105 | OBJ_31 /* FavIcon.framework in Frameworks */, 106 | ); 107 | runOnlyForDeploymentPostprocessing = 0; 108 | }; 109 | OBJ_41 /* Frameworks */ = { 110 | isa = PBXFrameworksBuildPhase; 111 | buildActionMask = 0; 112 | files = ( 113 | ); 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | /* End PBXFrameworksBuildPhase section */ 117 | 118 | /* Begin PBXGroup section */ 119 | 3519DB6B2021C3A80078D172 /* FavIcon */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 3519DB6C2021C3A80078D172 /* ContentTypes.swift */, 123 | 353096F320230935006B32D9 /* Detection.swift */, 124 | 3519DB6F2021C3A80078D172 /* Download.swift */, 125 | 3519DB712021C3A80078D172 /* FavIcon.h */, 126 | 3519DB702021C3A80078D172 /* FavIcon.swift */, 127 | 3519DB7D2021D0CB0078D172 /* HTML.swift */, 128 | 3519DB6D2021C3A80078D172 /* Icon.swift */, 129 | 3519DB6E2021C3A80078D172 /* IconType.swift */, 130 | 353096F620230F6F006B32D9 /* XML.swift */, 131 | ); 132 | path = FavIcon; 133 | sourceTree = ""; 134 | }; 135 | OBJ_11 /* Tests */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | OBJ_12 /* FavIconTests */, 139 | ); 140 | name = Tests; 141 | sourceTree = SOURCE_ROOT; 142 | }; 143 | OBJ_12 /* FavIconTests */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 353096FA2023122E006B32D9 /* BrowserConfig.xml */, 147 | 353096F92023122E006B32D9 /* Manifest.json */, 148 | 353096FD20231263006B32D9 /* DetectionTests.swift */, 149 | OBJ_13 /* DownloadTests.swift */, 150 | 359BE1D02021A81400D20A30 /* FavIconTests.swift */, 151 | 353096EE2023057A006B32D9 /* HTML.html */, 152 | 353096EF20230594006B32D9 /* HTMLTests.swift */, 153 | ); 154 | name = FavIconTests; 155 | path = Tests/FavIconTests; 156 | sourceTree = SOURCE_ROOT; 157 | }; 158 | OBJ_15 /* Products */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | "FavIcon::FavIconTests::Product" /* FavIconTests.xctest */, 162 | "FavIcon::FavIcon::Product" /* FavIcon.framework */, 163 | 3530970B202EE427006B32D9 /* FavIcon.framework */, 164 | 3530971F202EE5B5006B32D9 /* FavIconTests.xctest */, 165 | ); 166 | name = Products; 167 | sourceTree = BUILT_PRODUCTS_DIR; 168 | }; 169 | OBJ_5 = { 170 | isa = PBXGroup; 171 | children = ( 172 | 35A00D4C22AFC4FD0093E161 /* Package.swift */, 173 | OBJ_7 /* Sources */, 174 | OBJ_11 /* Tests */, 175 | OBJ_15 /* Products */, 176 | ); 177 | sourceTree = ""; 178 | }; 179 | OBJ_7 /* Sources */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 3519DB6B2021C3A80078D172 /* FavIcon */, 183 | ); 184 | path = Sources; 185 | sourceTree = SOURCE_ROOT; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXHeadersBuildPhase section */ 190 | 35309708202EE427006B32D9 /* Headers */ = { 191 | isa = PBXHeadersBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXHeadersBuildPhase section */ 198 | 199 | /* Begin PBXNativeTarget section */ 200 | 3530970A202EE427006B32D9 /* FavIcon-iOS */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = 35309712202EE427006B32D9 /* Build configuration list for PBXNativeTarget "FavIcon-iOS" */; 203 | buildPhases = ( 204 | 35309706202EE427006B32D9 /* Sources */, 205 | 35309707202EE427006B32D9 /* Frameworks */, 206 | 35309708202EE427006B32D9 /* Headers */, 207 | 35309709202EE427006B32D9 /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | ); 213 | name = "FavIcon-iOS"; 214 | productName = "FavIcon-iOS"; 215 | productReference = 3530970B202EE427006B32D9 /* FavIcon.framework */; 216 | productType = "com.apple.product-type.framework"; 217 | }; 218 | 3530971E202EE5B5006B32D9 /* FavIcon-iOSTests */ = { 219 | isa = PBXNativeTarget; 220 | buildConfigurationList = 35309727202EE5B5006B32D9 /* Build configuration list for PBXNativeTarget "FavIcon-iOSTests" */; 221 | buildPhases = ( 222 | 3530971B202EE5B5006B32D9 /* Sources */, 223 | 3530971C202EE5B5006B32D9 /* Frameworks */, 224 | 3530971D202EE5B5006B32D9 /* Resources */, 225 | ); 226 | buildRules = ( 227 | ); 228 | dependencies = ( 229 | 35309726202EE5B5006B32D9 /* PBXTargetDependency */, 230 | ); 231 | name = "FavIcon-iOSTests"; 232 | productName = "FavIcon-iOSTests"; 233 | productReference = 3530971F202EE5B5006B32D9 /* FavIconTests.xctest */; 234 | productType = "com.apple.product-type.bundle.unit-test"; 235 | }; 236 | "FavIcon::FavIcon" /* FavIcon-macOS */ = { 237 | isa = PBXNativeTarget; 238 | buildConfigurationList = OBJ_34 /* Build configuration list for PBXNativeTarget "FavIcon-macOS" */; 239 | buildPhases = ( 240 | OBJ_37 /* Sources */, 241 | OBJ_41 /* Frameworks */, 242 | ); 243 | buildRules = ( 244 | ); 245 | dependencies = ( 246 | ); 247 | name = "FavIcon-macOS"; 248 | productName = FavIcon; 249 | productReference = "FavIcon::FavIcon::Product" /* FavIcon.framework */; 250 | productType = "com.apple.product-type.framework"; 251 | }; 252 | "FavIcon::FavIconTests" /* FavIcon-macOSTests */ = { 253 | isa = PBXNativeTarget; 254 | buildConfigurationList = OBJ_25 /* Build configuration list for PBXNativeTarget "FavIcon-macOSTests" */; 255 | buildPhases = ( 256 | OBJ_28 /* Sources */, 257 | OBJ_30 /* Frameworks */, 258 | 353096F12023071F006B32D9 /* Resources */, 259 | ); 260 | buildRules = ( 261 | ); 262 | dependencies = ( 263 | OBJ_32 /* PBXTargetDependency */, 264 | ); 265 | name = "FavIcon-macOSTests"; 266 | productName = FavIconTests; 267 | productReference = "FavIcon::FavIconTests::Product" /* FavIconTests.xctest */; 268 | productType = "com.apple.product-type.bundle.unit-test"; 269 | }; 270 | /* End PBXNativeTarget section */ 271 | 272 | /* Begin PBXProject section */ 273 | OBJ_1 /* Project object */ = { 274 | isa = PBXProject; 275 | attributes = { 276 | LastSwiftUpdateCheck = 0920; 277 | LastUpgradeCheck = 1100; 278 | TargetAttributes = { 279 | 3530970A202EE427006B32D9 = { 280 | CreatedOnToolsVersion = 9.2; 281 | DevelopmentTeam = EX3M59X54A; 282 | LastSwiftMigration = 1100; 283 | ProvisioningStyle = Automatic; 284 | }; 285 | 3530971E202EE5B5006B32D9 = { 286 | CreatedOnToolsVersion = 9.2; 287 | DevelopmentTeam = EX3M59X54A; 288 | LastSwiftMigration = 1100; 289 | ProvisioningStyle = Automatic; 290 | }; 291 | "FavIcon::FavIcon" = { 292 | LastSwiftMigration = 1100; 293 | }; 294 | "FavIcon::FavIconTests" = { 295 | LastSwiftMigration = 1100; 296 | }; 297 | }; 298 | }; 299 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "FavIcon" */; 300 | compatibilityVersion = "Xcode 3.2"; 301 | developmentRegion = en; 302 | hasScannedForEncodings = 0; 303 | knownRegions = ( 304 | en, 305 | Base, 306 | ); 307 | mainGroup = OBJ_5; 308 | productRefGroup = OBJ_15 /* Products */; 309 | projectDirPath = ""; 310 | projectRoot = ""; 311 | targets = ( 312 | "FavIcon::FavIcon" /* FavIcon-macOS */, 313 | "FavIcon::FavIconTests" /* FavIcon-macOSTests */, 314 | 3530970A202EE427006B32D9 /* FavIcon-iOS */, 315 | 3530971E202EE5B5006B32D9 /* FavIcon-iOSTests */, 316 | ); 317 | }; 318 | /* End PBXProject section */ 319 | 320 | /* Begin PBXResourcesBuildPhase section */ 321 | 353096F12023071F006B32D9 /* Resources */ = { 322 | isa = PBXResourcesBuildPhase; 323 | buildActionMask = 2147483647; 324 | files = ( 325 | 353096FB2023122F006B32D9 /* Manifest.json in Resources */, 326 | 353096FC2023122F006B32D9 /* BrowserConfig.xml in Resources */, 327 | 353096F220230727006B32D9 /* HTML.html in Resources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | 35309709202EE427006B32D9 /* Resources */ = { 332 | isa = PBXResourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | runOnlyForDeploymentPostprocessing = 0; 337 | }; 338 | 3530971D202EE5B5006B32D9 /* Resources */ = { 339 | isa = PBXResourcesBuildPhase; 340 | buildActionMask = 2147483647; 341 | files = ( 342 | 35309731202EE6D3006B32D9 /* HTML.html in Resources */, 343 | 35309730202EE6D3006B32D9 /* Manifest.json in Resources */, 344 | 3530972F202EE6D3006B32D9 /* BrowserConfig.xml in Resources */, 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | /* End PBXResourcesBuildPhase section */ 349 | 350 | /* Begin PBXSourcesBuildPhase section */ 351 | 35309706202EE427006B32D9 /* Sources */ = { 352 | isa = PBXSourcesBuildPhase; 353 | buildActionMask = 2147483647; 354 | files = ( 355 | 35309718202EE4FF006B32D9 /* Icon.swift in Sources */, 356 | 35309717202EE4FB006B32D9 /* HTML.swift in Sources */, 357 | 35309715202EE4F1006B32D9 /* Download.swift in Sources */, 358 | 35309713202EE4EA006B32D9 /* ContentTypes.swift in Sources */, 359 | 35309719202EE502006B32D9 /* IconType.swift in Sources */, 360 | 3530971A202EE505006B32D9 /* XML.swift in Sources */, 361 | 35309716202EE4F7006B32D9 /* FavIcon.swift in Sources */, 362 | 35309714202EE4EE006B32D9 /* Detection.swift in Sources */, 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | }; 366 | 3530971B202EE5B5006B32D9 /* Sources */ = { 367 | isa = PBXSourcesBuildPhase; 368 | buildActionMask = 2147483647; 369 | files = ( 370 | 3530972A202EE6B8006B32D9 /* DetectionTests.swift in Sources */, 371 | 3530972D202EE6C4006B32D9 /* FavIconTests.swift in Sources */, 372 | 3530972E202EE6C7006B32D9 /* HTMLTests.swift in Sources */, 373 | 3530972C202EE6BE006B32D9 /* DownloadTests.swift in Sources */, 374 | ); 375 | runOnlyForDeploymentPostprocessing = 0; 376 | }; 377 | OBJ_28 /* Sources */ = { 378 | isa = PBXSourcesBuildPhase; 379 | buildActionMask = 0; 380 | files = ( 381 | 359BE1D22021A8F600D20A30 /* FavIconTests.swift in Sources */, 382 | 353096FE20231263006B32D9 /* DetectionTests.swift in Sources */, 383 | 353096F020230594006B32D9 /* HTMLTests.swift in Sources */, 384 | OBJ_29 /* DownloadTests.swift in Sources */, 385 | ); 386 | runOnlyForDeploymentPostprocessing = 0; 387 | }; 388 | OBJ_37 /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 0; 391 | files = ( 392 | 3519DB7E2021D0CB0078D172 /* HTML.swift in Sources */, 393 | 353096F520230991006B32D9 /* Detection.swift in Sources */, 394 | 3519DB752021C3A80078D172 /* Download.swift in Sources */, 395 | 3519DB732021C3A80078D172 /* Icon.swift in Sources */, 396 | 3519DB742021C3A80078D172 /* IconType.swift in Sources */, 397 | 3519DB762021C3A80078D172 /* FavIcon.swift in Sources */, 398 | 353096F820231018006B32D9 /* XML.swift in Sources */, 399 | 3519DB722021C3A80078D172 /* ContentTypes.swift in Sources */, 400 | ); 401 | runOnlyForDeploymentPostprocessing = 0; 402 | }; 403 | /* End PBXSourcesBuildPhase section */ 404 | 405 | /* Begin PBXTargetDependency section */ 406 | 35309726202EE5B5006B32D9 /* PBXTargetDependency */ = { 407 | isa = PBXTargetDependency; 408 | target = 3530970A202EE427006B32D9 /* FavIcon-iOS */; 409 | targetProxy = 35309725202EE5B5006B32D9 /* PBXContainerItemProxy */; 410 | }; 411 | OBJ_32 /* PBXTargetDependency */ = { 412 | isa = PBXTargetDependency; 413 | target = "FavIcon::FavIcon" /* FavIcon-macOS */; 414 | targetProxy = 359BE1CA201F1F7500D20A30 /* PBXContainerItemProxy */; 415 | }; 416 | /* End PBXTargetDependency section */ 417 | 418 | /* Begin XCBuildConfiguration section */ 419 | 35309710202EE427006B32D9 /* Debug */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | ALWAYS_SEARCH_USER_PATHS = NO; 423 | CLANG_ANALYZER_NONNULL = YES; 424 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 425 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 426 | CLANG_CXX_LIBRARY = "libc++"; 427 | CLANG_ENABLE_MODULES = YES; 428 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 429 | CLANG_WARN_BOOL_CONVERSION = YES; 430 | CLANG_WARN_COMMA = YES; 431 | CLANG_WARN_CONSTANT_CONVERSION = YES; 432 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 433 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 434 | CLANG_WARN_EMPTY_BODY = YES; 435 | CLANG_WARN_ENUM_CONVERSION = YES; 436 | CLANG_WARN_INFINITE_RECURSION = YES; 437 | CLANG_WARN_INT_CONVERSION = YES; 438 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 439 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 440 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 441 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 442 | CLANG_WARN_STRICT_PROTOTYPES = YES; 443 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 444 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 445 | CLANG_WARN_UNREACHABLE_CODE = YES; 446 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 447 | CODE_SIGN_IDENTITY = ""; 448 | CODE_SIGN_STYLE = Automatic; 449 | CURRENT_PROJECT_VERSION = 1; 450 | DEFINES_MODULE = YES; 451 | DEVELOPMENT_TEAM = EX3M59X54A; 452 | DYLIB_COMPATIBILITY_VERSION = 1; 453 | DYLIB_CURRENT_VERSION = 1; 454 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 455 | ENABLE_STRICT_OBJC_MSGSEND = YES; 456 | ENABLE_TESTABILITY = YES; 457 | GCC_C_LANGUAGE_STANDARD = gnu11; 458 | GCC_DYNAMIC_NO_PIC = NO; 459 | GCC_NO_COMMON_BLOCKS = YES; 460 | GCC_PREPROCESSOR_DEFINITIONS = ( 461 | "DEBUG=1", 462 | "$(inherited)", 463 | ); 464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 466 | GCC_WARN_UNDECLARED_SELECTOR = YES; 467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 468 | GCC_WARN_UNUSED_FUNCTION = YES; 469 | GCC_WARN_UNUSED_VARIABLE = YES; 470 | INFOPLIST_FILE = Sources/Info.plist; 471 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 472 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 474 | MTL_ENABLE_DEBUG_INFO = YES; 475 | PRODUCT_BUNDLE_IDENTIFIER = io.sector42.FavIcon; 476 | PRODUCT_NAME = FavIcon; 477 | SDKROOT = iphoneos; 478 | SKIP_INSTALL = YES; 479 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 480 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 481 | SWIFT_VERSION = 5.0; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | VERSIONING_SYSTEM = "apple-generic"; 484 | VERSION_INFO_PREFIX = ""; 485 | }; 486 | name = Debug; 487 | }; 488 | 35309711202EE427006B32D9 /* Release */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ALWAYS_SEARCH_USER_PATHS = NO; 492 | CLANG_ANALYZER_NONNULL = YES; 493 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 494 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 495 | CLANG_CXX_LIBRARY = "libc++"; 496 | CLANG_ENABLE_MODULES = YES; 497 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 498 | CLANG_WARN_BOOL_CONVERSION = YES; 499 | CLANG_WARN_COMMA = YES; 500 | CLANG_WARN_CONSTANT_CONVERSION = YES; 501 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 502 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 503 | CLANG_WARN_EMPTY_BODY = YES; 504 | CLANG_WARN_ENUM_CONVERSION = YES; 505 | CLANG_WARN_INFINITE_RECURSION = YES; 506 | CLANG_WARN_INT_CONVERSION = YES; 507 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 508 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 509 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 510 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 511 | CLANG_WARN_STRICT_PROTOTYPES = YES; 512 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 513 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 514 | CLANG_WARN_UNREACHABLE_CODE = YES; 515 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 516 | CODE_SIGN_IDENTITY = ""; 517 | CODE_SIGN_STYLE = Automatic; 518 | COPY_PHASE_STRIP = NO; 519 | CURRENT_PROJECT_VERSION = 1; 520 | DEFINES_MODULE = YES; 521 | DEVELOPMENT_TEAM = EX3M59X54A; 522 | DYLIB_COMPATIBILITY_VERSION = 1; 523 | DYLIB_CURRENT_VERSION = 1; 524 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 525 | ENABLE_NS_ASSERTIONS = NO; 526 | ENABLE_STRICT_OBJC_MSGSEND = YES; 527 | ENABLE_TESTABILITY = YES; 528 | GCC_C_LANGUAGE_STANDARD = gnu11; 529 | GCC_NO_COMMON_BLOCKS = YES; 530 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 531 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 532 | GCC_WARN_UNDECLARED_SELECTOR = YES; 533 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 534 | GCC_WARN_UNUSED_FUNCTION = YES; 535 | GCC_WARN_UNUSED_VARIABLE = YES; 536 | INFOPLIST_FILE = Sources/Info.plist; 537 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 538 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 539 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 540 | MTL_ENABLE_DEBUG_INFO = NO; 541 | PRODUCT_BUNDLE_IDENTIFIER = io.sector42.FavIcon; 542 | PRODUCT_NAME = FavIcon; 543 | SDKROOT = iphoneos; 544 | SKIP_INSTALL = YES; 545 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 546 | SWIFT_VERSION = 5.0; 547 | TARGETED_DEVICE_FAMILY = "1,2"; 548 | VALIDATE_PRODUCT = YES; 549 | VERSIONING_SYSTEM = "apple-generic"; 550 | VERSION_INFO_PREFIX = ""; 551 | }; 552 | name = Release; 553 | }; 554 | 35309728202EE5B5006B32D9 /* Debug */ = { 555 | isa = XCBuildConfiguration; 556 | buildSettings = { 557 | ALWAYS_SEARCH_USER_PATHS = NO; 558 | CLANG_ANALYZER_NONNULL = YES; 559 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 560 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 561 | CLANG_CXX_LIBRARY = "libc++"; 562 | CLANG_ENABLE_MODULES = YES; 563 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 564 | CLANG_WARN_BOOL_CONVERSION = YES; 565 | CLANG_WARN_COMMA = YES; 566 | CLANG_WARN_CONSTANT_CONVERSION = YES; 567 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 568 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 569 | CLANG_WARN_EMPTY_BODY = YES; 570 | CLANG_WARN_ENUM_CONVERSION = YES; 571 | CLANG_WARN_INFINITE_RECURSION = YES; 572 | CLANG_WARN_INT_CONVERSION = YES; 573 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 574 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 575 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 576 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 577 | CLANG_WARN_STRICT_PROTOTYPES = YES; 578 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 579 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 580 | CLANG_WARN_UNREACHABLE_CODE = YES; 581 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 582 | CODE_SIGN_IDENTITY = "iPhone Developer"; 583 | CODE_SIGN_STYLE = Automatic; 584 | DEVELOPMENT_TEAM = EX3M59X54A; 585 | ENABLE_STRICT_OBJC_MSGSEND = YES; 586 | ENABLE_TESTABILITY = YES; 587 | GCC_C_LANGUAGE_STANDARD = gnu11; 588 | GCC_DYNAMIC_NO_PIC = NO; 589 | GCC_NO_COMMON_BLOCKS = YES; 590 | GCC_PREPROCESSOR_DEFINITIONS = ( 591 | "DEBUG=1", 592 | "$(inherited)", 593 | ); 594 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 595 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 596 | GCC_WARN_UNDECLARED_SELECTOR = YES; 597 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 598 | GCC_WARN_UNUSED_FUNCTION = YES; 599 | GCC_WARN_UNUSED_VARIABLE = YES; 600 | INFOPLIST_FILE = Tests/Info.plist; 601 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 602 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 603 | MTL_ENABLE_DEBUG_INFO = YES; 604 | PRODUCT_BUNDLE_IDENTIFIER = ""; 605 | PRODUCT_NAME = FavIconTests; 606 | SDKROOT = iphoneos; 607 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 608 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 609 | SWIFT_VERSION = 5.0; 610 | TARGETED_DEVICE_FAMILY = "1,2"; 611 | TARGET_NAME = FavIconTests; 612 | }; 613 | name = Debug; 614 | }; 615 | 35309729202EE5B5006B32D9 /* Release */ = { 616 | isa = XCBuildConfiguration; 617 | buildSettings = { 618 | ALWAYS_SEARCH_USER_PATHS = NO; 619 | CLANG_ANALYZER_NONNULL = YES; 620 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 621 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 622 | CLANG_CXX_LIBRARY = "libc++"; 623 | CLANG_ENABLE_MODULES = YES; 624 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 625 | CLANG_WARN_BOOL_CONVERSION = YES; 626 | CLANG_WARN_COMMA = YES; 627 | CLANG_WARN_CONSTANT_CONVERSION = YES; 628 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 629 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 630 | CLANG_WARN_EMPTY_BODY = YES; 631 | CLANG_WARN_ENUM_CONVERSION = YES; 632 | CLANG_WARN_INFINITE_RECURSION = YES; 633 | CLANG_WARN_INT_CONVERSION = YES; 634 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 635 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 636 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 637 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 638 | CLANG_WARN_STRICT_PROTOTYPES = YES; 639 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 640 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 641 | CLANG_WARN_UNREACHABLE_CODE = YES; 642 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 643 | CODE_SIGN_IDENTITY = "iPhone Developer"; 644 | CODE_SIGN_STYLE = Automatic; 645 | COPY_PHASE_STRIP = NO; 646 | DEVELOPMENT_TEAM = EX3M59X54A; 647 | ENABLE_NS_ASSERTIONS = NO; 648 | ENABLE_STRICT_OBJC_MSGSEND = YES; 649 | GCC_C_LANGUAGE_STANDARD = gnu11; 650 | GCC_NO_COMMON_BLOCKS = YES; 651 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 652 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 653 | GCC_WARN_UNDECLARED_SELECTOR = YES; 654 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 655 | GCC_WARN_UNUSED_FUNCTION = YES; 656 | GCC_WARN_UNUSED_VARIABLE = YES; 657 | INFOPLIST_FILE = Tests/Info.plist; 658 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 659 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 660 | MTL_ENABLE_DEBUG_INFO = NO; 661 | PRODUCT_BUNDLE_IDENTIFIER = ""; 662 | PRODUCT_NAME = FavIconTests; 663 | SDKROOT = iphoneos; 664 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 665 | SWIFT_VERSION = 5.0; 666 | TARGETED_DEVICE_FAMILY = "1,2"; 667 | TARGET_NAME = FavIconTests; 668 | VALIDATE_PRODUCT = YES; 669 | }; 670 | name = Release; 671 | }; 672 | OBJ_26 /* Debug */ = { 673 | isa = XCBuildConfiguration; 674 | buildSettings = { 675 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 676 | FRAMEWORK_SEARCH_PATHS = ( 677 | "$(inherited)", 678 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 679 | ); 680 | HEADER_SEARCH_PATHS = "$(inherited)"; 681 | INFOPLIST_FILE = Tests/Info.plist; 682 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; 683 | OTHER_LDFLAGS = "$(inherited)"; 684 | OTHER_SWIFT_FLAGS = "$(inherited)"; 685 | SUPPORTED_PLATFORMS = macosx; 686 | SWIFT_VERSION = 5.0; 687 | TARGET_NAME = FavIconTests; 688 | }; 689 | name = Debug; 690 | }; 691 | OBJ_27 /* Release */ = { 692 | isa = XCBuildConfiguration; 693 | buildSettings = { 694 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 695 | FRAMEWORK_SEARCH_PATHS = ( 696 | "$(inherited)", 697 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 698 | ); 699 | HEADER_SEARCH_PATHS = "$(inherited)"; 700 | INFOPLIST_FILE = Tests/Info.plist; 701 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks @loader_path/Frameworks"; 702 | OTHER_LDFLAGS = "$(inherited)"; 703 | OTHER_SWIFT_FLAGS = "$(inherited)"; 704 | SUPPORTED_PLATFORMS = macosx; 705 | SWIFT_VERSION = 5.0; 706 | TARGET_NAME = FavIconTests; 707 | }; 708 | name = Release; 709 | }; 710 | OBJ_3 /* Debug */ = { 711 | isa = XCBuildConfiguration; 712 | buildSettings = { 713 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 714 | CLANG_ENABLE_OBJC_ARC = YES; 715 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 716 | CLANG_WARN_BOOL_CONVERSION = YES; 717 | CLANG_WARN_COMMA = YES; 718 | CLANG_WARN_CONSTANT_CONVERSION = YES; 719 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 720 | CLANG_WARN_EMPTY_BODY = YES; 721 | CLANG_WARN_ENUM_CONVERSION = YES; 722 | CLANG_WARN_INFINITE_RECURSION = YES; 723 | CLANG_WARN_INT_CONVERSION = YES; 724 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 725 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 726 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 727 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 728 | CLANG_WARN_STRICT_PROTOTYPES = YES; 729 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 730 | CLANG_WARN_UNREACHABLE_CODE = YES; 731 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 732 | COMBINE_HIDPI_IMAGES = YES; 733 | COPY_PHASE_STRIP = NO; 734 | DEBUG_INFORMATION_FORMAT = dwarf; 735 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 736 | ENABLE_NS_ASSERTIONS = YES; 737 | ENABLE_STRICT_OBJC_MSGSEND = YES; 738 | ENABLE_TESTABILITY = YES; 739 | GCC_NO_COMMON_BLOCKS = YES; 740 | GCC_OPTIMIZATION_LEVEL = 0; 741 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 742 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 743 | GCC_WARN_UNDECLARED_SELECTOR = YES; 744 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 745 | GCC_WARN_UNUSED_FUNCTION = YES; 746 | GCC_WARN_UNUSED_VARIABLE = YES; 747 | HEADER_SEARCH_PATHS = "$(LIBXML2_INCLUDE_DIR)"; 748 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 749 | MACOSX_DEPLOYMENT_TARGET = 10.10; 750 | ONLY_ACTIVE_ARCH = YES; 751 | OTHER_SWIFT_FLAGS = "-DXcode"; 752 | PRODUCT_NAME = "$(TARGET_NAME)"; 753 | SDKROOT = macosx; 754 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 755 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; 756 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 757 | USE_HEADERMAP = NO; 758 | }; 759 | name = Debug; 760 | }; 761 | OBJ_35 /* Debug */ = { 762 | isa = XCBuildConfiguration; 763 | buildSettings = { 764 | ENABLE_TESTABILITY = YES; 765 | FRAMEWORK_SEARCH_PATHS = ( 766 | "$(inherited)", 767 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 768 | ); 769 | HEADER_SEARCH_PATHS = "$(inherited)"; 770 | INFOPLIST_FILE = Sources/Info.plist; 771 | LD_RUNPATH_SEARCH_PATHS = ""; 772 | MACOSX_DEPLOYMENT_TARGET = 10.10; 773 | OTHER_LDFLAGS = "$(inherited)"; 774 | OTHER_SWIFT_FLAGS = "$(inherited)"; 775 | PRODUCT_BUNDLE_IDENTIFIER = FavIcon; 776 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 777 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 778 | SKIP_INSTALL = YES; 779 | SUPPORTED_PLATFORMS = macosx; 780 | SWIFT_VERSION = 5.0; 781 | TARGET_NAME = FavIcon; 782 | VALID_ARCHS = "arm64 x86_64"; 783 | }; 784 | name = Debug; 785 | }; 786 | OBJ_36 /* Release */ = { 787 | isa = XCBuildConfiguration; 788 | buildSettings = { 789 | ENABLE_TESTABILITY = YES; 790 | FRAMEWORK_SEARCH_PATHS = ( 791 | "$(inherited)", 792 | "$(PLATFORM_DIR)/Developer/Library/Frameworks", 793 | ); 794 | HEADER_SEARCH_PATHS = "$(inherited)"; 795 | INFOPLIST_FILE = Sources/Info.plist; 796 | LD_RUNPATH_SEARCH_PATHS = ""; 797 | MACOSX_DEPLOYMENT_TARGET = 10.10; 798 | OTHER_LDFLAGS = "$(inherited)"; 799 | OTHER_SWIFT_FLAGS = "$(inherited)"; 800 | PRODUCT_BUNDLE_IDENTIFIER = FavIcon; 801 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; 802 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 803 | SKIP_INSTALL = YES; 804 | SUPPORTED_PLATFORMS = macosx; 805 | SWIFT_VERSION = 5.0; 806 | TARGET_NAME = FavIcon; 807 | VALID_ARCHS = "arm64 x86_64"; 808 | }; 809 | name = Release; 810 | }; 811 | OBJ_4 /* Release */ = { 812 | isa = XCBuildConfiguration; 813 | buildSettings = { 814 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 815 | CLANG_ENABLE_OBJC_ARC = YES; 816 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 817 | CLANG_WARN_BOOL_CONVERSION = YES; 818 | CLANG_WARN_COMMA = YES; 819 | CLANG_WARN_CONSTANT_CONVERSION = YES; 820 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 821 | CLANG_WARN_EMPTY_BODY = YES; 822 | CLANG_WARN_ENUM_CONVERSION = YES; 823 | CLANG_WARN_INFINITE_RECURSION = YES; 824 | CLANG_WARN_INT_CONVERSION = YES; 825 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 826 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 827 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 828 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 829 | CLANG_WARN_STRICT_PROTOTYPES = YES; 830 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 831 | CLANG_WARN_UNREACHABLE_CODE = YES; 832 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 833 | COMBINE_HIDPI_IMAGES = YES; 834 | COPY_PHASE_STRIP = YES; 835 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 836 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 837 | ENABLE_STRICT_OBJC_MSGSEND = YES; 838 | GCC_NO_COMMON_BLOCKS = YES; 839 | GCC_OPTIMIZATION_LEVEL = s; 840 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 841 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 842 | GCC_WARN_UNDECLARED_SELECTOR = YES; 843 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 844 | GCC_WARN_UNUSED_FUNCTION = YES; 845 | GCC_WARN_UNUSED_VARIABLE = YES; 846 | HEADER_SEARCH_PATHS = "$(LIBXML2_INCLUDE_DIR)"; 847 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 848 | MACOSX_DEPLOYMENT_TARGET = 10.10; 849 | OTHER_SWIFT_FLAGS = "-DXcode"; 850 | PRODUCT_NAME = "$(TARGET_NAME)"; 851 | SDKROOT = macosx; 852 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; 853 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE; 854 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 855 | USE_HEADERMAP = NO; 856 | }; 857 | name = Release; 858 | }; 859 | /* End XCBuildConfiguration section */ 860 | 861 | /* Begin XCConfigurationList section */ 862 | 35309712202EE427006B32D9 /* Build configuration list for PBXNativeTarget "FavIcon-iOS" */ = { 863 | isa = XCConfigurationList; 864 | buildConfigurations = ( 865 | 35309710202EE427006B32D9 /* Debug */, 866 | 35309711202EE427006B32D9 /* Release */, 867 | ); 868 | defaultConfigurationIsVisible = 0; 869 | defaultConfigurationName = Debug; 870 | }; 871 | 35309727202EE5B5006B32D9 /* Build configuration list for PBXNativeTarget "FavIcon-iOSTests" */ = { 872 | isa = XCConfigurationList; 873 | buildConfigurations = ( 874 | 35309728202EE5B5006B32D9 /* Debug */, 875 | 35309729202EE5B5006B32D9 /* Release */, 876 | ); 877 | defaultConfigurationIsVisible = 0; 878 | defaultConfigurationName = Debug; 879 | }; 880 | OBJ_2 /* Build configuration list for PBXProject "FavIcon" */ = { 881 | isa = XCConfigurationList; 882 | buildConfigurations = ( 883 | OBJ_3 /* Debug */, 884 | OBJ_4 /* Release */, 885 | ); 886 | defaultConfigurationIsVisible = 0; 887 | defaultConfigurationName = Debug; 888 | }; 889 | OBJ_25 /* Build configuration list for PBXNativeTarget "FavIcon-macOSTests" */ = { 890 | isa = XCConfigurationList; 891 | buildConfigurations = ( 892 | OBJ_26 /* Debug */, 893 | OBJ_27 /* Release */, 894 | ); 895 | defaultConfigurationIsVisible = 0; 896 | defaultConfigurationName = Debug; 897 | }; 898 | OBJ_34 /* Build configuration list for PBXNativeTarget "FavIcon-macOS" */ = { 899 | isa = XCConfigurationList; 900 | buildConfigurations = ( 901 | OBJ_35 /* Debug */, 902 | OBJ_36 /* Release */, 903 | ); 904 | defaultConfigurationIsVisible = 0; 905 | defaultConfigurationName = Debug; 906 | }; 907 | /* End XCConfigurationList section */ 908 | }; 909 | rootObject = OBJ_1 /* Project object */; 910 | } 911 | -------------------------------------------------------------------------------- /FavIcon.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FavIcon.xcodeproj/xcshareddata/xcschemes/FavIcon-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /FavIcon.xcodeproj/xcshareddata/xcschemes/FavIcon-iOSTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /FavIcon.xcodeproj/xcshareddata/xcschemes/FavIcon-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /FavIcon.xcodeproj/xcshareddata/xcschemes/FavIcon-macOSTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 21 | 22 | 23 | 24 | 26 | 32 | 33 | 34 | 35 | 36 | 46 | 47 | 53 | 54 | 56 | 57 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /FavIcon.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SchemeUserState 5 | 6 | FavIcon-Package.xcscheme 7 | 8 | 9 | SuppressBuildableAutocreation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /FavIcon.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FavIcon.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Leon Breedt 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UNAME = ${shell uname} 2 | SCHEME_MAC = FavIcon-macOS 3 | SCHEME_IOS = FavIcon-iOS 4 | CONFIGURATION = Debug 5 | XCODEBUILD = xcodebuild -configuration ${CONFIGURATION} 6 | SDK_MAC = -scheme ${SCHEME_MAC} -sdk macosx 7 | SDK_IPHONE = -scheme ${SCHEME_IOS} -sdk iphonesimulator -destination "name=iPhone 8" 8 | SDK_PATH_MACOSX = ${shell xcrun --sdk macosx --show-sdk-path} 9 | 10 | .PHONY: all test build release clean 11 | 12 | all: build 13 | build: 14 | @${XCODEBUILD} ${SDK_MAC} 15 | @${XCODEBUILD} ${SDK_IPHONE} 16 | 17 | build-spm: 18 | swift build 19 | 20 | test: build 21 | @${XCODEBUILD} ${SDK_MAC} test 22 | @${XCODEBUILD} ${SDK_IPHONE} test 23 | 24 | release: 25 | @test -z "${VERSION}" && echo "error: VERSION variable not set" && exit 1 26 | git tag ${VERSION} 27 | git push --tags 28 | pod trunk push FavIcon.podspec 29 | 30 | clean: 31 | @${XCODEBUILD} ${SDK_MAC} clean 32 | @${XCODEBUILD} ${SDK_IPHONE} clean 33 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | // 4 | // FavIcon 5 | // Copyright © 2020 Leon Breedt 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | 21 | import PackageDescription 22 | 23 | let package = Package( 24 | name: "FavIcon", 25 | platforms: [ 26 | .macOS("10.10"), 27 | .iOS("9.0") 28 | ], 29 | products: [ 30 | .library(name: "FavIcon", targets: ["FavIcon"]) 31 | ], 32 | targets: [ 33 | .target(name: "FavIcon", dependencies: []), 34 | .testTarget(name: "FavIconTests", dependencies: ["FavIcon"]) 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FavIcon [![License](https://img.shields.io/badge/license-Apache%202.0-lightgrey.svg)](https://raw.githubusercontent.com/leonbreedt/FavIcon/main/LICENSE) [![Build Status](https://travis-ci.org/leonbreedt/FavIcon.svg)](https://travis-ci.org/leonbreedt/FavIcon) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) ![Swift 5.0](https://img.shields.io/badge/Swift-5.0-orange.svg) ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20-lightgrey.svg) 2 | 3 | ## Not actively maintained 4 | 5 | I am no longer able to spend as much time working on this library. 6 | 7 | You may want to consider an using an alternative, such as 8 | [FavIconFinder](https://github.com/will-lumley/FaviconFinder), which is pure 9 | Swift and is more actively maintained. 10 | 11 | ## What is this? 12 | 13 | FavIcon is a tiny Swift library for downloading the favicon representing a website. 14 | 15 | Wait, why is a library needed to do this? Surely it's just a simple HTTP GET of 16 | `/favicon.ico`, right? Right? Well. Go have a read of [this StackOverflow 17 | post](http://stackoverflow.com/questions/19029342/favicons-best-practices), and 18 | see how you feel afterwards. 19 | 20 | ## Quick Start 21 | 22 | ### Swift Package Manager 23 | 24 | Add it to your `Package.swift` as a dependency: 25 | 26 | ```swift 27 | // swift-tools-version:5.0 28 | import PackageDescription 29 | 30 | let package = Package( 31 | ... 32 | dependencies: [ 33 | .package(url: "https://github.com/leonbreedt/FavIcon.git", from: "3.1.0"), 34 | ], 35 | ) 36 | ``` 37 | 38 | ### CocoaPods 39 | 40 | *Note:* CocoaPods (1.4.0 or later) is required. 41 | 42 | Add it to your `Podfile`: 43 | 44 | ```ruby 45 | use_frameworks! 46 | pod 'FavIcon', '~> 3.1.0' 47 | ``` 48 | 49 | ### Carthage 50 | 51 | Add it to your `Cartfile`: 52 | 53 | ```ogdl 54 | github "leonbreedt/FavIcon" ~> 3.1.0 55 | ``` 56 | 57 | ## Features 58 | - Detection of `/favicon.ico` if it exists 59 | - Parsing of the HTML at a URL, and scanning for appropriate `` or 60 | `` tags that refer to icons using Apple, Google or Microsoft 61 | conventions. 62 | - Discovery of and parsing of Web Application manifest JSON files to obtain 63 | lists of icons. 64 | - Discovery of and parsing of Microsoft browser configuration XML files for 65 | obtaining lists of icons. 66 | 67 | Yup. These are all potential ways of indicating that your website has an icon 68 | that can be used in user interfaces. Good work, fellow programmers. 👍 69 | 70 | ## Usage Example 71 | Perhaps you have a location in your user interface where you want to put 72 | the icon of a website the user is currently visiting? 73 | 74 | ```swift 75 | try FavIcon.downloadPreferred("https://apple.com") { result in 76 | if case let .success(image) = result { 77 | // On iOS, this is a UIImage, do something with it here. 78 | // This closure will be executed on the main queue, so it's safe to touch 79 | // the UI here. 80 | } 81 | } 82 | ``` 83 | 84 | This will detect all of the available icons at the URL, and if it is able to 85 | determine their sizes, it will try to find the icon closest in size to your 86 | desired size, otherwise, it will prefer the largest icon. If it has no idea of 87 | the size of any of the icons, it will prefer the first one it found. 88 | 89 | Of course, if this approach is too opaque for you, you can download them all 90 | using `downloadAll(url:completion:)`. 91 | 92 | Or perhaps you’d like to take a stab at downloading them yourself at a later 93 | time, choosing which icon you prefer based on your own criteria, in which case 94 | `scan(url:completion:)` will give you information about the detected icons, which 95 | you can feed to `download(url:completion:)` for downloading at your convenience. 96 | 97 | 98 | ## Example Project 99 | 100 | See the iOS project in `Example/` for a simple example of how to use the library. 101 | 102 | ## License 103 | 104 | Apache 2.0 105 | 106 | -------------------------------------------------------------------------------- /Sources/FavIcon/ContentTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import Foundation 19 | 20 | func parseHTTPContentType(_ headerValue: String) -> (mimeType: String, encoding: String.Encoding) { 21 | let headerComponents = headerValue 22 | .components(separatedBy: ";") 23 | .map { $0.trimmingCharacters(in: .whitespaces) } 24 | 25 | if headerComponents.count > 1 { 26 | let parameterValues = headerComponents[1..(uniqueKeysWithValues: parameterValues) 32 | 33 | // Default according to RFC is ISO-8859-1, but probably nothing obeys that, so default 34 | // to UTF-8 instead. 35 | var encoding = String.Encoding.utf8 36 | if let charset = parameters["charset"], let parsedEncoding = parseStringEncoding(charset) { 37 | encoding = parsedEncoding 38 | } 39 | 40 | return (mimeType: headerComponents[0], encoding: encoding) 41 | } else { 42 | return (mimeType: headerComponents[0], encoding: .utf8) 43 | } 44 | } 45 | 46 | func parseStringEncoding(_ value: String) -> String.Encoding? { 47 | switch value.lowercased() { 48 | case "iso-8859-1", "latin1": return .isoLatin1 49 | case "iso-8859-2", "latin2": return .isoLatin2 50 | case "iso-2022-jp": return .iso2022JP 51 | case "shift_jis": return .shiftJIS 52 | case "us-ascii": return .ascii 53 | case "utf-8": return .utf8 54 | case "utf-16": return .utf16 55 | case "utf-32": return .utf32 56 | case "utf-32be": return .utf32BigEndian 57 | case "utf-32le": return .utf32LittleEndian 58 | case "windows-1250": return .windowsCP1250 59 | case "windows-1251": return .windowsCP1251 60 | case "windows-1252": return .windowsCP1252 61 | case "windows-1253": return .windowsCP1253 62 | case "windows-1254": return .windowsCP1254 63 | case "x-mac-roman": return .macOSRoman 64 | default: 65 | return nil 66 | } 67 | } 68 | 69 | extension HTTPURLResponse { 70 | func mimeTypeAndEncoding() -> (mimeType: String, encoding: String.Encoding) { 71 | if let contentType = allHeaderFields["Content-Type"] as? String { 72 | return parseHTTPContentType(contentType) 73 | } 74 | return (mimeType: "application/octet-stream", encoding: .utf8) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/FavIcon/Detection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import Foundation 19 | 20 | private let iconSizeTypeHints: [IconSize: IconType] = [ 21 | IconSize(width: 16, height: 16): .classic, 22 | IconSize(width: 32, height: 32): .appleOSXSafariTab, 23 | IconSize(width: 96, height: 96): .googleTV, 24 | IconSize(width: 192, height: 192): .googleAndroidChrome, 25 | IconSize(width: 196, height: 196): .googleAndroidChrome 26 | ] 27 | 28 | private let microsoftSizeHints: [String: IconSize] = [ 29 | "msapplication-tileimage": IconSize(width: 144, height: 144), 30 | "msapplication-square70x70logo": IconSize(width: 70, height: 70), 31 | "msapplication-square150x150logo": IconSize(width: 150, height: 150), 32 | "msapplication-wide310x150logo": IconSize(width: 310, height: 150), 33 | "msapplication-square310x310logo": IconSize(width: 310, height: 310) 34 | ] 35 | 36 | func detectHTMLHeadIcons(_ document: HTMLDocument, baseURL: URL) -> [Icon] { 37 | var icons = [Icon]() 38 | 39 | for link in document.query(xpath: "/html/head/link") { 40 | guard let rel = link.attributes["rel"] else { continue } 41 | guard let href = link.attributes["href"] else { continue } 42 | guard let url = URL(string: href, relativeTo: baseURL)?.absoluteURL else { continue } 43 | 44 | switch rel.lowercased() { 45 | case "shortcut icon": 46 | icons.append(Icon(url: url.absoluteURL, type: .shortcut)) 47 | case "icon": 48 | let sizes = parseHTMLIconSizes(link.attributes["sizes"]) 49 | if sizes.count > 0 { 50 | for size in sizes { 51 | let type = iconSizeTypeHints[size] ?? .classic 52 | icons.append(Icon(url: url, type: type, width: size.width, height: size.height)) 53 | } 54 | } else { 55 | icons.append(Icon(url: url.absoluteURL, type: .classic)) 56 | } 57 | case "apple-touch-icon", 58 | "apple-touch-icon-precomposed": 59 | let sizes = parseHTMLIconSizes(link.attributes["sizes"]) 60 | if sizes.count > 0 { 61 | for size in sizes { 62 | icons.append(Icon(url: url, type: .appleIOSWebClip, width: size.width, height: size.height)) 63 | } 64 | } else { 65 | icons.append(Icon(url: url.absoluteURL, type: .appleIOSWebClip, width: 60, height: 60)) 66 | } 67 | default: 68 | break 69 | } 70 | } 71 | 72 | for meta in document.query(xpath: "/html/head/meta") { 73 | if let name = meta.attributes["name"]?.lowercased() { 74 | guard let content = meta.attributes["content"] else { continue } 75 | guard let url = URL(string: content, relativeTo: baseURL) else { continue } 76 | guard let size = microsoftSizeHints[name] else { continue } 77 | icons.append(Icon(url: url, type: .microsoftPinnedSite, width: size.width, height: size.height)) 78 | } else if let property = meta.attributes["property"]?.lowercased() { 79 | guard let content = meta.attributes["content"] else { continue } 80 | guard property == "og:image" else { continue } 81 | guard let imageURL = URL(string: content, relativeTo: baseURL) else { continue } 82 | icons.append(Icon(url: imageURL, type: .openGraphImage)) 83 | } 84 | } 85 | 86 | return icons 87 | } 88 | 89 | func extractWebAppManifestURLs(_ document: HTMLDocument, baseURL: URL) -> [URL] { 90 | var urls: [URL] = [] 91 | for link in document.query(xpath: "/html/head/link") { 92 | guard let rel = link.attributes["rel"]?.lowercased() else { continue } 93 | guard rel == "manifest" else { continue } 94 | guard let href = link.attributes["href"] else { continue } 95 | guard let manifestURL = URL(string: href, relativeTo: baseURL) else { continue } 96 | 97 | urls.append(manifestURL) 98 | } 99 | return urls 100 | } 101 | 102 | func detectWebAppManifestIcons(_ json: String, baseURL: URL) -> [Icon] { 103 | var icons: [Icon] = [] 104 | 105 | guard let data = json.data(using: .utf8) else { return icons } 106 | guard let object = try? JSONSerialization.jsonObject(with: data, options: []) else { 107 | return icons 108 | } 109 | guard let manifest = object as? [String: Any] else { return icons } 110 | guard let manifestIcons = manifest["icons"] as? [[String: Any]] else { return icons } 111 | 112 | for icon in manifestIcons { 113 | guard let type = icon["type"] as? String else { continue } 114 | guard type.lowercased() == "image/png" else { continue } 115 | guard let src = icon["src"] as? String else { continue } 116 | guard let sizeValues = icon["sizes"] as? String else { continue } 117 | guard let url = URL(string: src, relativeTo: baseURL)?.absoluteURL else { continue } 118 | 119 | let sizes = parseHTMLIconSizes(sizeValues) 120 | if sizes.count > 0 { 121 | for size in sizes { 122 | icons.append(Icon(url: url, type: .webAppManifest, width: size.width, height: size.height)) 123 | } 124 | } else { 125 | icons.append(Icon(url: url, type: .webAppManifest)) 126 | } 127 | } 128 | 129 | return icons 130 | } 131 | 132 | func extractBrowserConfigURL(_ document: HTMLDocument, baseURL: URL) -> (url: URL?, disabled: Bool) { 133 | for meta in document.query(xpath: "/html/head/meta") { 134 | guard let name = meta.attributes["name"]?.lowercased() else { continue } 135 | guard name == "msapplication-config" else { continue } 136 | guard let content = meta.attributes["content"] else { continue } 137 | if content.lowercased() == "none" { 138 | // Explicitly asked us not to download the file. 139 | return (url: nil, disabled: true) 140 | } else { 141 | return (url: URL(string: content, relativeTo: baseURL)?.absoluteURL, disabled: false) 142 | } 143 | } 144 | return (url: nil, disabled: false) 145 | } 146 | 147 | func detectBrowserConfigXMLIcons(_ document: XMLDocument, baseURL: URL) -> [Icon] { 148 | var icons: [Icon] = [] 149 | 150 | for tile in document.query(xpath: "/browserconfig/msapplication/tile/*") { 151 | guard let src = tile.attributes["src"] else { continue } 152 | guard let url = URL(string: src, relativeTo: baseURL)?.absoluteURL else { continue } 153 | 154 | switch tile.name.lowercased() { 155 | case "tileimage": 156 | icons.append(Icon(url: url, type: .microsoftPinnedSite, width: 144, height: 144)) 157 | case "square70x70logo": 158 | icons.append(Icon(url: url, type: .microsoftPinnedSite, width: 70, height: 70)) 159 | case "square150x150logo": 160 | icons.append(Icon(url: url, type: .microsoftPinnedSite, width: 150, height: 150)) 161 | case "wide310x150logo": 162 | icons.append(Icon(url: url, type: .microsoftPinnedSite, width: 310, height: 150)) 163 | case "square310x310logo": 164 | icons.append(Icon(url: url, type: .microsoftPinnedSite, width: 310, height: 310)) 165 | default: 166 | break 167 | } 168 | } 169 | 170 | return icons 171 | } 172 | 173 | private func parseHTMLIconSizes(_ string: String?) -> [IconSize] { 174 | var sizes: [IconSize] = [] 175 | if let string = string?.lowercased(), string != "any" { 176 | for size in string.components(separatedBy: .whitespaces) { 177 | let parts = size.components(separatedBy: "x") 178 | if parts.count != 2 { continue } 179 | if let width = Int(parts[0]), let height = Int(parts[1]) { 180 | sizes.append(IconSize(width: width, height: height)) 181 | } 182 | } 183 | } 184 | return sizes 185 | } 186 | 187 | extension IconSize: Hashable { 188 | func hash(into hasher: inout Hasher) { 189 | hasher.combine(width) 190 | hasher.combine(height) 191 | } 192 | } 193 | 194 | private func == (lhs: IconSize, rhs: IconSize) -> Bool { 195 | return lhs.width == rhs.width && lhs.height == rhs.height 196 | } 197 | 198 | private struct IconSize { 199 | let width: Int 200 | let height: Int 201 | } 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /Sources/FavIcon/Download.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import Foundation 19 | 20 | enum DownloadResult { 21 | case text(value: String, mimeType: String, actualURL: URL) 22 | case binary(data: Data, mimeType: String, actualURL: URL) 23 | case exists 24 | case error(Error) 25 | } 26 | 27 | enum DownloadError: Error { 28 | case invalidResponse 29 | case emptyResponse 30 | case invalidTextResponse 31 | case notFound 32 | case serverError(code: Int) 33 | } 34 | 35 | private let downloadSession = URLSession(configuration: .ephemeral) 36 | 37 | func downloadURLs(_ urls: [URL], method: String = "GET", completion: @escaping ([DownloadResult]) -> Void) { 38 | let dispatchGroup = DispatchGroup() 39 | 40 | var results = [(index: Int, result: DownloadResult)]() 41 | let addResult: (Int, DownloadResult) -> Void = { (index: Int, result: DownloadResult) in 42 | DispatchQueue.main.async { 43 | results.append((index: index, result: result)) 44 | } 45 | } 46 | 47 | for (index, url) in urls.enumerated() { 48 | dispatchGroup.enter() 49 | 50 | var request = URLRequest(url: url) 51 | request.httpMethod = method 52 | let task = downloadSession.dataTask(with: request) { data, response, error in 53 | defer { 54 | dispatchGroup.leave() 55 | } 56 | 57 | guard error == nil else { 58 | addResult(index, .error(error!)) 59 | return 60 | } 61 | 62 | guard let response = response else { 63 | addResult(index, .error(DownloadError.invalidResponse)) 64 | return 65 | } 66 | 67 | if let httpResponse = response as? HTTPURLResponse { 68 | if httpResponse.statusCode == 404 { 69 | addResult(index, .error(DownloadError.notFound)) 70 | return 71 | } 72 | 73 | if httpResponse.statusCode < 200 || httpResponse.statusCode > 299 { 74 | addResult(index, .error(DownloadError.serverError(code: httpResponse.statusCode))) 75 | return 76 | } 77 | 78 | if method.lowercased() == "head" { 79 | addResult(index, .exists) 80 | return 81 | } 82 | } 83 | 84 | if let data = data { 85 | let mimeType = response.mimeType ?? "application/octet-stream" 86 | let encoding: String.Encoding = response.textEncodingName != nil 87 | ? parseStringEncoding(response.textEncodingName!) ?? .utf8 88 | : .utf8 89 | if mimeType.starts(with: "text/") || mimeType == "application/json" { 90 | guard let text = String(data: data, encoding: encoding) else { 91 | addResult(index, .error(DownloadError.invalidTextResponse)) 92 | return 93 | } 94 | addResult(index, .text(value: text, mimeType: mimeType, actualURL: response.url!)) 95 | return 96 | } else { 97 | addResult(index, .binary(data: data, mimeType: mimeType, actualURL: response.url!)) 98 | return 99 | } 100 | } else { 101 | addResult(index, .error(DownloadError.emptyResponse)) 102 | return 103 | } 104 | } 105 | 106 | task.resume() 107 | } 108 | 109 | dispatchGroup.notify(queue: .main) { 110 | let sortedResults = 111 | results 112 | .sorted(by: { $0.index < $1.index }) 113 | .map { $0.result } 114 | completion(sortedResults) 115 | } 116 | } 117 | 118 | func downloadURL(_ url: URL, method: String = "GET", completion: @escaping (DownloadResult) -> Void) { 119 | downloadURLs([url], method: method) { results in 120 | completion(results.first!) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/FavIcon/FavIcon.h: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | #import 19 | 20 | //! Project version number for FavIcon. 21 | FOUNDATION_EXPORT double FavIconVersionNumber; 22 | 23 | //! Project version string for FavIcon. 24 | FOUNDATION_EXPORT const unsigned char FavIconVersionString[]; 25 | -------------------------------------------------------------------------------- /Sources/FavIcon/FavIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import Foundation 19 | 20 | #if os(OSX) 21 | import Cocoa 22 | public typealias ImageType = NSImage 23 | #else 24 | import UIKit 25 | public typealias ImageType = UIImage 26 | #endif 27 | 28 | /// The result of downloading an icon. 29 | public enum IconDownloadResult { 30 | /// The icon was downloaded successfully. 31 | /// - parameter image: The downloaded icon image. 32 | case success(image: ImageType) 33 | /// The icon failed to download 34 | /// - parameter error: The error causing the download to fail. 35 | case failure(error: Error) 36 | } 37 | 38 | /// Enumerates errors that could occur while scanning or downloading. 39 | enum IconError: Error { 40 | /// Invalid URL specified. 41 | case invalidBaseURL 42 | /// An invalid response was received while attempting to download an icon. 43 | case invalidDownloadResponse 44 | /// No icons were detected at the supplied URL. 45 | case noIconsDetected 46 | /// The icon image was corrupt, or is not of a supported file format. 47 | case corruptImage 48 | } 49 | 50 | public final class FavIcon { 51 | /// Scans a base URL, attempting to determine all of the supported icons that can 52 | /// be used for favicon purposes. 53 | /// 54 | /// It will do the following to determine possible icons that can be used: 55 | /// 56 | /// - Check whether or not `/favicon.ico` exists. 57 | /// - If the base URL returns an HTML page, parse the `` section and check for `` 58 | /// and `` tags that reference icons using Apple, Microsoft and Google 59 | /// conventions. 60 | /// - If _Web Application Manifest JSON_ (`manifest.json`) files are referenced, or 61 | /// _Microsoft browser configuration XML_ (`browserconfig.xml`) files 62 | /// are referenced, download and parse them to check if they reference icons. 63 | /// 64 | /// All of this work is performed in a background queue. 65 | /// 66 | /// - parameter url: The base URL to scan. 67 | /// - parameter completion: A closure to call when the scan has completed. The closure will be call 68 | /// on the main queue. 69 | public static func scan(_ url: URL, 70 | completion: @escaping ([Icon]) -> Void) { 71 | let htmlURL = url 72 | let favIconURL = URL(string: "/favicon.ico", relativeTo: url as URL)!.absoluteURL 73 | 74 | var icons = [Icon]() 75 | let group = DispatchGroup() 76 | 77 | group.enter() 78 | downloadURL(favIconURL, method: "HEAD") { result in 79 | defer { group.leave() } 80 | switch result { 81 | case .exists: 82 | icons.append(Icon(url: favIconURL, type: .classic)) 83 | default: 84 | return 85 | } 86 | } 87 | 88 | group.enter() 89 | downloadURL(htmlURL) { result in 90 | defer { group.leave() } 91 | if case .text(let text, let mimeType, let downloadedURL) = result { 92 | guard mimeType == "text/html" else { return } 93 | guard let data = text.data(using: .utf8) else { return } 94 | 95 | let document = HTMLDocument(data: data) 96 | 97 | icons.append(contentsOf: detectHTMLHeadIcons(document, baseURL: downloadedURL)) 98 | for manifestURL in extractWebAppManifestURLs(document, baseURL: downloadedURL) { 99 | group.enter() 100 | downloadURL(manifestURL) { result in 101 | defer { group.leave() } 102 | if case .text(let text, _, let downloadedURL) = result { 103 | icons.append(contentsOf: detectWebAppManifestIcons(text, baseURL: downloadedURL)) 104 | } 105 | } 106 | } 107 | 108 | let browserConfigResult = extractBrowserConfigURL(document, baseURL: url) 109 | if let browserConfigURL = browserConfigResult.url, !browserConfigResult.disabled { 110 | group.enter() 111 | downloadURL(browserConfigURL) { result in 112 | defer { group.leave() } 113 | if case .text(let text, _, let downloadedURL) = result { 114 | let document = XMLDocument(string: text) 115 | icons.append(contentsOf: detectBrowserConfigXMLIcons(document, baseURL: downloadedURL)) 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | group.notify(queue: .main) { 123 | completion(icons) 124 | } 125 | } 126 | 127 | /// Scans a base URL, attempting to determine all of the supported icons that can 128 | /// be used for favicon purposes. 129 | /// 130 | /// It will do the following to determine possible icons that can be used: 131 | /// 132 | /// - Check whether or not `/favicon.ico` exists. 133 | /// - If the base URL returns an HTML page, parse the `` section and check for `` 134 | /// and `` tags that reference icons using Apple, Microsoft and Google 135 | /// conventions. 136 | /// - If _Web Application Manifest JSON_ (`manifest.json`) files are referenced, or 137 | /// _Microsoft browser configuration XML_ (`browserconfig.xml`) files 138 | /// are referenced, download and parse them to check if they reference icons. 139 | /// 140 | /// All of this work is performed in a background queue. 141 | /// 142 | /// - parameter url: The base URL to scan. 143 | /// - parameter completion: A closure to call when the scan has completed. The closure will be call 144 | /// on the main queue. 145 | public static func scan(_ url: String, completion: @escaping ([Icon]) -> Void) throws { 146 | guard let url = URL(string: url) else { throw IconError.invalidBaseURL } 147 | scan(url, completion: completion) 148 | } 149 | 150 | /// Downloads an array of detected icons in the background. 151 | /// 152 | /// - parameter icons: The icons to download. 153 | /// - parameter completion: A closure to call when all download tasks have 154 | /// results available (successful or otherwise). The closure 155 | /// will be called on the main queue. 156 | public static func download(_ icons: [Icon], 157 | completion: @escaping ([IconDownloadResult]) -> Void) { 158 | let urls = icons.map { $0.url } 159 | downloadURLs(urls) { results in 160 | DispatchQueue.main.async { 161 | let downloadResults: [IconDownloadResult] = results.map { result in 162 | switch result { 163 | case .binary(let data, _, _): 164 | if let image = ImageType(data: data) { 165 | return .success(image: image) 166 | } else { 167 | return .failure(error: IconError.corruptImage) 168 | } 169 | case .error(let error): 170 | return .failure(error: error) 171 | default: 172 | return .failure(error: IconError.invalidDownloadResponse) 173 | } 174 | } 175 | 176 | completion(sortResults(downloadResults)) 177 | } 178 | } 179 | } 180 | 181 | /// Downloads all available icons by calling `scan(url:)` to discover the available icons, and then 182 | /// performing background downloads of each icon. 183 | /// 184 | /// - parameter url: The URL to scan for icons. 185 | /// - parameter completion: A closure to call when all download tasks have results available 186 | /// (successful or otherwise). The closure will be called on the main queue. 187 | public static func downloadAll(_ url: URL, completion: @escaping ([IconDownloadResult]) -> Void) { 188 | scan(url) { icons in 189 | download(icons, completion: completion) 190 | } 191 | } 192 | 193 | /// Downloads all available icons by calling `scan(url:)` to discover the available icons, and then 194 | /// performing background downloads of each icon. 195 | /// 196 | /// - parameter url: The URL to scan for icons. 197 | /// - parameter completion: A closure to call when all download tasks have results available 198 | /// (successful or otherwise). The closure will be called on the main queue. 199 | public static func downloadAll(_ url: String, completion: @escaping ([IconDownloadResult]) -> Void) throws { 200 | guard let url = URL(string: url) else { throw IconError.invalidBaseURL } 201 | downloadAll(url, completion: completion) 202 | } 203 | 204 | /// Downloads the most preferred icon, by calling `scan(url:)` to discover available icons, and then choosing 205 | /// the most preferable available icon. If both `width` and `height` are supplied, the icon closest to the 206 | /// preferred size is chosen. Otherwise, the largest icon is chosen, if dimensions are known. If no icon 207 | /// has dimensions, the icons are chosen by order of their `DetectedIconType` enumeration raw value. 208 | /// 209 | /// - parameter url: The URL to scan for icons. 210 | /// - parameter width: The preferred icon width, in pixels, or `nil`. 211 | /// - parameter height: The preferred icon height, in pixels, or `nil`. 212 | /// - parameter completion: A closure to call when the download task has produced results. The closure will 213 | /// be called on the main queue. 214 | /// - throws: An appropriate `IconError` if downloading was not successful. 215 | public static func downloadPreferred(_ url: URL, 216 | width: Int? = nil, 217 | height: Int? = nil, 218 | completion: @escaping (IconDownloadResult) -> Void) throws { 219 | scan(url) { icons in 220 | let sortedIcons = sortIcons(icons, preferredWidth: width, preferredHeight: height) 221 | if sortedIcons.count == 0 { 222 | DispatchQueue.main.async { 223 | completion(.failure(error: IconError.noIconsDetected)) 224 | } 225 | return 226 | } 227 | 228 | download(sortedIcons) { results in 229 | let downloadResult: IconDownloadResult 230 | let sortedResults = sortResults(results, preferredWidth: width, preferredHeight: height) 231 | if sortedResults.count > 0 { 232 | downloadResult = sortedResults[0] 233 | } else { 234 | downloadResult = .failure(error: IconError.noIconsDetected) 235 | } 236 | 237 | DispatchQueue.main.async { 238 | completion(downloadResult) 239 | } 240 | } 241 | } 242 | } 243 | 244 | /// Downloads the most preferred icon, by calling `scan(url:)` to discover available icons, and then choosing 245 | /// the most preferable available icon. If both `width` and `height` are supplied, the icon closest to the 246 | /// preferred size is chosen. Otherwise, the largest icon is chosen, if dimensions are known. If no icon 247 | /// has dimensions, the icons are chosen by order of their `DetectedIconType` enumeration raw value. 248 | /// 249 | /// - parameter url: The URL to scan for icons. 250 | /// - parameter width: The preferred icon width, in pixels, or `nil`. 251 | /// - parameter height: The preferred icon height, in pixels, or `nil`. 252 | /// - parameter completion: A closure to call when the download task has produced results. The closure will 253 | /// be called on the main queue. 254 | /// - throws: An appropriate `IconError` if downloading was not successful. 255 | public static func downloadPreferred(_ url: String, 256 | width: Int? = nil, 257 | height: Int? = nil, 258 | completion: @escaping (IconDownloadResult) -> Void) throws { 259 | guard let url = URL(string: url) else { throw IconError.invalidBaseURL } 260 | try downloadPreferred(url, width: width, height: height, completion: completion) 261 | } 262 | 263 | static func sortIcons(_ icons: [Icon], preferredWidth: Int? = nil, preferredHeight: Int? = nil) -> [Icon] { 264 | guard icons.count > 0 else { return [] } 265 | 266 | let iconsInPreferredOrder = icons.sorted { left, right in 267 | // Fall back to comparing dimensions. 268 | if let preferredWidth = preferredWidth, let preferredHeight = preferredHeight, 269 | let widthLeft = left.width, let heightLeft = left.height, 270 | let widthRight = right.width, let heightRight = right.height { 271 | // Which is closest to preferred size? 272 | let deltaA = abs(widthLeft - preferredWidth) * abs(heightLeft - preferredHeight) 273 | let deltaB = abs(widthRight - preferredWidth) * abs(heightRight - preferredHeight) 274 | return deltaA < deltaB 275 | } else { 276 | if let areaLeft = left.area, let areaRight = right.area { 277 | // Which is larger? 278 | return areaRight < areaLeft 279 | } 280 | } 281 | 282 | if left.area != nil { 283 | // Only A has dimensions, prefer it. 284 | return true 285 | } 286 | if right.area != nil { 287 | // Only B has dimensions, prefer it. 288 | return false 289 | } 290 | 291 | // Neither has dimensions, order by enum value 292 | return left.type.rawValue < right.type.rawValue 293 | } 294 | 295 | return iconsInPreferredOrder 296 | } 297 | 298 | static func sortResults(_ results: [IconDownloadResult], preferredWidth: Int? = nil, preferredHeight: Int? = nil) -> [IconDownloadResult] { 299 | guard results.count > 0 else { return [] } 300 | 301 | let resultsInPreferredOrder = results.sorted { left, right in 302 | switch (left, right) { 303 | case (.success(let leftImage), .success(let rightImage)): 304 | if let preferredWidth = preferredWidth, let preferredHeight = preferredHeight { 305 | let widthLeft = leftImage.size.width 306 | let heightLeft = leftImage.size.height 307 | let widthRight = rightImage.size.width 308 | let heightRight = rightImage.size.height 309 | 310 | // Which is closest to preferred size? 311 | let deltaA = abs(widthLeft - CGFloat(preferredWidth)) * abs(heightLeft - CGFloat(preferredHeight)) 312 | let deltaB = abs(widthRight - CGFloat(preferredWidth)) * abs(heightRight - CGFloat(preferredHeight)) 313 | 314 | return deltaA < deltaB 315 | } else { 316 | // Fall back to largest image. 317 | return leftImage.area > rightImage.area 318 | } 319 | case (.success, .failure): 320 | // An image is better than none. 321 | return true 322 | case (.failure, .success): 323 | // An image is better than none. 324 | return false 325 | default: 326 | return true 327 | } 328 | } 329 | 330 | return resultsInPreferredOrder 331 | 332 | } 333 | } 334 | 335 | extension Icon { 336 | var area: Int? { 337 | if let width = width, let height = height { 338 | return width * height 339 | } 340 | return nil 341 | } 342 | } 343 | 344 | extension ImageType { 345 | var area: CGFloat { 346 | return size.width * size.height 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /Sources/FavIcon/HTML.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import Foundation 19 | import libxml2 20 | 21 | final class HTMLDocument { 22 | fileprivate var _document: htmlDocPtr! 23 | private var _children: [HTMLElement]? 24 | 25 | convenience init(string: String) { 26 | self.init(data: string.data(using: .utf8)!) 27 | } 28 | 29 | init(data: Data) { 30 | ensureLibXMLErrorHandlingSuppressed() 31 | 32 | guard data.count > 0 else { return } 33 | 34 | data.withUnsafeBytes { ptr in 35 | guard let baseAddress = ptr.baseAddress else { return } 36 | _document = htmlReadMemory(baseAddress.assumingMemoryBound(to: Int8.self), Int32(data.count), nil, nil, 0) 37 | } 38 | } 39 | 40 | var children: [HTMLElement] { 41 | guard let document = _document else { 42 | return [] 43 | } 44 | if let children = _children { 45 | return children 46 | } 47 | var newChildren = [HTMLElement]() 48 | 49 | var currentChild = document.pointee.children 50 | while currentChild != nil { 51 | if currentChild?.pointee.type == XML_ELEMENT_NODE { 52 | newChildren.append(HTMLElement(document: self, node: currentChild!)) 53 | } 54 | currentChild = currentChild?.pointee.next 55 | } 56 | 57 | _children = newChildren 58 | return newChildren 59 | } 60 | 61 | func query(xpath: String) -> [HTMLElement] { 62 | var results = [HTMLElement]() 63 | 64 | guard let document = _document else { return results } 65 | guard let context = xmlXPathNewContext(document) else { 66 | return results 67 | } 68 | defer { xmlXPathFreeContext(context) } 69 | 70 | var object: xmlXPathObjectPtr? = nil 71 | xpath.withCString { str in 72 | str.withMemoryRebound(to: UInt8.self, capacity: 1) { strp in 73 | object = xmlXPathEvalExpression(strp, context) 74 | } 75 | } 76 | guard object != nil else { 77 | return results 78 | } 79 | defer { xmlXPathFreeObject(object) } 80 | 81 | let nodeCount = object!.pointee.nodesetval.pointee.nodeNr 82 | for i in 0.. [HTMLElement] { 178 | var results = [HTMLElement]() 179 | 180 | guard let document = _document else { return results } 181 | guard let context = xmlXPathNewContext(document._document) else { 182 | return results 183 | } 184 | defer { xmlXPathFreeContext(context) } 185 | 186 | var object: xmlXPathObjectPtr? = nil 187 | xpath.withCString { str in 188 | str.withMemoryRebound(to: UInt8.self, capacity: 1) { strp in 189 | object = xmlXPathEvalExpression(strp, context) 190 | } 191 | } 192 | guard object != nil else { 193 | return results 194 | } 195 | defer { xmlXPathFreeObject(object) } 196 | 197 | let nodeCount = object!.pointee.nodesetval.pointee.nodeNr 198 | for i in 0.. 0 else { return } 33 | 34 | data.withUnsafeBytes { ptr in 35 | guard let baseAddress = ptr.baseAddress else { return } 36 | _document = xmlReadMemory(baseAddress.assumingMemoryBound(to: Int8.self), Int32(data.count), nil, nil, 0) 37 | } 38 | } 39 | 40 | var children: [XMLElement] { 41 | guard let document = _document else { 42 | return [] 43 | } 44 | if let children = _children { 45 | return children 46 | } 47 | var newChildren = [XMLElement]() 48 | 49 | var currentChild = document.pointee.children 50 | while currentChild != nil { 51 | if currentChild?.pointee.type == XML_ELEMENT_NODE { 52 | newChildren.append(XMLElement(document: self, node: currentChild!)) 53 | } 54 | currentChild = currentChild?.pointee.next 55 | } 56 | 57 | _children = newChildren 58 | return newChildren 59 | } 60 | 61 | func query(xpath: String) -> [XMLElement] { 62 | var results = [XMLElement]() 63 | 64 | guard let document = _document else { return results } 65 | guard let context = xmlXPathNewContext(document) else { 66 | return results 67 | } 68 | defer { xmlXPathFreeContext(context) } 69 | 70 | var object: xmlXPathObjectPtr? = nil 71 | xpath.withCString { str in 72 | str.withMemoryRebound(to: UInt8.self, capacity: 1) { strp in 73 | object = xmlXPathEvalExpression(strp, context) 74 | } 75 | } 76 | guard object != nil else { 77 | return results 78 | } 79 | defer { xmlXPathFreeObject(object) } 80 | 81 | let nodeCount = object!.pointee.nodesetval.pointee.nodeNr 82 | for i in 0.. [XMLElement] { 177 | var results = [XMLElement]() 178 | 179 | guard let document = _document else { return results } 180 | guard let context = xmlXPathNewContext(document._document) else { 181 | return results 182 | } 183 | defer { xmlXPathFreeContext(context) } 184 | 185 | var object: xmlXPathObjectPtr? = nil 186 | xpath.withCString { str in 187 | str.withMemoryRebound(to: UInt8.self, capacity: 1) { strp in 188 | object = xmlXPathEvalExpression(strp, context) 189 | } 190 | } 191 | guard object != nil else { 192 | return results 193 | } 194 | defer { xmlXPathFreeObject(object) } 195 | 196 | let nodeCount = object!.pointee.nodesetval.pointee.nodeNr 197 | for i in 0.. 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | FavIcon 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2020 Leon Breedt. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Tests/FavIconTests/BrowserConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | #009900 11 | 12 | 13 | 14 | 30 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 23 | 1 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/FavIconTests/DetectionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import XCTest 19 | @testable import FavIcon 20 | 21 | import Foundation 22 | 23 | class DetectionTests: XCTestCase { 24 | func testManifestJSONDetection() { 25 | let json = stringForContentsOfFile(filePath: pathForTestBundleResource(fileName: "Manifest.json")) ?? "" 26 | let icons = detectWebAppManifestIcons(json, baseURL: URL(string: "https://localhost")!.absoluteURL) 27 | 28 | XCTAssertEqual(6, icons.count) 29 | 30 | XCTAssertEqual("https://localhost/launcher-icon-0-75x.png", icons[0].url.absoluteString) 31 | XCTAssertEqual(IconType.webAppManifest.rawValue, icons[0].type.rawValue) 32 | XCTAssertEqual(36, icons[0].width!) 33 | XCTAssertEqual(36, icons[0].height!) 34 | 35 | XCTAssertEqual("https://localhost/launcher-icon-1x.png", icons[1].url.absoluteString) 36 | XCTAssertEqual(IconType.webAppManifest.rawValue, icons[1].type.rawValue) 37 | XCTAssertEqual(48, icons[1].width!) 38 | XCTAssertEqual(48, icons[1].height!) 39 | 40 | XCTAssertEqual("https://localhost/launcher-icon-1-5x.png", icons[2].url.absoluteString) 41 | XCTAssertEqual(IconType.webAppManifest.rawValue, icons[2].type.rawValue) 42 | XCTAssertEqual(72, icons[2].width!) 43 | XCTAssertEqual(72, icons[2].height!) 44 | 45 | XCTAssertEqual("https://localhost/launcher-icon-2x.png", icons[3].url.absoluteString) 46 | XCTAssertEqual(IconType.webAppManifest.rawValue, icons[3].type.rawValue) 47 | XCTAssertEqual(96, icons[3].width!) 48 | XCTAssertEqual(96, icons[3].height!) 49 | 50 | XCTAssertEqual("https://localhost/launcher-icon-3x.png", icons[4].url.absoluteString) 51 | XCTAssertEqual(IconType.webAppManifest.rawValue, icons[4].type.rawValue) 52 | XCTAssertEqual(144, icons[4].width!) 53 | XCTAssertEqual(144, icons[4].height!) 54 | 55 | XCTAssertEqual("https://localhost/launcher-icon-4x.png", icons[5].url.absoluteString) 56 | XCTAssertEqual(IconType.webAppManifest.rawValue, icons[5].type.rawValue) 57 | XCTAssertEqual(192, icons[5].width!) 58 | XCTAssertEqual(192, icons[5].height!) 59 | } 60 | 61 | func testBrowserConfigXMLIconDetection() { 62 | let xml = stringForContentsOfFile(filePath: pathForTestBundleResource(fileName: "BrowserConfig.xml")) ?? "" 63 | let document = XMLDocument(string: xml) 64 | let icons = detectBrowserConfigXMLIcons(document, baseURL: URL(string: "https://localhost")!) 65 | 66 | XCTAssertEqual(5, icons.count) 67 | 68 | XCTAssertEqual("https://localhost/small.png", icons[0].url.absoluteString) 69 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[0].type.rawValue) 70 | XCTAssertEqual(70, icons[0].width!) 71 | XCTAssertEqual(70, icons[0].height!) 72 | 73 | XCTAssertEqual("https://localhost/medium.png", icons[1].url.absoluteString) 74 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[1].type.rawValue) 75 | XCTAssertEqual(150, icons[1].width!) 76 | XCTAssertEqual(150, icons[1].height!) 77 | 78 | XCTAssertEqual("https://localhost/wide.png", icons[2].url.absoluteString) 79 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[2].type.rawValue) 80 | XCTAssertEqual(310, icons[2].width!) 81 | XCTAssertEqual(150, icons[2].height!) 82 | 83 | XCTAssertEqual("https://localhost/large.png", icons[3].url.absoluteString) 84 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[3].type.rawValue) 85 | XCTAssertEqual(310, icons[3].width!) 86 | XCTAssertEqual(310, icons[3].height!) 87 | 88 | XCTAssertEqual("https://localhost/tile.png", icons[4].url.absoluteString) 89 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[4].type.rawValue) 90 | XCTAssertEqual(144, icons[4].width!) 91 | XCTAssertEqual(144, icons[4].height!) 92 | } 93 | 94 | func testHTMLHeadIconExtraction() { 95 | let html = stringForContentsOfFile(filePath: pathForTestBundleResource(fileName: "HTML.html")) ?? "" 96 | let document = HTMLDocument(string: html) 97 | let icons = detectHTMLHeadIcons(document, baseURL: URL(string: "https://localhost")!) 98 | 99 | XCTAssertEqual(26, icons.count) 100 | 101 | XCTAssertEqual("https://localhost/shortcut.ico", icons[0].url.absoluteString) 102 | XCTAssertEqual(IconType.shortcut.rawValue, icons[0].type.rawValue) 103 | XCTAssertNil(icons[0].width) 104 | XCTAssertNil(icons[0].height) 105 | 106 | XCTAssertEqual("https://localhost/content/images/favicon-96x96.png", icons[1].url.absoluteString) 107 | XCTAssertEqual(IconType.googleTV.rawValue, icons[1].type.rawValue) 108 | XCTAssertEqual(96, icons[1].width!) 109 | XCTAssertEqual(96, icons[1].height!) 110 | 111 | XCTAssertEqual("https://localhost/content/images/favicon-16x16.png", icons[2].url.absoluteString) 112 | XCTAssertEqual(IconType.classic.rawValue, icons[2].type.rawValue) 113 | XCTAssertEqual(16, icons[2].width!) 114 | XCTAssertEqual(16, icons[2].height!) 115 | 116 | XCTAssertEqual("https://localhost/content/images/favicon-32x32.png", icons[3].url.absoluteString) 117 | XCTAssertEqual(IconType.appleOSXSafariTab.rawValue, icons[3].type.rawValue) 118 | XCTAssertEqual(32, icons[3].width!) 119 | XCTAssertEqual(32, icons[3].height!) 120 | 121 | XCTAssertEqual("https://localhost/content/icons/favicon-192x192.png", icons[4].url.absoluteString) 122 | XCTAssertEqual(IconType.googleAndroidChrome.rawValue, icons[4].type.rawValue) 123 | XCTAssertEqual(192, icons[4].width!) 124 | XCTAssertEqual(192, icons[4].height!) 125 | 126 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-57x57.png", icons[5].url.absoluteString) 127 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[5].type.rawValue) 128 | XCTAssertEqual(57, icons[5].width!) 129 | XCTAssertEqual(57, icons[5].height!) 130 | 131 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-114x114.png", icons[6].url.absoluteString) 132 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[6].type.rawValue) 133 | XCTAssertEqual(114, icons[6].width!) 134 | XCTAssertEqual(114, icons[6].height!) 135 | 136 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-72x72.png", icons[7].url.absoluteString) 137 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[7].type.rawValue) 138 | XCTAssertEqual(72, icons[7].width!) 139 | XCTAssertEqual(72, icons[7].height!) 140 | 141 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-144x144.png", icons[8].url.absoluteString) 142 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[8].type.rawValue) 143 | XCTAssertEqual(144, icons[8].width!) 144 | XCTAssertEqual(144, icons[8].height!) 145 | 146 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-60x60.png", icons[9].url.absoluteString) 147 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[9].type.rawValue) 148 | XCTAssertEqual(60, icons[9].width!) 149 | XCTAssertEqual(60, icons[9].height!) 150 | 151 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-120x120.png", icons[10].url.absoluteString) 152 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[10].type.rawValue) 153 | XCTAssertEqual(120, icons[10].width!) 154 | XCTAssertEqual(120, icons[10].height!) 155 | 156 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-76x76.png", icons[11].url.absoluteString) 157 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[11].type.rawValue) 158 | XCTAssertEqual(76, icons[11].width!) 159 | XCTAssertEqual(76, icons[11].height!) 160 | 161 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-152x152.png", icons[12].url.absoluteString) 162 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[12].type.rawValue) 163 | XCTAssertEqual(152, icons[12].width!) 164 | XCTAssertEqual(152, icons[12].height!) 165 | 166 | XCTAssertEqual("https://localhost/content/images/apple-touch-icon-180x180.png", icons[13].url.absoluteString) 167 | XCTAssertEqual(IconType.appleIOSWebClip.rawValue, icons[13].type.rawValue) 168 | XCTAssertEqual(180, icons[13].width!) 169 | XCTAssertEqual(180, icons[13].height!) 170 | 171 | XCTAssertTrue(icons[14].url.absoluteString.starts(with: "data:")) 172 | XCTAssertEqual(IconType.shortcut.rawValue, icons[14].type.rawValue) 173 | XCTAssertNil(icons[14].width) 174 | XCTAssertNil(icons[14].height) 175 | 176 | XCTAssertEqual("https://s.ytimg.com/yts/img/favicon-vfl8qSV2F.ico", icons[15].url.absoluteString) 177 | XCTAssertEqual(IconType.shortcut.rawValue, icons[15].type.rawValue) 178 | XCTAssertNil(icons[15].width) 179 | XCTAssertNil(icons[15].height) 180 | 181 | XCTAssertEqual("https://s.ytimg.com/yts/img/favicon_32-vflOogEID.png", icons[16].url.absoluteString) 182 | XCTAssertEqual(IconType.appleOSXSafariTab.rawValue, icons[16].type.rawValue) 183 | XCTAssertEqual(32, icons[16].width!) 184 | XCTAssertEqual(32, icons[16].height!) 185 | 186 | XCTAssertEqual("https://s.ytimg.com/yts/img/favicon_48-vflVjB_Qk.png", icons[17].url.absoluteString) 187 | XCTAssertEqual(IconType.classic.rawValue, icons[17].type.rawValue) 188 | XCTAssertEqual(48, icons[17].width!) 189 | XCTAssertEqual(48, icons[17].height!) 190 | 191 | XCTAssertEqual("https://s.ytimg.com/yts/img/favicon_96-vflW9Ec0w.png", icons[18].url.absoluteString) 192 | XCTAssertEqual(IconType.googleTV.rawValue, icons[18].type.rawValue) 193 | XCTAssertEqual(96, icons[18].width!) 194 | XCTAssertEqual(96, icons[18].height!) 195 | 196 | XCTAssertEqual("https://s.ytimg.com/yts/img/favicon_144-vfliLAfaB.png", icons[19].url.absoluteString) 197 | XCTAssertEqual(IconType.classic.rawValue, icons[19].type.rawValue) 198 | XCTAssertEqual(144, icons[19].width!) 199 | XCTAssertEqual(144, icons[19].height!) 200 | 201 | XCTAssertEqual("https://localhost/content/images/mstile-144x144.png", icons[20].url.absoluteString) 202 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[20].type.rawValue) 203 | XCTAssertEqual(144, icons[20].width!) 204 | XCTAssertEqual(144, icons[20].height!) 205 | 206 | XCTAssertEqual("https://localhost/tile-tiny.png", icons[21].url.absoluteString) 207 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[21].type.rawValue) 208 | XCTAssertEqual(70, icons[21].width!) 209 | XCTAssertEqual(70, icons[21].height!) 210 | 211 | XCTAssertEqual("https://localhost/tile-square.png", icons[22].url.absoluteString) 212 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[22].type.rawValue) 213 | XCTAssertEqual(150, icons[22].width!) 214 | XCTAssertEqual(150, icons[22].height!) 215 | 216 | XCTAssertEqual("https://localhost/tile-wide.png", icons[23].url.absoluteString) 217 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[23].type.rawValue) 218 | XCTAssertEqual(310, icons[23].width!) 219 | XCTAssertEqual(150, icons[23].height!) 220 | 221 | XCTAssertEqual("https://localhost/tile-large.png", icons[24].url.absoluteString) 222 | XCTAssertEqual(IconType.microsoftPinnedSite.rawValue, icons[24].type.rawValue) 223 | XCTAssertEqual(310, icons[24].width!) 224 | XCTAssertEqual(310, icons[24].height!) 225 | 226 | XCTAssertEqual("https://www.facebook.com/images/fb_icon_325x325.png", icons[25].url.absoluteString) 227 | XCTAssertEqual(IconType.openGraphImage.rawValue, icons[25].type.rawValue) 228 | XCTAssertNil(icons[25].width) 229 | XCTAssertNil(icons[25].height) 230 | } 231 | 232 | func testIssue6_ContentTypeWithEmptyComponent() { 233 | let result = parseHTTPContentType("text/html;;charset=UTF-8") 234 | 235 | XCTAssertEqual("text/html", result.mimeType) 236 | XCTAssertEqual(String.Encoding.utf8, result.encoding) 237 | } 238 | 239 | func testIssue20_EmptyXML() { 240 | let document = XMLDocument(string: "") 241 | 242 | XCTAssertEqual(0, document.children.count) 243 | XCTAssertEqual(0, document.query(xpath: "/BrowserConfig").count) 244 | XCTAssertEqual(0, document.query(xpath: "").count) 245 | } 246 | 247 | func testIssue23_InvalidXMLDoesNotCrash() { 248 | let document = XMLDocument(string: " String { 256 | let testBundle = Bundle(for: FavIconTests.self) 257 | return testBundle.path(forResource: fileName, ofType: "")! 258 | } 259 | 260 | private func stringForContentsOfFile(filePath: String, encoding: String.Encoding = String.Encoding.utf8) -> String? { 261 | return try? String(contentsOfFile: filePath, encoding: encoding) as String 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /Tests/FavIconTests/DownloadTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import XCTest 19 | @testable import FavIcon 20 | 21 | import Foundation 22 | 23 | class DownloadTests: XCTestCase { 24 | func testDownloadText() { 25 | let result = performDownload(url: "https://apple.com") 26 | 27 | XCTAssertNotNil(result) 28 | 29 | if case .text(let value, let mimeType, _) = result! { 30 | XCTAssertEqual("text/html", mimeType) 31 | XCTAssertTrue(value.count > 0) 32 | } else { 33 | XCTFail("expected text response") 34 | } 35 | } 36 | 37 | func testDownloadTextAndImage() { 38 | let results = performDownloads(urls: ["https://apple.com", "https://apple.com/favicon.ico"]) 39 | 40 | XCTAssertNotNil(results) 41 | 42 | if case .text(let value, let mimeType, _) = results![0] { 43 | XCTAssertEqual("text/html", mimeType) 44 | XCTAssertTrue(value.count > 0) 45 | } else { 46 | XCTFail("expected text response for first result") 47 | } 48 | 49 | if case .binary(let data, let mimeType, _) = results![1] { 50 | XCTAssertEqual("image/x-icon", mimeType) 51 | XCTAssertTrue(data.count > 0) 52 | } else { 53 | XCTFail("expected binary response for second result") 54 | } 55 | } 56 | 57 | func testDownloadImage() { 58 | let result = performDownload(url: "https://google.com/favicon.ico") 59 | 60 | XCTAssertNotNil(result) 61 | 62 | if case .binary(let data, let mimeType, _) = result! { 63 | XCTAssertEqual("image/x-icon", mimeType) 64 | XCTAssertTrue(data.count > 0) 65 | } else { 66 | XCTFail("expected binary response") 67 | } 68 | } 69 | 70 | private func performDownloads(urls: [String], timeout: TimeInterval = 15.0) -> [DownloadResult]? { 71 | var actualResults: [DownloadResult]? 72 | 73 | let downloadsCompleted = expectation(description: "download: \(urls)") 74 | downloadURLs(urls.map { URL(string: $0)!}) { results in 75 | actualResults = results 76 | downloadsCompleted.fulfill() 77 | } 78 | wait(for: [downloadsCompleted], timeout: timeout) 79 | 80 | return actualResults 81 | } 82 | 83 | private func performDownload(url: String, timeout: TimeInterval = 15.0) -> DownloadResult? { 84 | var actualResult: DownloadResult? 85 | 86 | let downloadCompleted = expectation(description: "download: \(url)") 87 | downloadURL(URL(string: url)!) { result in 88 | actualResult = result 89 | downloadCompleted.fulfill() 90 | } 91 | wait(for: [downloadCompleted], timeout: timeout) 92 | 93 | return actualResult 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Tests/FavIconTests/FavIconTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import XCTest 19 | @testable import FavIcon 20 | 21 | import Foundation 22 | 23 | class FavIconTests: XCTestCase { 24 | func testScan() { 25 | let url = "https://apple.com" 26 | var actualIcons: [Icon]! 27 | 28 | let completed = expectation(description: "scan: \(url)") 29 | do { 30 | try FavIcon.scan(url) { icons in 31 | actualIcons = icons 32 | completed.fulfill() 33 | } 34 | } catch let error { 35 | XCTFail("failed to scan for icons: \(error)") 36 | } 37 | wait(for: [completed], timeout: 15) 38 | 39 | XCTAssertNotNil(actualIcons) 40 | XCTAssertEqual(2, actualIcons.count) 41 | XCTAssertEqual(URL(string: "https://apple.com/favicon.ico")!, actualIcons[0].url) 42 | } 43 | 44 | func testIssue24_LowResIcons() { 45 | let url = "https://www.facebook.com" 46 | var actualResult: IconDownloadResult! 47 | 48 | let completed = expectation(description: "download: \(url)") 49 | do { 50 | try FavIcon.downloadPreferred(url) { result in 51 | actualResult = result 52 | completed.fulfill() 53 | } 54 | } catch let error { 55 | XCTFail("failed to download icons: \(error)") 56 | } 57 | wait(for: [completed], timeout: 15) 58 | 59 | XCTAssertNotNil(actualResult) 60 | 61 | switch actualResult! { 62 | case .success(let image): 63 | XCTAssertEqual(325.0, image.size.width) 64 | XCTAssertEqual(325.0, image.size.height) 65 | break 66 | case .failure(let error): 67 | XCTFail("unexpected error returned for download: \(error)") 68 | break 69 | } 70 | } 71 | 72 | func testDownloading() { 73 | let url = "https://apple.com" 74 | var actualResults: [IconDownloadResult]! 75 | 76 | let completed = expectation(description: "download: \(url)") 77 | do { 78 | try FavIcon.downloadAll(url) { results in 79 | actualResults = results 80 | completed.fulfill() 81 | } 82 | } catch let error { 83 | XCTFail("failed to download icons: \(error)") 84 | } 85 | wait(for: [completed], timeout: 15) 86 | 87 | XCTAssertEqual(2, actualResults.count) 88 | 89 | switch actualResults[0] { 90 | case .success(let image): 91 | XCTAssertEqual(1200, image.size.width) 92 | XCTAssertEqual(630, image.size.height) 93 | break 94 | case .failure(let error): 95 | XCTFail("unexpected error returned for download: \(error)") 96 | break 97 | } 98 | } 99 | 100 | func testChooseIcon() { 101 | let mixedIcons = [ 102 | Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .shortcut, width: 32, height: 32), 103 | Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .classic, width: 64, height: 64), 104 | Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .appleIOSWebClip, width: 64, height: 64), 105 | Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .appleOSXSafariTab, width: 144, height: 144), 106 | Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .classic) 107 | ] 108 | let noSizeIcons = [ 109 | Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .classic), 110 | Icon(url: URL(string: "https://google.com/favicon.ico")!, type: .shortcut) 111 | ] 112 | 113 | var sortedIcons = FavIcon.sortIcons(mixedIcons, preferredWidth: 50, preferredHeight: 50) 114 | var icon = sortedIcons[0] 115 | 116 | XCTAssertNotNil(icon) 117 | XCTAssertEqual(64, icon.width) 118 | XCTAssertEqual(64, icon.height) 119 | 120 | sortedIcons = FavIcon.sortIcons(mixedIcons, preferredWidth: 28, preferredHeight: 28) 121 | icon = sortedIcons[0] 122 | 123 | XCTAssertNotNil(icon) 124 | XCTAssertEqual(32, icon.width) 125 | XCTAssertEqual(32, icon.height) 126 | 127 | sortedIcons = FavIcon.sortIcons(mixedIcons) 128 | icon = sortedIcons[0] 129 | 130 | XCTAssertNotNil(icon) 131 | XCTAssertEqual(144, icon.width) 132 | XCTAssertEqual(144, icon.height) 133 | 134 | sortedIcons = FavIcon.sortIcons(noSizeIcons) 135 | icon = sortedIcons[0] 136 | 137 | XCTAssertNotNil(icon) 138 | XCTAssertEqual(IconType.shortcut.rawValue, icon.type.rawValue) 139 | 140 | sortedIcons = FavIcon.sortIcons([]) 141 | 142 | XCTAssertEqual(0, sortedIcons.count) 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /Tests/FavIconTests/HTML.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Tests/FavIconTests/HTMLTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FavIcon 3 | // Copyright © 2020 Leon Breedt 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import Foundation 19 | import XCTest 20 | @testable import FavIcon 21 | 22 | class HTMLDocumentTests: XCTestCase { 23 | func testHTMLFragment() { 24 | let document = HTMLDocument(string: "") 25 | let elements = document.query(xpath: "/html") 26 | 27 | XCTAssertEqual(1, elements.count) 28 | XCTAssertEqual("html", elements[0].name) 29 | XCTAssertEqual(0, elements[0].children.count) 30 | } 31 | 32 | func testHTMLElementAttributes() { 33 | let document = HTMLDocument(string: "") 34 | 35 | XCTAssertEqual(1, document.children.count) 36 | 37 | let html = document.children[0] 38 | 39 | XCTAssertEqual(1, html.attributes.count) 40 | XCTAssertEqual("en-us", html.attributes["lang"]) 41 | } 42 | 43 | func testHTMLElementsShouldNotBeCreatedMultipleTimes() { 44 | let document = HTMLDocument(string: "") 45 | 46 | let children1 = document.children 47 | let children2 = document.children 48 | 49 | XCTAssertTrue(children1[0] === children2[0]) 50 | } 51 | 52 | func testHTMLElementWithChildren() { 53 | let document = HTMLDocument(string: "

some text

") 54 | let elements = document.query(xpath: "/html/body") 55 | 56 | XCTAssertEqual(1, elements.count) 57 | XCTAssertEqual("body", elements[0].name) 58 | XCTAssertEqual(1, elements[0].children.count) 59 | XCTAssertEqual("p", elements[0].children[0].name) 60 | } 61 | 62 | func testMalformedHTML() { 63 | let document = HTMLDocument(string: "

some text

") 64 | let elements = document.query(xpath: "//body") 65 | 66 | XCTAssertEqual(1, elements.count) 67 | XCTAssertEqual("body", elements[0].name) 68 | XCTAssertEqual(1, elements[0].children.count) 69 | XCTAssertEqual("p", elements[0].children[0].name) 70 | } 71 | 72 | func testGoogleHTML() { 73 | // swiftlint:disable line_length 74 | let document = HTMLDocument(string: "") 75 | // swiftlint:enable line_length 76 | let links = document.query(xpath: "/html/head/link") 77 | 78 | XCTAssertEqual(1, links.count) 79 | XCTAssertEqual("link", links[0].name) 80 | XCTAssertEqual(2, links[0].attributes.count) 81 | XCTAssertEqual("/images/branding/product/ico/googleg_lodp.ico", links[0].attributes["href"]) 82 | XCTAssertEqual("shortcut icon", links[0].attributes["rel"]) 83 | } 84 | 85 | func testIssue20_EmptyHTML() { 86 | let document = HTMLDocument(string: "") 87 | 88 | XCTAssertEqual(0, document.children.count) 89 | XCTAssertEqual(0, document.query(xpath: "/html").count) 90 | XCTAssertEqual(0, document.query(xpath: "").count) 91 | } 92 | 93 | func testIssue23_InvalidHTMLDoesNotCrash() { 94 | let document = HTMLDocument(string: " html") 95 | 96 | XCTAssertEqual(1, document.children.count) 97 | XCTAssertEqual(1, document.query(xpath: "/html").count) 98 | XCTAssertEqual(0, document.query(xpath: "").count) 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /Tests/FavIconTests/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Web Application Manifest Sample", 3 | "icons": [ 4 | { 5 | "src": "launcher-icon-0-75x.png", 6 | "sizes": "36x36", 7 | "type": "image/png", 8 | "density": 0.75 9 | }, 10 | { 11 | "src": "launcher-icon-1x.png", 12 | "sizes": "48x48", 13 | "type": "image/png", 14 | "density": 1.0 15 | }, 16 | { 17 | "src": "launcher-icon-1-5x.png", 18 | "sizes": "72x72", 19 | "type": "image/png", 20 | "density": 1.5 21 | }, 22 | { 23 | "src": "launcher-icon-2x.png", 24 | "sizes": "96x96", 25 | "type": "image/png", 26 | "density": 2.0 27 | }, 28 | { 29 | "src": "launcher-icon-3x.png", 30 | "sizes": "144x144", 31 | "type": "image/png", 32 | "density": 3.0 33 | }, 34 | { 35 | "src": "launcher-icon-4x.png", 36 | "sizes": "192x192", 37 | "type": "image/png", 38 | "density": 4.0 39 | } 40 | ], 41 | "start_url": "index.html", 42 | "display": "standalone", 43 | "orientation": "landscape" 44 | } -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | FavIconTests 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | --------------------------------------------------------------------------------