From 503ccd5d37531dc1ae21e46ef008c8c9ce2992da Mon Sep 17 00:00:00 2001 From: Leonardo Cardoso Date: Thu, 23 Sep 2021 18:46:01 +0200 Subject: [PATCH 1/7] Bump version to 3.5.0 --- CHANGELOG.md | 8 ++++++++ Example/SwiftLinkPreviewExample/Info.plist | 2 +- README.md | 8 ++++---- Sources/Info-macOS.plist | 2 +- Sources/Info-tvOS.plist | 2 +- Sources/Info-watchOS.plist | 2 +- Sources/Info.plist | 2 +- SwiftLinkPreview.podspec | 4 ++-- SwiftLinkPreviewTests/Info-macOS.plist | 2 +- SwiftLinkPreviewTests/Info-tvOS.plist | 2 +- SwiftLinkPreviewTests/Info.plist | 2 +- 11 files changed, 22 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1319ad..c93b285 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log #### 3.x Releases +- `3.5.x` Releases - [3.5.0](#350) - `3.4.x` Releases - [3.4.0](#340) - `3.3.x` Releases - [3.3.0](#330) - `3.2.x` Releases - [3.2.0](#320) @@ -22,6 +23,13 @@ --- +## [3.5.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/3.5.0) + +#### Changed +- Update README.md [#150](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/150) + - Changed by [benlmyers](https://github.com/benlmyers) + + ## [3.4.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/3.4.0) #### Added diff --git a/Example/SwiftLinkPreviewExample/Info.plist b/Example/SwiftLinkPreviewExample/Info.plist index f89431e..209ba21 100644 --- a/Example/SwiftLinkPreviewExample/Info.plist +++ b/Example/SwiftLinkPreviewExample/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 3.4.0 + 3.5.0 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/README.md b/README.md index 527553d..e5f1435 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ > It makes a preview from an URL, grabbing all the information such as title, relevant texts and images. [![Platform](https://img.shields.io/badge/platform-iOS%20|%20macOS%20|%20watchOS%20|%20tvOS-orange.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#requirements-and-details) -[![CocoaPods](https://img.shields.io/badge/pod-v3.4.0-red.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#cocoapods) +[![CocoaPods](https://img.shields.io/badge/pod-v3.5.0-red.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#cocoapods) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#carthage) [![Swift Package Manager](https://img.shields.io/badge/SPM-compatible-orange.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#swift-package-manager) [![Build Status](https://travis-ci.org/LeonardoCardoso/SwiftLinkPreview.svg?branch=master)](https://travis-ci.org/LeonardoCardoso/SwiftLinkPreview) @@ -59,7 +59,7 @@ To use **SwiftLinkPreview** as a pod package just add the following in your **Po target 'Your Target Name' do use_frameworks! // ... - pod 'SwiftLinkPreview', '~> 3.4.0' + pod 'SwiftLinkPreview', '~> 3.5.0' // ... end ``` @@ -70,7 +70,7 @@ To use **SwiftLinkPreview** as a Carthage module package just add the following ```ruby // ... - github "LeonardoCardoso/SwiftLinkPreview" ~> 3.4.0 + github "LeonardoCardoso/SwiftLinkPreview" ~> 3.5.0 // ... ``` @@ -85,7 +85,7 @@ let package = Package( name: "Your Target Name", dependencies: [ // ... - .Package(url: "https://github.com/LeonardoCardoso/SwiftLinkPreview.git", "3.4.0") + .Package(url: "https://github.com/LeonardoCardoso/SwiftLinkPreview.git", "3.5.0") // ... ] ) diff --git a/Sources/Info-macOS.plist b/Sources/Info-macOS.plist index 648e64f..311f45e 100644 --- a/Sources/Info-macOS.plist +++ b/Sources/Info-macOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.4.0 + 3.5.0 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Info-tvOS.plist b/Sources/Info-tvOS.plist index a8f98d8..2bf762f 100644 --- a/Sources/Info-tvOS.plist +++ b/Sources/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.4.0 + 3.5.0 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Info-watchOS.plist b/Sources/Info-watchOS.plist index a8f98d8..2bf762f 100644 --- a/Sources/Info-watchOS.plist +++ b/Sources/Info-watchOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.4.0 + 3.5.0 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Info.plist b/Sources/Info.plist index a8f98d8..2bf762f 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.4.0 + 3.5.0 CFBundleSignature ???? CFBundleVersion diff --git a/SwiftLinkPreview.podspec b/SwiftLinkPreview.podspec index 1323514..2a9855d 100755 --- a/SwiftLinkPreview.podspec +++ b/SwiftLinkPreview.podspec @@ -7,12 +7,12 @@ Pod::Spec.new do |s| s.name = "SwiftLinkPreview" s.summary = "It makes a preview from an url, grabbing all the information such as title, relevant texts and images." s.requires_arc = true - s.version = "3.4.0" + s.version = "3.5.0" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Leonardo Cardoso" => "contact@leocardz.com" } s.homepage = "https://github.com/LeonardoCardoso/SwiftLinkPreview" s.source = { :git => "https://github.com/LeonardoCardoso/SwiftLinkPreview.git", :tag => s.version } s.source_files = "Sources/**/*.swift" - s.swift_version = '4.2' + s.swift_version = '5' end diff --git a/SwiftLinkPreviewTests/Info-macOS.plist b/SwiftLinkPreviewTests/Info-macOS.plist index 6521e7a..589caa7 100644 --- a/SwiftLinkPreviewTests/Info-macOS.plist +++ b/SwiftLinkPreviewTests/Info-macOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 3.4.0 + 3.5.0 CFBundleSignature ???? CFBundleVersion diff --git a/SwiftLinkPreviewTests/Info-tvOS.plist b/SwiftLinkPreviewTests/Info-tvOS.plist index 6521e7a..589caa7 100644 --- a/SwiftLinkPreviewTests/Info-tvOS.plist +++ b/SwiftLinkPreviewTests/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 3.4.0 + 3.5.0 CFBundleSignature ???? CFBundleVersion diff --git a/SwiftLinkPreviewTests/Info.plist b/SwiftLinkPreviewTests/Info.plist index 6521e7a..589caa7 100644 --- a/SwiftLinkPreviewTests/Info.plist +++ b/SwiftLinkPreviewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 3.4.0 + 3.5.0 CFBundleSignature ???? CFBundleVersion From 53b1116b38fea183e90b0e660a80431e7596106a Mon Sep 17 00:00:00 2001 From: Leonardo Cardoso Date: Thu, 23 Sep 2021 19:14:13 +0200 Subject: [PATCH 2/7] Update example --- Example/SwiftLinkPreviewExample.xcodeproj/project.pbxproj | 7 +++---- .../xcschemes/SwiftLinkPreviewExample.xcscheme | 2 +- SwiftLinkPreview.xcodeproj/project.pbxproj | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Example/SwiftLinkPreviewExample.xcodeproj/project.pbxproj b/Example/SwiftLinkPreviewExample.xcodeproj/project.pbxproj index 9a0b079..2c28ba3 100644 --- a/Example/SwiftLinkPreviewExample.xcodeproj/project.pbxproj +++ b/Example/SwiftLinkPreviewExample.xcodeproj/project.pbxproj @@ -164,10 +164,9 @@ }; buildConfigurationList = 98846C721D09AA1B00846726 /* Build configuration list for PBXProject "SwiftLinkPreviewExample" */; compatibilityVersion = "Xcode 8.0"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); @@ -334,7 +333,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -386,7 +385,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; diff --git a/Example/SwiftLinkPreviewExample.xcodeproj/xcshareddata/xcschemes/SwiftLinkPreviewExample.xcscheme b/Example/SwiftLinkPreviewExample.xcodeproj/xcshareddata/xcschemes/SwiftLinkPreviewExample.xcscheme index 6dcaab6..cfc9d11 100644 --- a/Example/SwiftLinkPreviewExample.xcodeproj/xcshareddata/xcschemes/SwiftLinkPreviewExample.xcscheme +++ b/Example/SwiftLinkPreviewExample.xcodeproj/xcshareddata/xcschemes/SwiftLinkPreviewExample.xcscheme @@ -1,6 +1,6 @@ Date: Sat, 31 Jul 2021 13:33:48 +0700 Subject: [PATCH 3/7] Merge pull request #1 from kinhvodoi92/kinhvodoi92-fixRegexLimit update Regex.swift. Upgrade regex limit length --- CHANGELOG.md | 4 +++- Sources/Regex.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c93b285..9e6efc6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,10 @@ ## [3.5.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/3.5.0) #### Changed -- Update README.md [#150](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/150) +- Updated README.md [#150](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/150) - Changed by [benlmyers](https://github.com/benlmyers) +- Updated regex limit [#148](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/148) + - Changed by [kinhvodoi92](https://github.com/kinhvodoi92) ## [3.4.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/3.4.0) diff --git a/Sources/Regex.swift b/Sources/Regex.swift index c4e08dc..843db2b 100644 --- a/Sources/Regex.swift +++ b/Sources/Regex.swift @@ -71,7 +71,7 @@ class Regex { var matches: [NSTextCheckingResult] = [] - let limit = 300000 + let limit = 1000000 if string.count > limit { string.split(by: limit).forEach { From 5ade1dec7bd5b86ef98e50e19945f37df200d42c Mon Sep 17 00:00:00 2001 From: Leonardo Cardoso Date: Thu, 23 Sep 2021 19:30:03 +0200 Subject: [PATCH 4/7] Annotate cancel an @objc method #135 --- CHANGELOG.md | 2 ++ Sources/SwiftLinkPreview.swift | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e6efc6..f0699d7 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ - Changed by [benlmyers](https://github.com/benlmyers) - Updated regex limit [#148](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/148) - Changed by [kinhvodoi92](https://github.com/kinhvodoi92) +- Annotated `Cancelable.cancel()` as `@objc` to make it compatibale with Objective-C [#135](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/135) + - Changed by [LeonardoCardoso](https://github.com/LeonardoCardoso) ## [3.4.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/3.4.0) diff --git a/Sources/SwiftLinkPreview.swift b/Sources/SwiftLinkPreview.swift index 58b4d86..86f2f36 100644 --- a/Sources/SwiftLinkPreview.swift +++ b/Sources/SwiftLinkPreview.swift @@ -23,7 +23,7 @@ public enum SwiftLinkResponseKey: String { open class Cancellable: NSObject { public private(set) var isCancelled: Bool = false - open func cancel() { + @objc open func cancel() { isCancelled = true } } From 415b3a9b731315b3d43e10cdc8b706d98f966a6e Mon Sep 17 00:00:00 2001 From: Leonardo Cardoso Date: Thu, 23 Sep 2021 20:28:49 +0200 Subject: [PATCH 5/7] Capture baseURL --- CHANGELOG.md | 8 ++- .../Controllers/ViewController.swift | 4 +- README.md | 21 +++---- Sources/Regex.swift | 7 ++- Sources/Response.swift | 3 +- Sources/ResponseExtension.swift | 6 ++ Sources/SwiftLinkPreview.swift | 55 ++++++++++++++++--- SwiftLinkPreview.xcodeproj/project.pbxproj | 44 ++++++++++----- SwiftLinkPreviewTests/BaseURLTests.swift | 55 +++++++++++++++++++ SwiftLinkPreviewTests/Constants.swift | 1 + SwiftLinkPreviewTests/head-meta-base.html | 10 ++++ SwiftLinkPreviewTests/head-meta-facebook.html | 2 +- SwiftLinkPreviewTests/head-meta-meta.html | 2 +- 13 files changed, 176 insertions(+), 42 deletions(-) create mode 100644 SwiftLinkPreviewTests/BaseURLTests.swift create mode 100644 SwiftLinkPreviewTests/head-meta-base.html diff --git a/CHANGELOG.md b/CHANGELOG.md index f0699d7..d6e4e3e 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,13 +25,17 @@ ## [3.5.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/3.5.0) +#### Added +- Annotated `Cancelable.cancel()` as `@objc` to make it compatibale with Objective-C [#135](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/135) +- Added removal of duplicates from the images array [#45](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/45) +- Added capture of base URL [#45](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/45) + - Changed by [LeonardoCardoso](https://github.com/LeonardoCardoso) + #### Changed - Updated README.md [#150](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/150) - Changed by [benlmyers](https://github.com/benlmyers) - Updated regex limit [#148](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/148) - Changed by [kinhvodoi92](https://github.com/kinhvodoi92) -- Annotated `Cancelable.cancel()` as `@objc` to make it compatibale with Objective-C [#135](https://github.com/LeonardoCardoso/SwiftLinkPreview/issues/135) - - Changed by [LeonardoCardoso](https://github.com/LeonardoCardoso) ## [3.4.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/3.4.0) diff --git a/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift b/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift index 3bb2d28..9339003 100644 --- a/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift +++ b/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift @@ -38,8 +38,9 @@ class ViewController: UIViewController { "www.youtube.com", "www.google.com", "facebook.com", - + "https://github.com/LeonardoCardoso/SwiftLinkPreview", + "https://www.jbhifi.com.au/products/playstation-4-biomutant", "https://leocardz.com/swift-link-preview-5a9860c7756f", "NASA! 🖖🏽 https://www.nasa.gov/", @@ -268,6 +269,7 @@ class ViewController: UIViewController { print("video: ", result.video ?? "no video") print("icon: ", result.icon ?? "no icon") print("description: ", result.description ?? "no description") + print("baseURL: ", result.baseURL ?? "no baseURL") } guard textField?.text?.isEmpty == false else { diff --git a/README.md b/README.md index e5f1435..c2e23ca 100644 --- a/README.md +++ b/README.md @@ -121,16 +121,17 @@ let preview = slp.preview("Text containing URL", ```swift Response { - let url: URL // URL - let finalUrl: URL // unshortened URL - let canonicalUrl: String // canonical URL - let title: String // title - let description: String // page description or relevant text - let images: [String] // array of URLs of the images - let image: String // main image - let icon: String // favicon - let video: String // video - let price: String // price + let baseURL: String? // base + let url: URL? // URL + let finalUrl: URL? // unshortened URL + let canonicalUrl: String? // canonical URL + let title: String? // title + let description: String? // page description or relevant text + let images: [String]? // array of URLs of the images + let image: String? // main image + let icon: String? // favicon + let video: String? // video + let price: String? // price } ``` diff --git a/Sources/Regex.swift b/Sources/Regex.swift index 843db2b..3e44d8b 100644 --- a/Sources/Regex.swift +++ b/Sources/Regex.swift @@ -17,8 +17,9 @@ class Regex { static let imageTagPattern = "" static let secondaryImageTagPattern = "og:image\"(.+?)content=\"([^\"](.+?))\"(.+?)[/]?>" static let titlePattern = "(.*?)" - static let metatagPattern = "" - static let metatagContentPattern = "content=(\"(.*?)\")|('(.*?)')" + static let metaTagPattern = "" + static let baseTagPattern = "" + static let metaTagContentPattern = "content=(\"(.*?)\")|('(.*?)')" static let cannonicalUrlPattern = "([^\\+&#@%\\?=~_\\|!:,;]+)" static let rawTagPattern = "<[^>]+>" static let inlineStylePattern = "(.*?)" @@ -71,7 +72,7 @@ class Regex { var matches: [NSTextCheckingResult] = [] - let limit = 1000000 + let limit = 300000 if string.count > limit { string.split(by: limit).forEach { diff --git a/Sources/Response.swift b/Sources/Response.swift index d20f657..c3ef506 100644 --- a/Sources/Response.swift +++ b/Sources/Response.swift @@ -9,7 +9,8 @@ import Foundation public struct Response { - + + public internal(set) var baseURL: String? public internal(set) var url: URL? public internal(set) var finalUrl: URL? public internal(set) var canonicalUrl: String? diff --git a/Sources/ResponseExtension.swift b/Sources/ResponseExtension.swift index d3dd9b3..169748b 100644 --- a/Sources/ResponseExtension.swift +++ b/Sources/ResponseExtension.swift @@ -12,6 +12,7 @@ internal extension Response { var dictionary: [String: Any] { var responseData:[String: Any] = [:] + responseData["baseURL"] = baseURL responseData["url"] = url responseData["finalUrl"] = finalUrl responseData["canonicalUrl"] = canonicalUrl @@ -35,11 +36,14 @@ internal extension Response { case images case icon case video + case baseURL case price } mutating func set(_ value: Any, for key: Key) { switch key { + case Key.baseURL: + if let value = value as? String { self.baseURL = value } case Key.url: if let value = value as? URL { self.url = value } case Key.finalUrl: @@ -65,6 +69,8 @@ internal extension Response { func value(for key: Key) -> Any? { switch key { + case Key.baseURL: + return self.baseURL case Key.url: return self.url case Key.finalUrl: diff --git a/Sources/SwiftLinkPreview.swift b/Sources/SwiftLinkPreview.swift index 86f2f36..308f5ee 100644 --- a/Sources/SwiftLinkPreview.swift +++ b/Sources/SwiftLinkPreview.swift @@ -127,15 +127,17 @@ open class SwiftLinkPreview: NSObject { result.url = url result.finalUrl = self.extractInURLRedirectionIfNeeded(unshortened) result.canonicalUrl = self.extractCanonicalURL(unshortened) + result.baseURL = result.baseURL ?? (result.canonicalUrl?.starts(with: "http") == false ? "https://\(result.canonicalUrl!)" : result.canonicalUrl) self.extractInfo(response: result, cancellable: cancellable, completion: { result.title = $0.title result.description = $0.description - result.image = $0.image - result.images = $0.images - result.icon = $0.icon - result.video = $0.video + + result.image = self.formatImageURL($0.image, base: $0.baseURL) + result.images = self.formatImageURLs($0.images, base: $0.baseURL) + result.icon = self.formatImageURL($0.icon, base: $0.baseURL) + result.video = self.formatImageURL($0.video, base: $0.baseURL) result.price = $0.price self.cache.slp_setCachedResponse(url: unshortened.absoluteString, response: result) @@ -154,6 +156,28 @@ open class SwiftLinkPreview: NSObject { return cancellable } + private func formatImageURL(_ url: String?, base: String?) -> String? { + guard var url = url else { return nil } + + if !url.starts(with: "http"), let base = base { + url = "\(base)\(url)" + } + + return url + } + + func formatImageURLs(_ array: [String]?, base: String?) -> [String]? { + guard var array = array else { return nil } + + for i in 0 ..< array.count { + if let formatted = formatImageURL(array[0], base: base) { + array[i] = formatted + } + } + + return Array(Set(array)) + } + /* Extract url redirection inside the GET query. Like https://www.dji.com/404?url=http%3A%2F%2Fwww.dji.com%2Fmatrice600-pro%2Finfo#specs -> http://www.dji.com/de/matrice600-pro/info#specs @@ -287,9 +311,9 @@ extension SwiftLinkPreview { CFStringConvertIANACharSetNameToEncoding( $0 as CFString ) ) ) } ?? .utf8 if let html = String( data: data, encoding: encoding ) { - for meta in Regex.pregMatchAll( html, regex: Regex.metatagPattern, index: 1 ) { + for meta in Regex.pregMatchAll( html, regex: Regex.metaTagPattern, index: 1 ) { if (meta.contains( "http-equiv=\"refresh\"" ) || meta.contains( "http-equiv='refresh'" )), - let value = Regex.pregMatchFirst( meta, regex: Regex.metatagContentPattern, index: 2 )?.decoded.extendedTrim, + let value = Regex.pregMatchFirst( meta, regex: Regex.metaTagContentPattern, index: 2 )?.decoded.extendedTrim, let redirectString = value.split( separator: ";" ) .first( where: { $0.lowercased().starts( with: "url=" ) } )? .split( separator: "=", maxSplits: 1 ).last, @@ -444,6 +468,8 @@ extension SwiftLinkPreview { result = self.crawlMetaTags(sanitizedHtmlCode, result: result) + result = self.crawlMetaBase(sanitizedHtmlCode, result: result) + var otherResponse = self.crawlTitle(sanitizedHtmlCode, result: result) otherResponse = self.crawlDescription(otherResponse.htmlCode, result: otherResponse.result) @@ -534,10 +560,10 @@ extension SwiftLinkPreview { Response.Key.title.rawValue, Response.Key.description.rawValue, Response.Key.image.rawValue, - Response.Key.video.rawValue, + Response.Key.video.rawValue ] - let metatags = Regex.pregMatchAll(htmlCode, regex: Regex.metatagPattern, index: 1) + let metatags = Regex.pregMatchAll(htmlCode, regex: Regex.metaTagPattern, index: 1) for metatag in metatags { for tag in possibleTags { @@ -552,7 +578,7 @@ extension SwiftLinkPreview { if let key = Response.Key(rawValue: tag), result.value(for: key) == nil { - if let value = Regex.pregMatchFirst(metatag, regex: Regex.metatagContentPattern, index: 2) { + if let value = Regex.pregMatchFirst(metatag, regex: Regex.metaTagContentPattern, index: 2) { let value = value.decoded.extendedTrim if tag == "image" { let value = addImagePrefixIfNeeded(value, result: result) @@ -572,6 +598,17 @@ extension SwiftLinkPreview { return result } + internal func crawlMetaBase(_ htmlCode: String, result: Response) -> Response { + + var result = result + + if let base = Regex.pregMatchAll(htmlCode, regex: Regex.baseTagPattern, index: 2).first { + result.set(base, for: .baseURL) + } + + return result + } + // Crawl for title if needed internal func crawlTitle(_ htmlCode: String, result: Response) -> (htmlCode: String, result: Response) { var result = result diff --git a/SwiftLinkPreview.xcodeproj/project.pbxproj b/SwiftLinkPreview.xcodeproj/project.pbxproj index 403e874..3ee57ac 100644 --- a/SwiftLinkPreview.xcodeproj/project.pbxproj +++ b/SwiftLinkPreview.xcodeproj/project.pbxproj @@ -8,6 +8,12 @@ /* Begin PBXBuildFile section */ 1F8164ED26287866000F2905 /* VideoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F8164EC26287866000F2905 /* VideoTests.swift */; }; + 27BCC85826FCF22E00886BDA /* BaseURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BCC85726FCF22E00886BDA /* BaseURLTests.swift */; }; + 27BCC85D26FCF3BF00886BDA /* BaseURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BCC85726FCF22E00886BDA /* BaseURLTests.swift */; }; + 27BCC85E26FCF3C000886BDA /* BaseURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BCC85726FCF22E00886BDA /* BaseURLTests.swift */; }; + 27BCC86026FCF4C000886BDA /* head-meta-base.html in Resources */ = {isa = PBXBuildFile; fileRef = 27BCC85F26FCF4C000886BDA /* head-meta-base.html */; }; + 27BCC86126FCF4C000886BDA /* head-meta-base.html in Resources */ = {isa = PBXBuildFile; fileRef = 27BCC85F26FCF4C000886BDA /* head-meta-base.html */; }; + 27BCC86226FCF4C000886BDA /* head-meta-base.html in Resources */ = {isa = PBXBuildFile; fileRef = 27BCC85F26FCF4C000886BDA /* head-meta-base.html */; }; 68074FFA1F23B6C900649DE6 /* head-meta-icon.html in Resources */ = {isa = PBXBuildFile; fileRef = 68074FF91F23B6C900649DE6 /* head-meta-icon.html */; }; 68074FFB1F23BB1100649DE6 /* head-meta-icon.html in Resources */ = {isa = PBXBuildFile; fileRef = 68074FF91F23B6C900649DE6 /* head-meta-icon.html */; }; 68074FFC1F23BB1400649DE6 /* head-meta-icon.html in Resources */ = {isa = PBXBuildFile; fileRef = 68074FF91F23B6C900649DE6 /* head-meta-icon.html */; }; @@ -150,6 +156,8 @@ /* Begin PBXFileReference section */ 1F8164EC26287866000F2905 /* VideoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoTests.swift; sourceTree = ""; }; + 27BCC85726FCF22E00886BDA /* BaseURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseURLTests.swift; sourceTree = ""; }; + 27BCC85F26FCF4C000886BDA /* head-meta-base.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "head-meta-base.html"; sourceTree = ""; }; 68074FF91F23B6C900649DE6 /* head-meta-icon.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "head-meta-icon.html"; sourceTree = ""; }; 686E58DE1F22416D000C2A33 /* IconTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IconTests.swift; sourceTree = ""; }; 7A552DE121A460910019E8B1 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; @@ -257,17 +265,18 @@ 985DCEB01D2BFD2700B40D76 /* Files */ = { isa = PBXGroup; children = ( - 98B5ED421D3E5F5C00AEBD54 /* head-meta-itemprop.html */, - 986D5BE61D33E0FD0025555F /* head-title.html */, - 985DCEBB1D2BFFAF00B40D76 /* body-text-span.html */, - 985DCEBC1D2BFFAF00B40D76 /* body-text-p.html */, - 985DCEBD1D2BFFAF00B40D76 /* body-text-div.html */, - 985DCEB71D2BFE4100B40D76 /* body-image-single.html */, 985DCEB81D2BFE4100B40D76 /* body-image-gallery.html */, - 985DCEB11D2BFD3400B40D76 /* head-meta-twitter.html */, - 985DCEB21D2BFD3400B40D76 /* head-meta-meta.html */, + 985DCEB71D2BFE4100B40D76 /* body-image-single.html */, + 985DCEBD1D2BFFAF00B40D76 /* body-text-div.html */, + 985DCEBC1D2BFFAF00B40D76 /* body-text-p.html */, + 985DCEBB1D2BFFAF00B40D76 /* body-text-span.html */, + 27BCC85F26FCF4C000886BDA /* head-meta-base.html */, 985DCEB31D2BFD3400B40D76 /* head-meta-facebook.html */, 68074FF91F23B6C900649DE6 /* head-meta-icon.html */, + 98B5ED421D3E5F5C00AEBD54 /* head-meta-itemprop.html */, + 985DCEB21D2BFD3400B40D76 /* head-meta-meta.html */, + 985DCEB11D2BFD3400B40D76 /* head-meta-twitter.html */, + 986D5BE61D33E0FD0025555F /* head-title.html */, ); name = Files; sourceTree = ""; @@ -358,18 +367,19 @@ 98DC53391D1D73DB001134E3 /* SwiftLinkPreviewTests */ = { isa = PBXGroup; children = ( - 98B5ED491D3E7DC600AEBD54 /* HugeTests.swift */, + 27BCC85726FCF22E00886BDA /* BaseURLTests.swift */, 985DCEC81D2C029700B40D76 /* BodyTests.swift */, + 988B48D61D2C3C2E0040A4AD /* Constants */, + 985DCEB01D2BFD2700B40D76 /* Files */, + 98B5ED491D3E7DC600AEBD54 /* HugeTests.swift */, + 686E58DE1F22416D000C2A33 /* IconTests.swift */, 985DCEC61D2C026000B40D76 /* ImageTests.swift */, - 1F8164EC26287866000F2905 /* VideoTests.swift */, + 98E7C3121D3B23F5009E5F6D /* Info */, 985DCEC41D2C022E00B40D76 /* MetaTests.swift */, 982812911D3A9293000D3ABB /* RegexTests.swift */, 986D5BE41D33DFE50025555F /* TitleTests.swift */, - 686E58DE1F22416D000C2A33 /* IconTests.swift */, - 988B48D61D2C3C2E0040A4AD /* Constants */, - 985DCEB01D2BFD2700B40D76 /* Files */, - 98E7C3121D3B23F5009E5F6D /* Info */, 988B48D11D2C39790040A4AD /* Utils */, + 1F8164EC26287866000F2905 /* VideoTests.swift */, ); path = SwiftLinkPreviewTests; sourceTree = ""; @@ -641,6 +651,7 @@ 985DCEBF1D2BFFAF00B40D76 /* body-text-span.html in Resources */, 985DCEC01D2BFFAF00B40D76 /* body-text-p.html in Resources */, 985DCEC11D2BFFAF00B40D76 /* body-text-div.html in Resources */, + 27BCC86026FCF4C000886BDA /* head-meta-base.html in Resources */, 985DCEB91D2BFE4100B40D76 /* body-image-single.html in Resources */, 985DCEB61D2BFD3400B40D76 /* head-meta-facebook.html in Resources */, 986D5BE71D33E0FD0025555F /* head-title.html in Resources */, @@ -666,6 +677,7 @@ 98E7C32F1D3B24DA009E5F6D /* body-image-single.html in Resources */, 98B5ED461D3E62A200AEBD54 /* head-meta-itemprop.html in Resources */, 98E7C3301D3B24DA009E5F6D /* body-image-gallery.html in Resources */, + 27BCC86226FCF4C000886BDA /* head-meta-base.html in Resources */, 98E7C3311D3B24DA009E5F6D /* head-meta-twitter.html in Resources */, 98E7C3321D3B24DA009E5F6D /* head-meta-meta.html in Resources */, 98E7C3331D3B24DA009E5F6D /* head-meta-facebook.html in Resources */, @@ -691,6 +703,7 @@ 98F76D1D1D3AF87100E9B10E /* body-image-single.html in Resources */, 98B5ED441D3E62A000AEBD54 /* head-meta-itemprop.html in Resources */, 98F76D1E1D3AF87100E9B10E /* body-image-gallery.html in Resources */, + 27BCC86126FCF4C000886BDA /* head-meta-base.html in Resources */, 98F76D1F1D3AF87100E9B10E /* head-meta-twitter.html in Resources */, 98F76D201D3AF87100E9B10E /* head-meta-meta.html in Resources */, 98F76D211D3AF87100E9B10E /* head-meta-facebook.html in Resources */, @@ -740,6 +753,7 @@ 986D5BE51D33DFE60025555F /* TitleTests.swift in Sources */, 985DCEC71D2C026000B40D76 /* ImageTests.swift in Sources */, 988B48D81D2C3C3D0040A4AD /* Constants.swift in Sources */, + 27BCC85826FCF22E00886BDA /* BaseURLTests.swift in Sources */, 9272A10D1E2EF0E600F9F17E /* Regex.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -777,6 +791,7 @@ 98E7C3281D3B24C6009E5F6D /* File.swift in Sources */, 98E7C3291D3B24C6009E5F6D /* IntExtension.swift in Sources */, 9272A10F1E2EF0E800F9F17E /* Regex.swift in Sources */, + 27BCC85E26FCF3C000886BDA /* BaseURLTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -813,6 +828,7 @@ 98F76D121D3AF78600E9B10E /* File.swift in Sources */, 98F76D131D3AF78600E9B10E /* IntExtension.swift in Sources */, 9272A10E1E2EF0E700F9F17E /* Regex.swift in Sources */, + 27BCC85D26FCF3BF00886BDA /* BaseURLTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftLinkPreviewTests/BaseURLTests.swift b/SwiftLinkPreviewTests/BaseURLTests.swift new file mode 100644 index 0000000..33ee379 --- /dev/null +++ b/SwiftLinkPreviewTests/BaseURLTests.swift @@ -0,0 +1,55 @@ +// +// BaseURLTests.swift +// SwiftLinkPreviewTests +// +// Created by Leonardo Cardoso on 23.09.21. +// Copyright © 2021 leocardz.com. All rights reserved. +// + +import XCTest +@testable import SwiftLinkPreview + +// This class tests head meta info +class BaseURLTests: XCTestCase { + + // MARK: - Vars + var baseTemplate = "" + let slp = SwiftLinkPreview() + + // MARK: - SetUps + // Those setup functions get that template, and fulfil determinated areas with rand texts, images and tags + override func setUp() { + super.setUp() + + self.baseTemplate = File.toString(Constants.headMetaBase) + + } + + // MARK: - Base + func setUpBaseAndRun() { + + var baseTemplate = self.baseTemplate + baseTemplate = baseTemplate.replace(Constants.headRandom, with: String.randomTag()) + baseTemplate = baseTemplate.replace(Constants.bodyRandom, with: String.randomTag()).extendedTrim + + let result = self.slp.crawlMetaBase(baseTemplate, result: Response()) + + XCTAssertEqual(result.baseURL, "https://host/resource/index/") + } + + func testBase() { + + for _ in 0 ..< 100 { + + self.setUpBaseAndRun() + + } + + } + + func testResultBase() { + XCTAssertEqual(slp.formatImageURLs(["assets/test.png"], base: "https://host/resource/index/")?.first, + "https://host/resource/index/assets/test.png") + } + +} diff --git a/SwiftLinkPreviewTests/Constants.swift b/SwiftLinkPreviewTests/Constants.swift index db15838..e04a9fb 100644 --- a/SwiftLinkPreviewTests/Constants.swift +++ b/SwiftLinkPreviewTests/Constants.swift @@ -20,6 +20,7 @@ struct Constants { static let bodyIcon = "head-meta-icon" static let headMetaTwitter = "head-meta-twitter" static let headMetaMeta = "head-meta-meta" + static let headMetaBase = "head-meta-base" static let headMetaItemprop = "head-meta-itemprop" static let headMetaFacebook = "head-meta-facebook" static let headTitle = "head-title" diff --git a/SwiftLinkPreviewTests/head-meta-base.html b/SwiftLinkPreviewTests/head-meta-base.html new file mode 100644 index 0000000..7f66076 --- /dev/null +++ b/SwiftLinkPreviewTests/head-meta-base.html @@ -0,0 +1,10 @@ + + + [:head-random] + + + + [:body-random] + + + diff --git a/SwiftLinkPreviewTests/head-meta-facebook.html b/SwiftLinkPreviewTests/head-meta-facebook.html index 7d1b9d9..79e01b1 100644 --- a/SwiftLinkPreviewTests/head-meta-facebook.html +++ b/SwiftLinkPreviewTests/head-meta-facebook.html @@ -10,4 +10,4 @@ [:body-random] - \ No newline at end of file + diff --git a/SwiftLinkPreviewTests/head-meta-meta.html b/SwiftLinkPreviewTests/head-meta-meta.html index a6575a3..cb09599 100644 --- a/SwiftLinkPreviewTests/head-meta-meta.html +++ b/SwiftLinkPreviewTests/head-meta-meta.html @@ -10,4 +10,4 @@ [:body-random] - \ No newline at end of file + From 5cbefb762388eb84d88b89aff8568c0a969f5dae Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Mon, 6 Jun 2022 22:20:20 -0400 Subject: [PATCH 6/7] Fix composition of image URL and Base URL. When an image URL is not an absolute URL (ie. protocol://url), we want to put the base URL in front of it, if it is known. BUT, if neither the base URL ends in / or the image URL start with /, the current formatImageURL mashes them together without a slash, leading to something like https://base.comimages/image.jpg --- Sources/SwiftLinkPreview.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftLinkPreview.swift b/Sources/SwiftLinkPreview.swift index 308f5ee..4e735b8 100644 --- a/Sources/SwiftLinkPreview.swift +++ b/Sources/SwiftLinkPreview.swift @@ -157,13 +157,10 @@ open class SwiftLinkPreview: NSObject { } private func formatImageURL(_ url: String?, base: String?) -> String? { - guard var url = url else { return nil } + guard let base = base, let url = url, !url.contains( "://" ) + else { return url } - if !url.starts(with: "http"), let base = base { - url = "\(base)\(url)" - } - - return url + return base + (base.hasSuffix("/") || url.hasPrefix("/") ? "" : "/") + url } func formatImageURLs(_ array: [String]?, base: String?) -> [String]? { From f84740e01002e6806c03984b19bef0bb2d85e893 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Mon, 6 Jun 2022 22:26:40 -0400 Subject: [PATCH 7/7] Fix loss of session delegate & session invalidation control. SwiftLinkPreview's public API is one that asks the user for a URLSession. This API gives the user the freedom to fully configure their own URLSession (such as by setting a custom delegate) as well as giving them control over invalidating the session through finishTasksAndInvalidate() or invalidateAndCancel(). Internally, however, at some point SwiftLinkPreview re-creates the URLSession in order to set its own delegate. This is bad form because it belies the public API, resulting in behaviour that violates its own contract: now the URLSession that was given to it no longer honours the user's URLSessionDelegate and it ignores invalidation calls. Either SwiftLinkPreview should have a public API that *only* asks for a URLSessionConfiguration and then make its own URLSessions off that, or it should ask for a URLSession and then honour that contract without internally changing the actual session used. This solution does the latter. Note that SwiftLinkPreview's replacement delegate appears to be purely to ensure redirects are happening, but this is already the automatic behaviour for background-style URLSessions (which are probably the type you want to use with SwiftLinkPreview anyway). If the user wants redirects without a background-style URLSession, he should handle that in his own URLSession. Note that the behaviour of URLSession is 100% out-of-scope for SwiftLinkPreview, especially when SwiftLinkPreview is asking the user for what session to use. --- Sources/SwiftLinkPreview.swift | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Sources/SwiftLinkPreview.swift b/Sources/SwiftLinkPreview.swift index 4e735b8..a650f7f 100644 --- a/Sources/SwiftLinkPreview.swift +++ b/Sources/SwiftLinkPreview.swift @@ -86,10 +86,6 @@ open class SwiftLinkPreview: NSObject { let cancellable = Cancellable() - self.session = URLSession(configuration: self.session.configuration, - delegate: self, // To handle redirects - delegateQueue: self.session.delegateQueue) - let successResponseQueue = { (response: Response) in if !cancellable.isCancelled { self.responseQueue.async { @@ -827,17 +823,3 @@ extension SwiftLinkPreview { } } - -extension SwiftLinkPreview: URLSessionDataDelegate { - - public func urlSession(_ session: URLSession, - task: URLSessionTask, - willPerformHTTPRedirection response: HTTPURLResponse, - newRequest request: URLRequest, - completionHandler: @escaping (URLRequest?) -> Void) { - var request = request - request.httpMethod = "GET" - completionHandler(request) - } - -}