├── .github └── FUNDING.yml ├── Clean Links Extension ├── Base.lproj │ └── SafariExtensionViewController.xib ├── Clean_Links_Extension.entitlements ├── CleanedLinkItem.swift ├── CleanedLinkItem.xib ├── DataStructures.swift ├── ExternalLinkGuard.swift ├── Info.plist ├── SafariExtensionHandler.swift ├── SafariExtensionViewController.swift ├── fingerprint.png └── script.js ├── Clean Links.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── radoslav.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── radoslav.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Clean Links ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── identification-1.png │ │ ├── identification-2.png │ │ ├── identification-4.png │ │ ├── identification-5.png │ │ ├── identification-6.png │ │ ├── identification-7.png │ │ ├── identification-8.png │ │ ├── identification-9.png │ │ └── identification.png │ ├── Contents.json │ ├── Link.imageset │ │ ├── Contents.json │ │ ├── link-2.png │ │ ├── link-3.png │ │ └── link.png │ └── ShieldCheck.imageset │ │ ├── Contents.json │ │ ├── shield (1).png │ │ ├── shield (2).png │ │ └── shield.png ├── Base.lproj │ └── Main.storyboard ├── Clean_Links.entitlements ├── Info.plist └── ViewController.swift ├── Clean LinksTests ├── Clean_LinksTests.swift └── Info.plist ├── Clean LinksUITests ├── Clean_LinksUITests.swift └── Info.plist ├── Content Blocker ├── ContentBlockerRequestHandler.swift ├── Content_Blocker.entitlements ├── Info.plist └── blockerList.json ├── LICENSE ├── README.md ├── assets ├── fingerprint.png └── toolbar.png └── docs └── privacy.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ["https://www.paypal.me/Sh1d0w", "https://www.buymeacoffee.com/Sh1d0w"] -------------------------------------------------------------------------------- /Clean Links Extension/Base.lproj/SafariExtensionViewController.xib: -------------------------------------------------------------------------------- 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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Clean Links Extension/Clean_Links_Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Clean Links Extension/CleanedLinkItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CleanedLinkItem.swift 3 | // Clean Links Extension 4 | // 5 | // Created by Radoslav Vitanov on 14.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class CleanedLinkItem: NSCollectionViewItem { 12 | 13 | @IBOutlet weak var label: NSTextField! 14 | 15 | @IBOutlet weak var image: NSImageView! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | label.isEditable = false 21 | label.allowsEditingTextAttributes = true 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Clean Links Extension/CleanedLinkItem.xib: -------------------------------------------------------------------------------- 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 | 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 | -------------------------------------------------------------------------------- /Clean Links Extension/DataStructures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataStructures.swift 3 | // Clean Links Extension 4 | // 5 | // Created by Radoslav Vitanov on 14.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum EventType { 12 | case parameter 13 | case linkTracker 14 | } 15 | 16 | struct Event { 17 | let type: EventType 18 | let domain: String 19 | let value: String 20 | } 21 | -------------------------------------------------------------------------------- /Clean Links Extension/ExternalLinkGuard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExternalLinkGuard.swift 3 | // Clean Links Extension 4 | // 5 | // Created by Radoslav Vitanov on 15.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ExternalLinkGuard { 12 | var components: URLComponents 13 | 14 | init(components: URLComponents) { 15 | self.components = components 16 | } 17 | 18 | func cleanUrl() -> String? { 19 | if isGoogleTrackingUrl() { 20 | return removeGoogleRedirect() 21 | } 22 | 23 | if isFacebookTrackingUrl() { 24 | return removeFacebookRedirect() 25 | } 26 | 27 | return nil 28 | } 29 | 30 | func isGoogleTrackingUrl() -> Bool { 31 | if (components.host!.contains("google") && components.path == "/url") { 32 | return true 33 | } 34 | 35 | return false 36 | } 37 | 38 | func isFacebookTrackingUrl() -> Bool { 39 | if (components.host! == "l.facebook.com") { 40 | return true 41 | } 42 | 43 | return false 44 | } 45 | 46 | func removeGoogleRedirect() -> String { 47 | if components.query == nil { 48 | return components.string! 49 | } 50 | 51 | let urlParam = components.queryItems?.first(where: { (item) -> Bool in 52 | return item.name == "url" || item.name == "q" 53 | }) 54 | 55 | if let url = urlParam?.value { 56 | return url 57 | } 58 | 59 | return components.string! 60 | } 61 | 62 | func removeFacebookRedirect() -> String { 63 | if components.query == nil { 64 | return components.string! 65 | } 66 | 67 | let urlParam = components.queryItems?.first(where: { (item) -> Bool in 68 | item.name == "u" 69 | }) 70 | 71 | if let url = urlParam?.value { 72 | return url 73 | } 74 | 75 | return components.string! 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Clean Links Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Clean Links Extension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionPointIdentifier 28 | com.apple.Safari.extension 29 | NSExtensionPrincipalClass 30 | $(PRODUCT_MODULE_NAME).SafariExtensionHandler 31 | SFSafariContentScript 32 | 33 | 34 | Script 35 | script.js 36 | 37 | 38 | SFSafariToolbarItem 39 | 40 | Action 41 | Popover 42 | Identifier 43 | Button 44 | Image 45 | fingerprint.png 46 | Label 47 | Cleaned Links 48 | 49 | SFSafariWebsiteAccess 50 | 51 | Allowed Domains 52 | 53 | * 54 | 55 | Level 56 | All 57 | 58 | 59 | NSHumanReadableCopyright 60 | Copyright © 2020 Radoslav Vitanov. All rights reserved. 61 | NSHumanReadableDescription 62 | This is a Safari App Extension, which strips all most popular tracking query parameters off the links you click. 63 | 64 | 65 | -------------------------------------------------------------------------------- /Clean Links Extension/SafariExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariExtensionHandler.swift 3 | // Clean Links Extension 4 | // 5 | // Created by Radoslav Vitanov on 13.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import SafariServices 10 | 11 | class SafariExtensionHandler: SFSafariExtensionHandler { 12 | 13 | /** 14 | * This is a list of all query parameters we want to escape. 15 | */ 16 | var blacklist: [String] = [ 17 | "fbclid", 18 | "utm_source", 19 | "utm_medium", 20 | "utm_campaign", 21 | "utm_term", 22 | "utm_content", 23 | "gclid", 24 | "gclsrc", 25 | "dclid", 26 | "zanpid", 27 | "msclkid", 28 | "mc_eid", 29 | "_openstat", 30 | "_hsmi", 31 | "_hsenc", 32 | "spm", // alibaba group, taobao.com, aliyun.com etc... 33 | "spm_id_from", // bilibili.com 34 | "pf_rd_r", // Amazon Associates 35 | "pf_rd_m", // Amazon Associates 36 | "pf_rd_i", // Amazon Associates 37 | "pf_rd_s", // Amazon Associates 38 | "pf_rd_t", // Amazon Associates 39 | "pf_rd_p" // Amazon Associates 40 | ] 41 | 42 | /** 43 | * This is a helper variable, to workaround the problem when the user opens a new link while holding CMD. 44 | * Because we are redirecting the page's active tab to the url, both parent and child pages will trigger this. 45 | * 46 | * We are setting this to "true" via message from the DOM when the user presses the modifier key. Later on 47 | * during the check, if the modifier key was pressed we just don't redirect on the first call (which is the current 48 | * page), but we do for the next one which should be the page that was just opened. 49 | */ 50 | static var willOpenNewTab: Bool = false 51 | 52 | static var events: [Event] = []; 53 | 54 | /** 55 | * Handle messages coming from the DOM 56 | */ 57 | override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { 58 | if (messageName == "keypress") { 59 | SafariExtensionHandler.willOpenNewTab = userInfo!["new-tab"] as! Bool; 60 | } 61 | 62 | if (messageName == "prevented-redirect") { 63 | let domain = userInfo!["domain"] as! String 64 | let url = userInfo!["url"] as! String 65 | 66 | SafariExtensionHandler.events.insert( 67 | Event(type: EventType.linkTracker, domain: domain, value: url), 68 | at: 0) 69 | } 70 | } 71 | 72 | override func toolbarItemClicked(in window: SFSafariWindow) { 73 | 74 | } 75 | 76 | override func validateToolbarItem(in window: SFSafariWindow, validationHandler: @escaping ((Bool, String) -> Void)) { 77 | let badge = SafariExtensionHandler.events.count > 0 ? String(SafariExtensionHandler.events.count) : "" 78 | 79 | validationHandler(true, "\(badge)") 80 | } 81 | 82 | override func popoverViewController() -> SFSafariExtensionViewController { 83 | return SafariExtensionViewController.shared 84 | } 85 | 86 | override func popoverWillShow(in window: SFSafariWindow) { 87 | SafariExtensionViewController.shared.items = SafariExtensionHandler.events 88 | } 89 | 90 | override func page(_ page: SFSafariPage, willNavigateTo url: URL?) { 91 | // This is a workround when the user opens a link while holding CMD. 92 | // If this is the case then this method is called twice, once for the 93 | // parent page and once for the child. In this case we don't want to 94 | // redirect the parent page, as you will end up with two tabs with same 95 | // page opened. 96 | if (SafariExtensionHandler.willOpenNewTab) { 97 | SafariExtensionHandler.willOpenNewTab = false 98 | return; 99 | } 100 | 101 | // If we don't have access to that url, just skip 102 | guard let url = url else { 103 | return 104 | } 105 | 106 | // Parse url components 107 | guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { 108 | return 109 | } 110 | 111 | let linkGuard = ExternalLinkGuard(components: components) 112 | 113 | if let link = linkGuard.cleanUrl() { 114 | let parsedLink = URLComponents(string: link)! 115 | SafariExtensionHandler.events.insert(Event(type: EventType.linkTracker, domain: components.host!, value: parsedLink.host! 116 | ), at: 0) 117 | 118 | page.getContainingTab(completionHandler: { 119 | $0.navigate(to: URL(string: link)!) 120 | }) 121 | return 122 | } 123 | 124 | // Parse the query parameters, if there are any 125 | if let queryItems = components.queryItems { 126 | // Filter out the blacklisted params 127 | components.queryItems = queryItems.filter({ 128 | let isBlacklisted = blacklist.contains($0.name) || $0.name.starts(with: "utm_"); 129 | 130 | if (isBlacklisted) { 131 | SafariExtensionHandler.events.insert(Event(type: EventType.parameter, domain: components.host!, value: $0.name 132 | ), at: 0) 133 | } 134 | 135 | return !isBlacklisted 136 | }) 137 | 138 | // If there are no query parameters remove the query "?" sign 139 | if (components.queryItems?.count == 0) { 140 | components.query = nil 141 | } 142 | 143 | // If we haven't removed anything, e.g. no bad params were present 144 | // in the url, do nothing 145 | if (components.queryItems?.count == queryItems.count) { 146 | return 147 | } 148 | 149 | // Do the actual redirect to the clean url 150 | if let redirect = components.url { 151 | page.getContainingTab(completionHandler: { 152 | $0.navigate(to: redirect) 153 | }) 154 | } 155 | 156 | } 157 | 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /Clean Links Extension/SafariExtensionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariExtensionViewController.swift 3 | // Clean Links Extension 4 | // 5 | // Created by Radoslav Vitanov on 13.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import SafariServices 10 | 11 | class SafariExtensionViewController: SFSafariExtensionViewController { 12 | 13 | var items: [Event] = [] { 14 | didSet { 15 | DispatchQueue.main.async { 16 | self.collectionView.reloadData() 17 | } 18 | } 19 | } 20 | 21 | @IBOutlet weak var collectionView: NSCollectionView! 22 | 23 | static let shared: SafariExtensionViewController = { 24 | let shared = SafariExtensionViewController() 25 | shared.preferredContentSize = NSSize(width:350, height:300) 26 | return shared 27 | }() 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | collectionView.backgroundColors = [.clear] 33 | collectionView.dataSource = self 34 | collectionView.delegate = self 35 | collectionView.register(CleanedLinkItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Event") 36 | ) 37 | 38 | } 39 | 40 | } 41 | 42 | extension NSMutableAttributedString { 43 | var fontSize:CGFloat { return 14 } 44 | var boldFont:NSFont { return NSFont.boldSystemFont(ofSize: fontSize) } 45 | var normalFont:NSFont { return NSFont.systemFont(ofSize: fontSize)} 46 | 47 | func bold(_ value:String) -> NSMutableAttributedString { 48 | 49 | let attributes:[NSAttributedString.Key : Any] = [ 50 | .font : boldFont 51 | ] 52 | 53 | self.append(NSAttributedString(string: value, attributes:attributes)) 54 | return self 55 | } 56 | 57 | func normal(_ value:String) -> NSMutableAttributedString { 58 | 59 | let attributes:[NSAttributedString.Key : Any] = [ 60 | .font : normalFont, 61 | ] 62 | 63 | self.append(NSAttributedString(string: value, attributes:attributes)) 64 | return self 65 | } 66 | } 67 | 68 | extension SafariExtensionViewController: NSCollectionViewDelegateFlowLayout, NSCollectionViewDataSource { 69 | private func getLabel(item: Event) -> NSMutableAttributedString { 70 | switch item.type { 71 | case EventType.linkTracker: 72 | return NSMutableAttributedString() 73 | .normal("Prevented redirect from ") 74 | .bold("\(item.domain)") 75 | .normal(" to ") 76 | .bold("\(item.value)") 77 | default: 78 | return NSMutableAttributedString() 79 | .normal("Removed ") 80 | .bold("\(item.value)") 81 | .normal(" from ") 82 | .bold("\(item.domain)") 83 | } 84 | } 85 | 86 | private func estimateFrameForText(text: String) -> CGRect { 87 | let height: CGFloat = 30 88 | 89 | let size = CGSize(width: collectionView.frame.size.width, height: height) 90 | let options = NSString.DrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin) 91 | let attributes = [NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 14)] 92 | 93 | return NSString(string: text).boundingRect(with: size, options: options, attributes: attributes, context: nil) 94 | } 95 | 96 | func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { 97 | 98 | let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Event"), for: indexPath) as! CleanedLinkItem 99 | 100 | var image: NSImage? = nil 101 | let label: NSMutableAttributedString = getLabel(item: items[indexPath.item]) 102 | 103 | switch items[indexPath.item].type { 104 | case EventType.linkTracker: 105 | image = NSImage(named: NSImage.Name("Link")) 106 | default: 107 | image = NSImage(named: NSImage.Name("ShieldCheck")) 108 | } 109 | 110 | item.image.image = image 111 | item.label.attributedStringValue = label 112 | 113 | return item 114 | } 115 | 116 | func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { 117 | return items.count 118 | } 119 | 120 | func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize { 121 | //we are just measuring height so we add a padding constant to give the label some room to breathe! 122 | let padding: CGFloat = 5 123 | 124 | //estimate each cell's height 125 | let text = getLabel(item: items[indexPath.item]) 126 | let options = NSString.DrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin) 127 | let height = text.boundingRect(with: collectionView.frame.size, options: options) 128 | 129 | return CGSize(width: collectionView.frame.size.width, height: height.size.height + padding) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Clean Links Extension/fingerprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links Extension/fingerprint.png -------------------------------------------------------------------------------- /Clean Links Extension/script.js: -------------------------------------------------------------------------------- 1 | (function(w, d) { 2 | /** 3 | * This function catches the press of CMD or CTRL key and notifies the 4 | * extension. We need this to workaround opening of links while holding 5 | * CMD or CTRL. Because if we detect blacklisted params, we need to clean 6 | * up the link and then redirect the current page to the clean link, we 7 | * can end up with two tabs with same page opened, as the SafariExtensionHandler 8 | * page handler fires twice for both the parent and the child pages. 9 | */ 10 | function handleKey(e) { 11 | e = e || window.event; 12 | 13 | if (e) { 14 | var holdingModifier = e.ctrlKey || e.metaKey; 15 | var middleClick = e.which == 2 || e.button == 1; 16 | 17 | var shouldOpenNewTab = holdingModifier || middleClick; 18 | safari.extension.dispatchMessage("keypress", { "new-tab": shouldOpenNewTab }); 19 | } 20 | }; 21 | 22 | /** 23 | * This function checks if the url is the Google's url 24 | * tracking request, e.g. google.com/url?url=..... 25 | */ 26 | function isGoogleTrackingUrl(url) { 27 | if (url.hostname.indexOf("google") != -1 && url.pathname === "/url") { 28 | return true; 29 | } 30 | 31 | return false; 32 | } 33 | 34 | /** 35 | * This function checks if the url is the Facebook's url 36 | * tracking request, e.g. l.facebook.com/l.php?u=.... 37 | */ 38 | function isFacebookTrackingUrl(url) { 39 | if (url.hostname === "l.facebook.com") { 40 | return true; 41 | } 42 | 43 | return false; 44 | } 45 | 46 | /** 47 | * Google search steals your clicks. Every link in google search, 48 | * rewrites you clicks to url similar to www.google.com/url?url...&sa=... 49 | * before redirecting you to the real search result page. 50 | * 51 | * We will make sure they can't do that anymore. 52 | */ 53 | function removeGoogleRedirect(url) { 54 | if (url.searchParams.has("url")) { 55 | return url.searchParams.get("url"); 56 | } 57 | 58 | return url.searchParams.get("q"); 59 | } 60 | 61 | /** 62 | * Google search steals your clicks. Every link in google search, 63 | * rewrites you clicks to url similar to www.google.com/url?url...&sa=... 64 | * before redirecting you to the real search result page. 65 | * 66 | * We will make sure they can't do that anymore. 67 | */ 68 | function removeFacebookRedirect(url) { 69 | return url.searchParams.get("u"); 70 | } 71 | 72 | /** 73 | * For the same purpose as above, we need to check if the link that 74 | * the user has clicked has target="_blank" and if yes, send signal 75 | * to the extension that new tab will be opened. 76 | */ 77 | function handleExternalLinks(ev) { 78 | var anchor = null; 79 | 80 | for (var n = ev.target; n.parentNode; n = n.parentNode) { 81 | if (n.nodeName === "A") { 82 | anchor = n; 83 | break; 84 | } 85 | } 86 | 87 | // Check if the user has actually clicked on a link 88 | if (anchor === null) { 89 | return; 90 | } 91 | 92 | // We will try to determine if we should open the link in the same tab 93 | // or in a new one. This happens only if the link itself has target="_blank" 94 | // or the user is holding the CMD key. 95 | var shouldOpenNewTab = (anchor && anchor.target === "_blank") || (ev.ctrlKey || ev.metaKey); 96 | 97 | // Notify the extension that we want a new tab opened 98 | if (shouldOpenNewTab) { 99 | safari.extension.dispatchMessage("keypress", { "new-tab": true }); 100 | } 101 | 102 | // Parse the url 103 | var parsedUrl = new URL(anchor.href); 104 | 105 | var isTracker = false; 106 | var cleanUrl = parsedUrl; 107 | 108 | if (isGoogleTrackingUrl(parsedUrl)) { 109 | isTracker = true; 110 | cleanUrl = removeGoogleRedirect(parsedUrl); 111 | } 112 | 113 | if (isFacebookTrackingUrl(parsedUrl)) { 114 | isTracker = true; 115 | cleanUrl = removeFacebookRedirect(parsedUrl); 116 | } 117 | 118 | if (isTracker) { 119 | ev.preventDefault(); 120 | 121 | safari.extension.dispatchMessage("prevented-redirect", { 122 | "domain": parsedUrl.hostname, 123 | "url": cleanUrl 124 | }); 125 | 126 | w.open(cleanUrl, shouldOpenNewTab ? '_blank' : '_self','noopener,noreferrer'); 127 | 128 | return false; 129 | } 130 | 131 | return true; 132 | }; 133 | 134 | // Bind some event listeners 135 | d.onkeydown = handleKey; 136 | d.onkeyup = handleKey; 137 | d.onclick = handleExternalLinks; 138 | })(window, document); 139 | -------------------------------------------------------------------------------- /Clean Links.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 850AB753241D248D00D3CA35 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85BD8115241B35F00009A377 /* Assets.xcassets */; }; 11 | 850AB757241E7F4A00D3CA35 /* ExternalLinkGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850AB756241E7F4A00D3CA35 /* ExternalLinkGuard.swift */; }; 12 | 8593F5A4241CA60A00DF5915 /* CleanedLinkItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8593F5A2241CA60A00DF5915 /* CleanedLinkItem.swift */; }; 13 | 8593F5A5241CA60A00DF5915 /* CleanedLinkItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8593F5A3241CA60A00DF5915 /* CleanedLinkItem.xib */; }; 14 | 8593F5AD241CAE4E00DF5915 /* fingerprint.png in Resources */ = {isa = PBXBuildFile; fileRef = 8593F5AC241CAE4E00DF5915 /* fingerprint.png */; }; 15 | 8593F5B1241CC6AE00DF5915 /* DataStructures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8593F5B0241CC6AE00DF5915 /* DataStructures.swift */; }; 16 | 85BD810D241B35EE0009A377 /* Clean_Links.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 85BD810C241B35EE0009A377 /* Clean_Links.entitlements */; }; 17 | 85BD810F241B35EE0009A377 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BD810E241B35EE0009A377 /* AppDelegate.swift */; }; 18 | 85BD8112241B35EE0009A377 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85BD8110241B35EE0009A377 /* Main.storyboard */; }; 19 | 85BD8114241B35EE0009A377 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BD8113241B35EE0009A377 /* ViewController.swift */; }; 20 | 85BD8116241B35F00009A377 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85BD8115241B35F00009A377 /* Assets.xcassets */; }; 21 | 85BD8121241B35F00009A377 /* Clean_LinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BD8120241B35F00009A377 /* Clean_LinksTests.swift */; }; 22 | 85BD812C241B35F00009A377 /* Clean_LinksUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BD812B241B35F00009A377 /* Clean_LinksUITests.swift */; }; 23 | 85BD8133241B35F00009A377 /* Clean Links Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 85BD8132241B35F00009A377 /* Clean Links Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 24 | 85BD8138241B35F00009A377 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85BD8137241B35F00009A377 /* Cocoa.framework */; }; 25 | 85BD813B241B35F00009A377 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BD813A241B35F00009A377 /* SafariExtensionHandler.swift */; }; 26 | 85BD813D241B35F00009A377 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BD813C241B35F00009A377 /* SafariExtensionViewController.swift */; }; 27 | 85BD8140241B35F00009A377 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 85BD813E241B35F00009A377 /* SafariExtensionViewController.xib */; }; 28 | 85BD8143241B35F00009A377 /* script.js in Resources */ = {isa = PBXBuildFile; fileRef = 85BD8142241B35F00009A377 /* script.js */; }; 29 | 85DB596B241F91D200524FE9 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85BD8137241B35F00009A377 /* Cocoa.framework */; }; 30 | 85DB596E241F91D200524FE9 /* blockerList.json in Resources */ = {isa = PBXBuildFile; fileRef = 85DB596D241F91D200524FE9 /* blockerList.json */; }; 31 | 85DB5970241F91D200524FE9 /* ContentBlockerRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DB596F241F91D200524FE9 /* ContentBlockerRequestHandler.swift */; }; 32 | 85DB5975241F91D200524FE9 /* Content Blocker.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 85DB596A241F91D200524FE9 /* Content Blocker.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | 85BD811D241B35F00009A377 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 85BD8101241B35EE0009A377 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 85BD8108241B35EE0009A377; 41 | remoteInfo = "Clean Links"; 42 | }; 43 | 85BD8128241B35F00009A377 /* PBXContainerItemProxy */ = { 44 | isa = PBXContainerItemProxy; 45 | containerPortal = 85BD8101241B35EE0009A377 /* Project object */; 46 | proxyType = 1; 47 | remoteGlobalIDString = 85BD8108241B35EE0009A377; 48 | remoteInfo = "Clean Links"; 49 | }; 50 | 85BD8134241B35F00009A377 /* PBXContainerItemProxy */ = { 51 | isa = PBXContainerItemProxy; 52 | containerPortal = 85BD8101241B35EE0009A377 /* Project object */; 53 | proxyType = 1; 54 | remoteGlobalIDString = 85BD8131241B35F00009A377; 55 | remoteInfo = "Clean Links Extension"; 56 | }; 57 | 85DB5973241F91D200524FE9 /* PBXContainerItemProxy */ = { 58 | isa = PBXContainerItemProxy; 59 | containerPortal = 85BD8101241B35EE0009A377 /* Project object */; 60 | proxyType = 1; 61 | remoteGlobalIDString = 85DB5969241F91D200524FE9; 62 | remoteInfo = "Content Blocker"; 63 | }; 64 | /* End PBXContainerItemProxy section */ 65 | 66 | /* Begin PBXCopyFilesBuildPhase section */ 67 | 85BD814C241B35F00009A377 /* Embed App Extensions */ = { 68 | isa = PBXCopyFilesBuildPhase; 69 | buildActionMask = 2147483647; 70 | dstPath = ""; 71 | dstSubfolderSpec = 13; 72 | files = ( 73 | 85DB5975241F91D200524FE9 /* Content Blocker.appex in Embed App Extensions */, 74 | 85BD8133241B35F00009A377 /* Clean Links Extension.appex in Embed App Extensions */, 75 | ); 76 | name = "Embed App Extensions"; 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXCopyFilesBuildPhase section */ 80 | 81 | /* Begin PBXFileReference section */ 82 | 850AB756241E7F4A00D3CA35 /* ExternalLinkGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalLinkGuard.swift; sourceTree = ""; }; 83 | 8593F5A2241CA60A00DF5915 /* CleanedLinkItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CleanedLinkItem.swift; sourceTree = ""; }; 84 | 8593F5A3241CA60A00DF5915 /* CleanedLinkItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CleanedLinkItem.xib; sourceTree = ""; }; 85 | 8593F5AC241CAE4E00DF5915 /* fingerprint.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = fingerprint.png; sourceTree = ""; }; 86 | 8593F5B0241CC6AE00DF5915 /* DataStructures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStructures.swift; sourceTree = ""; }; 87 | 85BD8109241B35EE0009A377 /* Clean Links.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Clean Links.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 88 | 85BD810C241B35EE0009A377 /* Clean_Links.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Clean_Links.entitlements; sourceTree = ""; }; 89 | 85BD810E241B35EE0009A377 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 90 | 85BD8111241B35EE0009A377 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 91 | 85BD8113241B35EE0009A377 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 92 | 85BD8115241B35F00009A377 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 93 | 85BD8117241B35F00009A377 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 94 | 85BD811C241B35F00009A377 /* Clean LinksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Clean LinksTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 95 | 85BD8120241B35F00009A377 /* Clean_LinksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clean_LinksTests.swift; sourceTree = ""; }; 96 | 85BD8122241B35F00009A377 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 97 | 85BD8127241B35F00009A377 /* Clean LinksUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Clean LinksUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 98 | 85BD812B241B35F00009A377 /* Clean_LinksUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clean_LinksUITests.swift; sourceTree = ""; }; 99 | 85BD812D241B35F00009A377 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 100 | 85BD8132241B35F00009A377 /* Clean Links Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Clean Links Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 101 | 85BD8137241B35F00009A377 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 102 | 85BD813A241B35F00009A377 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; }; 103 | 85BD813C241B35F00009A377 /* SafariExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionViewController.swift; sourceTree = ""; }; 104 | 85BD813F241B35F00009A377 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/SafariExtensionViewController.xib; sourceTree = ""; }; 105 | 85BD8141241B35F00009A377 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 106 | 85BD8142241B35F00009A377 /* script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = script.js; sourceTree = ""; }; 107 | 85BD8146241B35F00009A377 /* Clean_Links_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Clean_Links_Extension.entitlements; sourceTree = ""; }; 108 | 85DB596A241F91D200524FE9 /* Content Blocker.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Content Blocker.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 109 | 85DB596D241F91D200524FE9 /* blockerList.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = blockerList.json; sourceTree = ""; }; 110 | 85DB596F241F91D200524FE9 /* ContentBlockerRequestHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRequestHandler.swift; sourceTree = ""; }; 111 | 85DB5971241F91D200524FE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 112 | 85DB5972241F91D200524FE9 /* Content_Blocker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Content_Blocker.entitlements; sourceTree = ""; }; 113 | /* End PBXFileReference section */ 114 | 115 | /* Begin PBXFrameworksBuildPhase section */ 116 | 85BD8106241B35EE0009A377 /* Frameworks */ = { 117 | isa = PBXFrameworksBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | ); 121 | runOnlyForDeploymentPostprocessing = 0; 122 | }; 123 | 85BD8119241B35F00009A377 /* Frameworks */ = { 124 | isa = PBXFrameworksBuildPhase; 125 | buildActionMask = 2147483647; 126 | files = ( 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | 85BD8124241B35F00009A377 /* Frameworks */ = { 131 | isa = PBXFrameworksBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | 85BD812F241B35F00009A377 /* Frameworks */ = { 138 | isa = PBXFrameworksBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | 85BD8138241B35F00009A377 /* Cocoa.framework in Frameworks */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | 85DB5967241F91D200524FE9 /* Frameworks */ = { 146 | isa = PBXFrameworksBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 85DB596B241F91D200524FE9 /* Cocoa.framework in Frameworks */, 150 | ); 151 | runOnlyForDeploymentPostprocessing = 0; 152 | }; 153 | /* End PBXFrameworksBuildPhase section */ 154 | 155 | /* Begin PBXGroup section */ 156 | 85BD8100241B35EE0009A377 = { 157 | isa = PBXGroup; 158 | children = ( 159 | 85BD810B241B35EE0009A377 /* Clean Links */, 160 | 85BD811F241B35F00009A377 /* Clean LinksTests */, 161 | 85BD812A241B35F00009A377 /* Clean LinksUITests */, 162 | 85BD8139241B35F00009A377 /* Clean Links Extension */, 163 | 85DB596C241F91D200524FE9 /* Content Blocker */, 164 | 85BD8136241B35F00009A377 /* Frameworks */, 165 | 85BD810A241B35EE0009A377 /* Products */, 166 | ); 167 | sourceTree = ""; 168 | }; 169 | 85BD810A241B35EE0009A377 /* Products */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 85BD8109241B35EE0009A377 /* Clean Links.app */, 173 | 85BD811C241B35F00009A377 /* Clean LinksTests.xctest */, 174 | 85BD8127241B35F00009A377 /* Clean LinksUITests.xctest */, 175 | 85BD8132241B35F00009A377 /* Clean Links Extension.appex */, 176 | 85DB596A241F91D200524FE9 /* Content Blocker.appex */, 177 | ); 178 | name = Products; 179 | sourceTree = ""; 180 | }; 181 | 85BD810B241B35EE0009A377 /* Clean Links */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 85BD810C241B35EE0009A377 /* Clean_Links.entitlements */, 185 | 85BD810E241B35EE0009A377 /* AppDelegate.swift */, 186 | 85BD8110241B35EE0009A377 /* Main.storyboard */, 187 | 85BD8113241B35EE0009A377 /* ViewController.swift */, 188 | 85BD8115241B35F00009A377 /* Assets.xcassets */, 189 | 85BD8117241B35F00009A377 /* Info.plist */, 190 | ); 191 | path = "Clean Links"; 192 | sourceTree = ""; 193 | }; 194 | 85BD811F241B35F00009A377 /* Clean LinksTests */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 85BD8120241B35F00009A377 /* Clean_LinksTests.swift */, 198 | 85BD8122241B35F00009A377 /* Info.plist */, 199 | ); 200 | path = "Clean LinksTests"; 201 | sourceTree = ""; 202 | }; 203 | 85BD812A241B35F00009A377 /* Clean LinksUITests */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 85BD812B241B35F00009A377 /* Clean_LinksUITests.swift */, 207 | 85BD812D241B35F00009A377 /* Info.plist */, 208 | ); 209 | path = "Clean LinksUITests"; 210 | sourceTree = ""; 211 | }; 212 | 85BD8136241B35F00009A377 /* Frameworks */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 85BD8137241B35F00009A377 /* Cocoa.framework */, 216 | ); 217 | name = Frameworks; 218 | sourceTree = ""; 219 | }; 220 | 85BD8139241B35F00009A377 /* Clean Links Extension */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | 8593F5AC241CAE4E00DF5915 /* fingerprint.png */, 224 | 85BD813A241B35F00009A377 /* SafariExtensionHandler.swift */, 225 | 85BD813C241B35F00009A377 /* SafariExtensionViewController.swift */, 226 | 85BD813E241B35F00009A377 /* SafariExtensionViewController.xib */, 227 | 85BD8141241B35F00009A377 /* Info.plist */, 228 | 85BD8142241B35F00009A377 /* script.js */, 229 | 85BD8146241B35F00009A377 /* Clean_Links_Extension.entitlements */, 230 | 8593F5A2241CA60A00DF5915 /* CleanedLinkItem.swift */, 231 | 8593F5A3241CA60A00DF5915 /* CleanedLinkItem.xib */, 232 | 8593F5B0241CC6AE00DF5915 /* DataStructures.swift */, 233 | 850AB756241E7F4A00D3CA35 /* ExternalLinkGuard.swift */, 234 | ); 235 | path = "Clean Links Extension"; 236 | sourceTree = ""; 237 | }; 238 | 85DB596C241F91D200524FE9 /* Content Blocker */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | 85DB596D241F91D200524FE9 /* blockerList.json */, 242 | 85DB596F241F91D200524FE9 /* ContentBlockerRequestHandler.swift */, 243 | 85DB5971241F91D200524FE9 /* Info.plist */, 244 | 85DB5972241F91D200524FE9 /* Content_Blocker.entitlements */, 245 | ); 246 | path = "Content Blocker"; 247 | sourceTree = ""; 248 | }; 249 | /* End PBXGroup section */ 250 | 251 | /* Begin PBXNativeTarget section */ 252 | 85BD8108241B35EE0009A377 /* Clean Links */ = { 253 | isa = PBXNativeTarget; 254 | buildConfigurationList = 85BD814D241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean Links" */; 255 | buildPhases = ( 256 | 85BD8105241B35EE0009A377 /* Sources */, 257 | 85BD8106241B35EE0009A377 /* Frameworks */, 258 | 85BD8107241B35EE0009A377 /* Resources */, 259 | 85BD814C241B35F00009A377 /* Embed App Extensions */, 260 | ); 261 | buildRules = ( 262 | ); 263 | dependencies = ( 264 | 85BD8135241B35F00009A377 /* PBXTargetDependency */, 265 | 85DB5974241F91D200524FE9 /* PBXTargetDependency */, 266 | ); 267 | name = "Clean Links"; 268 | productName = "Clean Links"; 269 | productReference = 85BD8109241B35EE0009A377 /* Clean Links.app */; 270 | productType = "com.apple.product-type.application"; 271 | }; 272 | 85BD811B241B35F00009A377 /* Clean LinksTests */ = { 273 | isa = PBXNativeTarget; 274 | buildConfigurationList = 85BD8150241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean LinksTests" */; 275 | buildPhases = ( 276 | 85BD8118241B35F00009A377 /* Sources */, 277 | 85BD8119241B35F00009A377 /* Frameworks */, 278 | 85BD811A241B35F00009A377 /* Resources */, 279 | ); 280 | buildRules = ( 281 | ); 282 | dependencies = ( 283 | 85BD811E241B35F00009A377 /* PBXTargetDependency */, 284 | ); 285 | name = "Clean LinksTests"; 286 | productName = "Clean LinksTests"; 287 | productReference = 85BD811C241B35F00009A377 /* Clean LinksTests.xctest */; 288 | productType = "com.apple.product-type.bundle.unit-test"; 289 | }; 290 | 85BD8126241B35F00009A377 /* Clean LinksUITests */ = { 291 | isa = PBXNativeTarget; 292 | buildConfigurationList = 85BD8153241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean LinksUITests" */; 293 | buildPhases = ( 294 | 85BD8123241B35F00009A377 /* Sources */, 295 | 85BD8124241B35F00009A377 /* Frameworks */, 296 | 85BD8125241B35F00009A377 /* Resources */, 297 | ); 298 | buildRules = ( 299 | ); 300 | dependencies = ( 301 | 85BD8129241B35F00009A377 /* PBXTargetDependency */, 302 | ); 303 | name = "Clean LinksUITests"; 304 | productName = "Clean LinksUITests"; 305 | productReference = 85BD8127241B35F00009A377 /* Clean LinksUITests.xctest */; 306 | productType = "com.apple.product-type.bundle.ui-testing"; 307 | }; 308 | 85BD8131241B35F00009A377 /* Clean Links Extension */ = { 309 | isa = PBXNativeTarget; 310 | buildConfigurationList = 85BD8149241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean Links Extension" */; 311 | buildPhases = ( 312 | 85BD812E241B35F00009A377 /* Sources */, 313 | 85BD812F241B35F00009A377 /* Frameworks */, 314 | 85BD8130241B35F00009A377 /* Resources */, 315 | ); 316 | buildRules = ( 317 | ); 318 | dependencies = ( 319 | ); 320 | name = "Clean Links Extension"; 321 | productName = "Clean Links Extension"; 322 | productReference = 85BD8132241B35F00009A377 /* Clean Links Extension.appex */; 323 | productType = "com.apple.product-type.app-extension"; 324 | }; 325 | 85DB5969241F91D200524FE9 /* Content Blocker */ = { 326 | isa = PBXNativeTarget; 327 | buildConfigurationList = 85DB5978241F91D200524FE9 /* Build configuration list for PBXNativeTarget "Content Blocker" */; 328 | buildPhases = ( 329 | 85DB5966241F91D200524FE9 /* Sources */, 330 | 85DB5967241F91D200524FE9 /* Frameworks */, 331 | 85DB5968241F91D200524FE9 /* Resources */, 332 | ); 333 | buildRules = ( 334 | ); 335 | dependencies = ( 336 | ); 337 | name = "Content Blocker"; 338 | productName = "Content Blocker"; 339 | productReference = 85DB596A241F91D200524FE9 /* Content Blocker.appex */; 340 | productType = "com.apple.product-type.app-extension"; 341 | }; 342 | /* End PBXNativeTarget section */ 343 | 344 | /* Begin PBXProject section */ 345 | 85BD8101241B35EE0009A377 /* Project object */ = { 346 | isa = PBXProject; 347 | attributes = { 348 | LastSwiftUpdateCheck = 1130; 349 | LastUpgradeCheck = 1130; 350 | ORGANIZATIONNAME = "Radoslav Vitanov"; 351 | TargetAttributes = { 352 | 85BD8108241B35EE0009A377 = { 353 | CreatedOnToolsVersion = 11.3.1; 354 | }; 355 | 85BD811B241B35F00009A377 = { 356 | CreatedOnToolsVersion = 11.3.1; 357 | TestTargetID = 85BD8108241B35EE0009A377; 358 | }; 359 | 85BD8126241B35F00009A377 = { 360 | CreatedOnToolsVersion = 11.3.1; 361 | TestTargetID = 85BD8108241B35EE0009A377; 362 | }; 363 | 85BD8131241B35F00009A377 = { 364 | CreatedOnToolsVersion = 11.3.1; 365 | }; 366 | 85DB5969241F91D200524FE9 = { 367 | CreatedOnToolsVersion = 11.3.1; 368 | }; 369 | }; 370 | }; 371 | buildConfigurationList = 85BD8104241B35EE0009A377 /* Build configuration list for PBXProject "Clean Links" */; 372 | compatibilityVersion = "Xcode 9.3"; 373 | developmentRegion = en; 374 | hasScannedForEncodings = 0; 375 | knownRegions = ( 376 | en, 377 | Base, 378 | ); 379 | mainGroup = 85BD8100241B35EE0009A377; 380 | productRefGroup = 85BD810A241B35EE0009A377 /* Products */; 381 | projectDirPath = ""; 382 | projectRoot = ""; 383 | targets = ( 384 | 85BD8108241B35EE0009A377 /* Clean Links */, 385 | 85BD811B241B35F00009A377 /* Clean LinksTests */, 386 | 85BD8126241B35F00009A377 /* Clean LinksUITests */, 387 | 85BD8131241B35F00009A377 /* Clean Links Extension */, 388 | 85DB5969241F91D200524FE9 /* Content Blocker */, 389 | ); 390 | }; 391 | /* End PBXProject section */ 392 | 393 | /* Begin PBXResourcesBuildPhase section */ 394 | 85BD8107241B35EE0009A377 /* Resources */ = { 395 | isa = PBXResourcesBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | 85BD810D241B35EE0009A377 /* Clean_Links.entitlements in Resources */, 399 | 85BD8116241B35F00009A377 /* Assets.xcassets in Resources */, 400 | 85BD8112241B35EE0009A377 /* Main.storyboard in Resources */, 401 | ); 402 | runOnlyForDeploymentPostprocessing = 0; 403 | }; 404 | 85BD811A241B35F00009A377 /* Resources */ = { 405 | isa = PBXResourcesBuildPhase; 406 | buildActionMask = 2147483647; 407 | files = ( 408 | ); 409 | runOnlyForDeploymentPostprocessing = 0; 410 | }; 411 | 85BD8125241B35F00009A377 /* Resources */ = { 412 | isa = PBXResourcesBuildPhase; 413 | buildActionMask = 2147483647; 414 | files = ( 415 | ); 416 | runOnlyForDeploymentPostprocessing = 0; 417 | }; 418 | 85BD8130241B35F00009A377 /* Resources */ = { 419 | isa = PBXResourcesBuildPhase; 420 | buildActionMask = 2147483647; 421 | files = ( 422 | 85BD8140241B35F00009A377 /* SafariExtensionViewController.xib in Resources */, 423 | 8593F5A5241CA60A00DF5915 /* CleanedLinkItem.xib in Resources */, 424 | 8593F5AD241CAE4E00DF5915 /* fingerprint.png in Resources */, 425 | 85BD8143241B35F00009A377 /* script.js in Resources */, 426 | 850AB753241D248D00D3CA35 /* Assets.xcassets in Resources */, 427 | ); 428 | runOnlyForDeploymentPostprocessing = 0; 429 | }; 430 | 85DB5968241F91D200524FE9 /* Resources */ = { 431 | isa = PBXResourcesBuildPhase; 432 | buildActionMask = 2147483647; 433 | files = ( 434 | 85DB596E241F91D200524FE9 /* blockerList.json in Resources */, 435 | ); 436 | runOnlyForDeploymentPostprocessing = 0; 437 | }; 438 | /* End PBXResourcesBuildPhase section */ 439 | 440 | /* Begin PBXSourcesBuildPhase section */ 441 | 85BD8105241B35EE0009A377 /* Sources */ = { 442 | isa = PBXSourcesBuildPhase; 443 | buildActionMask = 2147483647; 444 | files = ( 445 | 85BD8114241B35EE0009A377 /* ViewController.swift in Sources */, 446 | 85BD810F241B35EE0009A377 /* AppDelegate.swift in Sources */, 447 | ); 448 | runOnlyForDeploymentPostprocessing = 0; 449 | }; 450 | 85BD8118241B35F00009A377 /* Sources */ = { 451 | isa = PBXSourcesBuildPhase; 452 | buildActionMask = 2147483647; 453 | files = ( 454 | 85BD8121241B35F00009A377 /* Clean_LinksTests.swift in Sources */, 455 | ); 456 | runOnlyForDeploymentPostprocessing = 0; 457 | }; 458 | 85BD8123241B35F00009A377 /* Sources */ = { 459 | isa = PBXSourcesBuildPhase; 460 | buildActionMask = 2147483647; 461 | files = ( 462 | 85BD812C241B35F00009A377 /* Clean_LinksUITests.swift in Sources */, 463 | ); 464 | runOnlyForDeploymentPostprocessing = 0; 465 | }; 466 | 85BD812E241B35F00009A377 /* Sources */ = { 467 | isa = PBXSourcesBuildPhase; 468 | buildActionMask = 2147483647; 469 | files = ( 470 | 850AB757241E7F4A00D3CA35 /* ExternalLinkGuard.swift in Sources */, 471 | 85BD813D241B35F00009A377 /* SafariExtensionViewController.swift in Sources */, 472 | 8593F5B1241CC6AE00DF5915 /* DataStructures.swift in Sources */, 473 | 85BD813B241B35F00009A377 /* SafariExtensionHandler.swift in Sources */, 474 | 8593F5A4241CA60A00DF5915 /* CleanedLinkItem.swift in Sources */, 475 | ); 476 | runOnlyForDeploymentPostprocessing = 0; 477 | }; 478 | 85DB5966241F91D200524FE9 /* Sources */ = { 479 | isa = PBXSourcesBuildPhase; 480 | buildActionMask = 2147483647; 481 | files = ( 482 | 85DB5970241F91D200524FE9 /* ContentBlockerRequestHandler.swift in Sources */, 483 | ); 484 | runOnlyForDeploymentPostprocessing = 0; 485 | }; 486 | /* End PBXSourcesBuildPhase section */ 487 | 488 | /* Begin PBXTargetDependency section */ 489 | 85BD811E241B35F00009A377 /* PBXTargetDependency */ = { 490 | isa = PBXTargetDependency; 491 | target = 85BD8108241B35EE0009A377 /* Clean Links */; 492 | targetProxy = 85BD811D241B35F00009A377 /* PBXContainerItemProxy */; 493 | }; 494 | 85BD8129241B35F00009A377 /* PBXTargetDependency */ = { 495 | isa = PBXTargetDependency; 496 | target = 85BD8108241B35EE0009A377 /* Clean Links */; 497 | targetProxy = 85BD8128241B35F00009A377 /* PBXContainerItemProxy */; 498 | }; 499 | 85BD8135241B35F00009A377 /* PBXTargetDependency */ = { 500 | isa = PBXTargetDependency; 501 | target = 85BD8131241B35F00009A377 /* Clean Links Extension */; 502 | targetProxy = 85BD8134241B35F00009A377 /* PBXContainerItemProxy */; 503 | }; 504 | 85DB5974241F91D200524FE9 /* PBXTargetDependency */ = { 505 | isa = PBXTargetDependency; 506 | target = 85DB5969241F91D200524FE9 /* Content Blocker */; 507 | targetProxy = 85DB5973241F91D200524FE9 /* PBXContainerItemProxy */; 508 | }; 509 | /* End PBXTargetDependency section */ 510 | 511 | /* Begin PBXVariantGroup section */ 512 | 85BD8110241B35EE0009A377 /* Main.storyboard */ = { 513 | isa = PBXVariantGroup; 514 | children = ( 515 | 85BD8111241B35EE0009A377 /* Base */, 516 | ); 517 | name = Main.storyboard; 518 | sourceTree = ""; 519 | }; 520 | 85BD813E241B35F00009A377 /* SafariExtensionViewController.xib */ = { 521 | isa = PBXVariantGroup; 522 | children = ( 523 | 85BD813F241B35F00009A377 /* Base */, 524 | ); 525 | name = SafariExtensionViewController.xib; 526 | sourceTree = ""; 527 | }; 528 | /* End PBXVariantGroup section */ 529 | 530 | /* Begin XCBuildConfiguration section */ 531 | 85BD8147241B35F00009A377 /* Debug */ = { 532 | isa = XCBuildConfiguration; 533 | buildSettings = { 534 | ALWAYS_SEARCH_USER_PATHS = NO; 535 | CLANG_ANALYZER_NONNULL = YES; 536 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 537 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 538 | CLANG_CXX_LIBRARY = "libc++"; 539 | CLANG_ENABLE_MODULES = YES; 540 | CLANG_ENABLE_OBJC_ARC = YES; 541 | CLANG_ENABLE_OBJC_WEAK = YES; 542 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 543 | CLANG_WARN_BOOL_CONVERSION = YES; 544 | CLANG_WARN_COMMA = YES; 545 | CLANG_WARN_CONSTANT_CONVERSION = YES; 546 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 547 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 548 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 549 | CLANG_WARN_EMPTY_BODY = YES; 550 | CLANG_WARN_ENUM_CONVERSION = YES; 551 | CLANG_WARN_INFINITE_RECURSION = YES; 552 | CLANG_WARN_INT_CONVERSION = YES; 553 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 554 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 555 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 556 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 557 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 558 | CLANG_WARN_STRICT_PROTOTYPES = YES; 559 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 560 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 561 | CLANG_WARN_UNREACHABLE_CODE = YES; 562 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 563 | COPY_PHASE_STRIP = NO; 564 | DEBUG_INFORMATION_FORMAT = dwarf; 565 | ENABLE_STRICT_OBJC_MSGSEND = YES; 566 | ENABLE_TESTABILITY = YES; 567 | GCC_C_LANGUAGE_STANDARD = gnu11; 568 | GCC_DYNAMIC_NO_PIC = NO; 569 | GCC_NO_COMMON_BLOCKS = YES; 570 | GCC_OPTIMIZATION_LEVEL = 0; 571 | GCC_PREPROCESSOR_DEFINITIONS = ( 572 | "DEBUG=1", 573 | "$(inherited)", 574 | ); 575 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 576 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 577 | GCC_WARN_UNDECLARED_SELECTOR = YES; 578 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 579 | GCC_WARN_UNUSED_FUNCTION = YES; 580 | GCC_WARN_UNUSED_VARIABLE = YES; 581 | MACOSX_DEPLOYMENT_TARGET = 10.15; 582 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 583 | MTL_FAST_MATH = YES; 584 | ONLY_ACTIVE_ARCH = YES; 585 | SDKROOT = macosx; 586 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 587 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 588 | }; 589 | name = Debug; 590 | }; 591 | 85BD8148241B35F00009A377 /* Release */ = { 592 | isa = XCBuildConfiguration; 593 | buildSettings = { 594 | ALWAYS_SEARCH_USER_PATHS = NO; 595 | CLANG_ANALYZER_NONNULL = YES; 596 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 597 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 598 | CLANG_CXX_LIBRARY = "libc++"; 599 | CLANG_ENABLE_MODULES = YES; 600 | CLANG_ENABLE_OBJC_ARC = YES; 601 | CLANG_ENABLE_OBJC_WEAK = YES; 602 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 603 | CLANG_WARN_BOOL_CONVERSION = YES; 604 | CLANG_WARN_COMMA = YES; 605 | CLANG_WARN_CONSTANT_CONVERSION = YES; 606 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 607 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 608 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 609 | CLANG_WARN_EMPTY_BODY = YES; 610 | CLANG_WARN_ENUM_CONVERSION = YES; 611 | CLANG_WARN_INFINITE_RECURSION = YES; 612 | CLANG_WARN_INT_CONVERSION = YES; 613 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 614 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 615 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 616 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 617 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 618 | CLANG_WARN_STRICT_PROTOTYPES = YES; 619 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 620 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 621 | CLANG_WARN_UNREACHABLE_CODE = YES; 622 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 623 | COPY_PHASE_STRIP = NO; 624 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 625 | ENABLE_NS_ASSERTIONS = NO; 626 | ENABLE_STRICT_OBJC_MSGSEND = YES; 627 | GCC_C_LANGUAGE_STANDARD = gnu11; 628 | GCC_NO_COMMON_BLOCKS = YES; 629 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 630 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 631 | GCC_WARN_UNDECLARED_SELECTOR = YES; 632 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 633 | GCC_WARN_UNUSED_FUNCTION = YES; 634 | GCC_WARN_UNUSED_VARIABLE = YES; 635 | MACOSX_DEPLOYMENT_TARGET = 10.15; 636 | MTL_ENABLE_DEBUG_INFO = NO; 637 | MTL_FAST_MATH = YES; 638 | SDKROOT = macosx; 639 | SWIFT_COMPILATION_MODE = wholemodule; 640 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 641 | }; 642 | name = Release; 643 | }; 644 | 85BD814A241B35F00009A377 /* Debug */ = { 645 | isa = XCBuildConfiguration; 646 | buildSettings = { 647 | CODE_SIGN_ENTITLEMENTS = "Clean Links Extension/Clean_Links_Extension.entitlements"; 648 | CODE_SIGN_IDENTITY = "Apple Development"; 649 | CODE_SIGN_STYLE = Automatic; 650 | CURRENT_PROJECT_VERSION = 12; 651 | DEVELOPMENT_TEAM = 4BJWF2898T; 652 | ENABLE_HARDENED_RUNTIME = YES; 653 | INFOPLIST_FILE = "Clean Links Extension/Info.plist"; 654 | LD_RUNPATH_SEARCH_PATHS = ( 655 | "$(inherited)", 656 | "@executable_path/../Frameworks", 657 | "@executable_path/../../../../Frameworks", 658 | ); 659 | MACOSX_DEPLOYMENT_TARGET = 10.12; 660 | MARKETING_VERSION = 1.2; 661 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-Links-Extension"; 662 | PRODUCT_NAME = "$(TARGET_NAME)"; 663 | SKIP_INSTALL = YES; 664 | SWIFT_VERSION = 5.0; 665 | }; 666 | name = Debug; 667 | }; 668 | 85BD814B241B35F00009A377 /* Release */ = { 669 | isa = XCBuildConfiguration; 670 | buildSettings = { 671 | CODE_SIGN_ENTITLEMENTS = "Clean Links Extension/Clean_Links_Extension.entitlements"; 672 | CODE_SIGN_IDENTITY = "Apple Development"; 673 | CODE_SIGN_STYLE = Automatic; 674 | CURRENT_PROJECT_VERSION = 12; 675 | DEVELOPMENT_TEAM = 4BJWF2898T; 676 | ENABLE_HARDENED_RUNTIME = YES; 677 | INFOPLIST_FILE = "Clean Links Extension/Info.plist"; 678 | LD_RUNPATH_SEARCH_PATHS = ( 679 | "$(inherited)", 680 | "@executable_path/../Frameworks", 681 | "@executable_path/../../../../Frameworks", 682 | ); 683 | MACOSX_DEPLOYMENT_TARGET = 10.12; 684 | MARKETING_VERSION = 1.2; 685 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-Links-Extension"; 686 | PRODUCT_NAME = "$(TARGET_NAME)"; 687 | SKIP_INSTALL = YES; 688 | SWIFT_VERSION = 5.0; 689 | }; 690 | name = Release; 691 | }; 692 | 85BD814E241B35F00009A377 /* Debug */ = { 693 | isa = XCBuildConfiguration; 694 | buildSettings = { 695 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 696 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 697 | CODE_SIGN_ENTITLEMENTS = "Clean Links/Clean_Links.entitlements"; 698 | CODE_SIGN_IDENTITY = "Apple Development"; 699 | CODE_SIGN_STYLE = Automatic; 700 | COMBINE_HIDPI_IMAGES = YES; 701 | CURRENT_PROJECT_VERSION = 12; 702 | DEVELOPMENT_TEAM = 4BJWF2898T; 703 | ENABLE_HARDENED_RUNTIME = YES; 704 | INFOPLIST_FILE = "Clean Links/Info.plist"; 705 | LD_RUNPATH_SEARCH_PATHS = ( 706 | "$(inherited)", 707 | "@executable_path/../Frameworks", 708 | ); 709 | MACOSX_DEPLOYMENT_TARGET = 10.12; 710 | MARKETING_VERSION = 1.2; 711 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-Links"; 712 | PRODUCT_NAME = "$(TARGET_NAME)"; 713 | SWIFT_VERSION = 5.0; 714 | }; 715 | name = Debug; 716 | }; 717 | 85BD814F241B35F00009A377 /* Release */ = { 718 | isa = XCBuildConfiguration; 719 | buildSettings = { 720 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 721 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 722 | CODE_SIGN_ENTITLEMENTS = "Clean Links/Clean_Links.entitlements"; 723 | CODE_SIGN_IDENTITY = "Apple Development"; 724 | CODE_SIGN_STYLE = Automatic; 725 | COMBINE_HIDPI_IMAGES = YES; 726 | CURRENT_PROJECT_VERSION = 12; 727 | DEVELOPMENT_TEAM = 4BJWF2898T; 728 | ENABLE_HARDENED_RUNTIME = YES; 729 | INFOPLIST_FILE = "Clean Links/Info.plist"; 730 | LD_RUNPATH_SEARCH_PATHS = ( 731 | "$(inherited)", 732 | "@executable_path/../Frameworks", 733 | ); 734 | MACOSX_DEPLOYMENT_TARGET = 10.12; 735 | MARKETING_VERSION = 1.2; 736 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-Links"; 737 | PRODUCT_NAME = "$(TARGET_NAME)"; 738 | SWIFT_VERSION = 5.0; 739 | }; 740 | name = Release; 741 | }; 742 | 85BD8151241B35F00009A377 /* Debug */ = { 743 | isa = XCBuildConfiguration; 744 | buildSettings = { 745 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 746 | BUNDLE_LOADER = "$(TEST_HOST)"; 747 | CODE_SIGN_STYLE = Automatic; 748 | COMBINE_HIDPI_IMAGES = YES; 749 | DEVELOPMENT_TEAM = 4BJWF2898T; 750 | INFOPLIST_FILE = "Clean LinksTests/Info.plist"; 751 | LD_RUNPATH_SEARCH_PATHS = ( 752 | "$(inherited)", 753 | "@executable_path/../Frameworks", 754 | "@loader_path/../Frameworks", 755 | ); 756 | MACOSX_DEPLOYMENT_TARGET = 10.15; 757 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-LinksTests"; 758 | PRODUCT_NAME = "$(TARGET_NAME)"; 759 | SWIFT_VERSION = 5.0; 760 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clean Links.app/Contents/MacOS/Clean Links"; 761 | }; 762 | name = Debug; 763 | }; 764 | 85BD8152241B35F00009A377 /* Release */ = { 765 | isa = XCBuildConfiguration; 766 | buildSettings = { 767 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 768 | BUNDLE_LOADER = "$(TEST_HOST)"; 769 | CODE_SIGN_STYLE = Automatic; 770 | COMBINE_HIDPI_IMAGES = YES; 771 | DEVELOPMENT_TEAM = 4BJWF2898T; 772 | INFOPLIST_FILE = "Clean LinksTests/Info.plist"; 773 | LD_RUNPATH_SEARCH_PATHS = ( 774 | "$(inherited)", 775 | "@executable_path/../Frameworks", 776 | "@loader_path/../Frameworks", 777 | ); 778 | MACOSX_DEPLOYMENT_TARGET = 10.15; 779 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-LinksTests"; 780 | PRODUCT_NAME = "$(TARGET_NAME)"; 781 | SWIFT_VERSION = 5.0; 782 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Clean Links.app/Contents/MacOS/Clean Links"; 783 | }; 784 | name = Release; 785 | }; 786 | 85BD8154241B35F00009A377 /* Debug */ = { 787 | isa = XCBuildConfiguration; 788 | buildSettings = { 789 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 790 | CODE_SIGN_STYLE = Automatic; 791 | COMBINE_HIDPI_IMAGES = YES; 792 | DEVELOPMENT_TEAM = 4BJWF2898T; 793 | INFOPLIST_FILE = "Clean LinksUITests/Info.plist"; 794 | LD_RUNPATH_SEARCH_PATHS = ( 795 | "$(inherited)", 796 | "@executable_path/../Frameworks", 797 | "@loader_path/../Frameworks", 798 | ); 799 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-LinksUITests"; 800 | PRODUCT_NAME = "$(TARGET_NAME)"; 801 | SWIFT_VERSION = 5.0; 802 | TEST_TARGET_NAME = "Clean Links"; 803 | }; 804 | name = Debug; 805 | }; 806 | 85BD8155241B35F00009A377 /* Release */ = { 807 | isa = XCBuildConfiguration; 808 | buildSettings = { 809 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 810 | CODE_SIGN_STYLE = Automatic; 811 | COMBINE_HIDPI_IMAGES = YES; 812 | DEVELOPMENT_TEAM = 4BJWF2898T; 813 | INFOPLIST_FILE = "Clean LinksUITests/Info.plist"; 814 | LD_RUNPATH_SEARCH_PATHS = ( 815 | "$(inherited)", 816 | "@executable_path/../Frameworks", 817 | "@loader_path/../Frameworks", 818 | ); 819 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-LinksUITests"; 820 | PRODUCT_NAME = "$(TARGET_NAME)"; 821 | SWIFT_VERSION = 5.0; 822 | TEST_TARGET_NAME = "Clean Links"; 823 | }; 824 | name = Release; 825 | }; 826 | 85DB5976241F91D200524FE9 /* Debug */ = { 827 | isa = XCBuildConfiguration; 828 | buildSettings = { 829 | CODE_SIGN_ENTITLEMENTS = "Content Blocker/Content_Blocker.entitlements"; 830 | CODE_SIGN_IDENTITY = "Apple Development"; 831 | CODE_SIGN_STYLE = Automatic; 832 | CURRENT_PROJECT_VERSION = 3; 833 | DEVELOPMENT_TEAM = 4BJWF2898T; 834 | ENABLE_HARDENED_RUNTIME = YES; 835 | INFOPLIST_FILE = "Content Blocker/Info.plist"; 836 | LD_RUNPATH_SEARCH_PATHS = ( 837 | "$(inherited)", 838 | "@executable_path/../Frameworks", 839 | "@executable_path/../../../../Frameworks", 840 | ); 841 | MACOSX_DEPLOYMENT_TARGET = 10.12; 842 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-Links.Content-Blocker"; 843 | PRODUCT_NAME = "$(TARGET_NAME)"; 844 | SKIP_INSTALL = YES; 845 | SWIFT_VERSION = 5.0; 846 | }; 847 | name = Debug; 848 | }; 849 | 85DB5977241F91D200524FE9 /* Release */ = { 850 | isa = XCBuildConfiguration; 851 | buildSettings = { 852 | CODE_SIGN_ENTITLEMENTS = "Content Blocker/Content_Blocker.entitlements"; 853 | CODE_SIGN_IDENTITY = "Apple Development"; 854 | CODE_SIGN_STYLE = Automatic; 855 | CURRENT_PROJECT_VERSION = 3; 856 | DEVELOPMENT_TEAM = 4BJWF2898T; 857 | ENABLE_HARDENED_RUNTIME = YES; 858 | INFOPLIST_FILE = "Content Blocker/Info.plist"; 859 | LD_RUNPATH_SEARCH_PATHS = ( 860 | "$(inherited)", 861 | "@executable_path/../Frameworks", 862 | "@executable_path/../../../../Frameworks", 863 | ); 864 | MACOSX_DEPLOYMENT_TARGET = 10.12; 865 | PRODUCT_BUNDLE_IDENTIFIER = "com.vitanov.Clean-Links.Content-Blocker"; 866 | PRODUCT_NAME = "$(TARGET_NAME)"; 867 | SKIP_INSTALL = YES; 868 | SWIFT_VERSION = 5.0; 869 | }; 870 | name = Release; 871 | }; 872 | /* End XCBuildConfiguration section */ 873 | 874 | /* Begin XCConfigurationList section */ 875 | 85BD8104241B35EE0009A377 /* Build configuration list for PBXProject "Clean Links" */ = { 876 | isa = XCConfigurationList; 877 | buildConfigurations = ( 878 | 85BD8147241B35F00009A377 /* Debug */, 879 | 85BD8148241B35F00009A377 /* Release */, 880 | ); 881 | defaultConfigurationIsVisible = 0; 882 | defaultConfigurationName = Release; 883 | }; 884 | 85BD8149241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean Links Extension" */ = { 885 | isa = XCConfigurationList; 886 | buildConfigurations = ( 887 | 85BD814A241B35F00009A377 /* Debug */, 888 | 85BD814B241B35F00009A377 /* Release */, 889 | ); 890 | defaultConfigurationIsVisible = 0; 891 | defaultConfigurationName = Release; 892 | }; 893 | 85BD814D241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean Links" */ = { 894 | isa = XCConfigurationList; 895 | buildConfigurations = ( 896 | 85BD814E241B35F00009A377 /* Debug */, 897 | 85BD814F241B35F00009A377 /* Release */, 898 | ); 899 | defaultConfigurationIsVisible = 0; 900 | defaultConfigurationName = Release; 901 | }; 902 | 85BD8150241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean LinksTests" */ = { 903 | isa = XCConfigurationList; 904 | buildConfigurations = ( 905 | 85BD8151241B35F00009A377 /* Debug */, 906 | 85BD8152241B35F00009A377 /* Release */, 907 | ); 908 | defaultConfigurationIsVisible = 0; 909 | defaultConfigurationName = Release; 910 | }; 911 | 85BD8153241B35F00009A377 /* Build configuration list for PBXNativeTarget "Clean LinksUITests" */ = { 912 | isa = XCConfigurationList; 913 | buildConfigurations = ( 914 | 85BD8154241B35F00009A377 /* Debug */, 915 | 85BD8155241B35F00009A377 /* Release */, 916 | ); 917 | defaultConfigurationIsVisible = 0; 918 | defaultConfigurationName = Release; 919 | }; 920 | 85DB5978241F91D200524FE9 /* Build configuration list for PBXNativeTarget "Content Blocker" */ = { 921 | isa = XCConfigurationList; 922 | buildConfigurations = ( 923 | 85DB5976241F91D200524FE9 /* Debug */, 924 | 85DB5977241F91D200524FE9 /* Release */, 925 | ); 926 | defaultConfigurationIsVisible = 0; 927 | defaultConfigurationName = Release; 928 | }; 929 | /* End XCConfigurationList section */ 930 | }; 931 | rootObject = 85BD8101241B35EE0009A377 /* Project object */; 932 | } 933 | -------------------------------------------------------------------------------- /Clean Links.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Clean Links.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Clean Links.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Clean Links.xcodeproj/project.xcworkspace/xcuserdata/radoslav.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links.xcodeproj/project.xcworkspace/xcuserdata/radoslav.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Clean Links.xcodeproj/project.xcworkspace/xcuserdata/radoslav.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Clean Links.xcodeproj/xcuserdata/radoslav.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Clean Links.xcodeproj/xcuserdata/radoslav.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Clean Links Extension.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | Clean Links.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | Content Blocker.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 0 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Clean Links/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Clean Links 4 | // 5 | // Created by Radoslav Vitanov on 13.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | func applicationDidFinishLaunching(_ aNotification: Notification) { 15 | // Insert code here to initialize your application 16 | } 17 | 18 | func applicationWillTerminate(_ aNotification: Notification) { 19 | // Insert code here to tear down your application 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "identification-2.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "identification-4.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "identification-5.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "identification-6.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "identification-7.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "identification-8.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "identification-9.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "identification.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "identification-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "idiom" : "mac", 59 | "size" : "512x512", 60 | "scale" : "2x" 61 | } 62 | ], 63 | "info" : { 64 | "version" : 1, 65 | "author" : "xcode" 66 | } 67 | } -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-1.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-2.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-4.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-5.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-6.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-7.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-8.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification-9.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/AppIcon.appiconset/identification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/AppIcon.appiconset/identification.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/Link.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "link.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "link-2.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "link-3.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/Link.imageset/link-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/Link.imageset/link-2.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/Link.imageset/link-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/Link.imageset/link-3.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/Link.imageset/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/Link.imageset/link.png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/ShieldCheck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "shield.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "shield (1).png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "shield (2).png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/ShieldCheck.imageset/shield (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/ShieldCheck.imageset/shield (1).png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/ShieldCheck.imageset/shield (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/ShieldCheck.imageset/shield (2).png -------------------------------------------------------------------------------- /Clean Links/Assets.xcassets/ShieldCheck.imageset/shield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/Clean Links/Assets.xcassets/ShieldCheck.imageset/shield.png -------------------------------------------------------------------------------- /Clean Links/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 | 22 | 23 | 24 | 25 | 26 | 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 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /Clean Links/Clean_Links.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Clean Links/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2020 Radoslav Vitanov. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | NSSupportsAutomaticTermination 32 | 33 | NSSupportsSuddenTermination 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Clean Links/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Clean Links 4 | // 5 | // Created by Radoslav Vitanov on 13.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SafariServices.SFSafariApplication 11 | 12 | class ViewController: NSViewController { 13 | 14 | @IBOutlet var appNameLabel: NSTextField! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | self.appNameLabel.stringValue = "Clean Links"; 19 | } 20 | 21 | @IBAction func openSafariExtensionPreferences(_ sender: AnyObject?) { 22 | SFSafariApplication.showPreferencesForExtension(withIdentifier: "com.vitanov.Clean-Links-Extension") { error in 23 | if let _ = error { 24 | // Insert code to inform the user that something went wrong. 25 | 26 | } 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Clean LinksTests/Clean_LinksTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Clean_LinksTests.swift 3 | // Clean LinksTests 4 | // 5 | // Created by Radoslav Vitanov on 13.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Clean_Links 11 | 12 | class Clean_LinksTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Clean LinksTests/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 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Clean LinksUITests/Clean_LinksUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Clean_LinksUITests.swift 3 | // Clean LinksUITests 4 | // 5 | // Created by Radoslav Vitanov on 13.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class Clean_LinksUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Clean LinksUITests/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 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Content Blocker/ContentBlockerRequestHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentBlockerRequestHandler.swift 3 | // Content Blocker 4 | // 5 | // Created by Radoslav Vitanov on 16.03.20. 6 | // Copyright © 2020 Radoslav Vitanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling { 12 | 13 | func beginRequest(with context: NSExtensionContext) { 14 | let attachment = NSItemProvider(contentsOf: Bundle.main.url(forResource: "blockerList", withExtension: "json"))! 15 | 16 | let item = NSExtensionItem() 17 | item.attachments = [attachment] 18 | 19 | context.completeRequest(returningItems: [item], completionHandler: nil) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Content Blocker/Content_Blocker.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Content Blocker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Clean Links Content Blocker 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionPointIdentifier 28 | com.apple.Safari.content-blocker 29 | NSExtensionPrincipalClass 30 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler 31 | 32 | NSHumanReadableCopyright 33 | Copyright © 2020 Radoslav Vitanov. All rights reserved. 34 | NSHumanReadableDescription 35 | This is a Safari Content Blocker which prevents known tracking urls to be accessed. 36 | 37 | 38 | -------------------------------------------------------------------------------- /Content Blocker/blockerList.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "action": { 4 | "type": "block" 5 | }, 6 | "trigger": { 7 | "url-filter": "(.*)facebook.com/si(.*)" 8 | } 9 | }, 10 | { 11 | "action": { 12 | "type": "block" 13 | }, 14 | "trigger": { 15 | "url-filter": "(.*)facebook.com/ajax/bz(.*)" 16 | } 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Radoslav Vitanov (https://github.com/Sh1d0w) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Clean Links

4 |

5 | What happens in your browser, stays in your browser. 6 |

7 |
8 |
9 |
10 |
11 | 12 | This is a Safari App Extension, which strips all popular tracking query parameters off the links you click. No more `fbclid` or `gclid`! 13 | 14 | # Requirements 15 | 16 | The extension is available in macOS 10.12 and later. 17 | 18 | # Installation 19 | 20 | To install *Clean Links* you can either download the latest prebuild extension from the [releases](https://github.com/Sh1d0w/clean-links/releases) tab, or you can clone the repository and build it yourself. 21 | 22 | After that double click on the extension to install it. Navigate to `Safari -> Preferences -> Extensions` and make sure *Clean Links Extension* and *Clean Links Content Blocker* are enabled. 23 | 24 | You should see the extension icon next to the url bar, indicating that the extension has been successfully activated. 25 | 26 | 27 | 28 | # FAQ 29 | 30 | ### What tracking parameters does the plugin currently strips? 31 | 32 | Tracking tokens from the following services are stripped: 33 | 34 | - [UTM parameters](https://en.wikipedia.org/wiki/UTM_parameters) used by Google Analytics 35 | - [DoubleClick Click Identifier](https://en.wikipedia.org/wiki/DoubleClick_Click_Identifier) (dclid), used by DoubleClick, now Google 36 | - [Facebook Click Identifier](https://en.wikipedia.org/wiki/Facebook_Click_Identifier) (fbclid) used by Facebook in social media analytics 37 | - [Google Click Identifier](https://en.wikipedia.org/wiki/Google_Click_Identifier) (gclid and gclsrc), used by Google Ads 38 | - [Microsoft Click Identifier](https://en.wikipedia.org/wiki/Microsoft_Click_Identifier) (msclkid), used by Bing Ads 39 | - [Zanox click identifier](https://en.wikipedia.org/wiki/Zanox_click_identifier) (zanpid), used by Awin 40 | - Mailchimp (mc_eid) 41 | - Yandex (_openstat) 42 | - HubSpot (_hsenc, _hsmi) 43 | 44 | **Clean Links** also does more than just removing tracking parameters, you can read more about it's privacy features in the [Privacy](./docs/privacy.md) section. 45 | 46 | # Roadmap 47 | 48 | - [ ] Possibly publish the extension to the Mac App Store, in order to provide auto updates 49 | - [ ] Make the main app screen more user friendly 50 | 51 | ## Support 52 | 53 | If this extension was useful to you or you want to support the project, I would be very grateful if you buy me a cup of coffee. 54 | 55 | Buy Me A Coffee 56 | 57 | # Resources 58 | 59 | - Icon made by [Pixel perfect](https://www.flaticon.com/authors/pixel-perfect) from www.flaticon.com 60 | 61 | ## License 62 | 63 | MIT 64 | -------------------------------------------------------------------------------- /assets/fingerprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/assets/fingerprint.png -------------------------------------------------------------------------------- /assets/toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sh1d0w/clean-links/52db4fcd75d2a9f28339dc3dcc4969edad24b440/assets/toolbar.png -------------------------------------------------------------------------------- /docs/privacy.md: -------------------------------------------------------------------------------- 1 | # Protecting your privacy 2 | 3 | *We are actively working on detecting and adding more privacy preserving features. Keep eye on this document.* 4 | 5 | 6 | By default big tech companies like Facebook or Google do a lot of dirty tricks to track you in background when you are on their platforms, without you even noticing. 7 | 8 | This plugin takes the responsibility to protect you as much as possible from this. 9 | 10 | Below you can find what we prevent on each platform. 11 | 12 | Table of contents 13 | ================= 14 | 15 | 16 | * [Facebook](#facebook) 17 | * [Google](#google) 18 | 19 | 20 | ## Facebook 21 | 22 | ### Click tracking 23 | 24 | When you click on an external link while browsing `facebook.com` (let's say some article linked from a post), several things happen. 25 | 26 | - **Facebook** adds `fbclid` query parameter to the url you are about to visit. This allows FB to track that you have clicked the link and allows them to further track your activity when you land on that external page. 27 | - **Facebook** also does an AJAX request to `https://www.facebook.com/si/linkclick/ajax_callback/` with a lot of personal data and again tracks your click, browser etc. 28 | - **Facebook** replaces the original article link with `l.facebook.com/l.php?u=http://the-real-url.com` just in case you have javascript enabled or you try to copy the link to the article to a friend. This again tracks your click. 29 | 30 | 31 | ### Presence tracking 32 | 33 | Periodically **Facebook** pings `https://www.facebook.com/ajax/bz` with a lot of personal data as well, indicating what you are looking to, time spent etc. 34 | 35 | ### What *Clean Links* do to prevent all this? 36 | 37 | - **Clean Links** removes `fbclid` query parameter from the links you visit. 38 | - **Clean Links** blocks all ajax requests to `https://www.facebook.com/si/linkclick/ajax_callback/` 39 | - **Clean Links** blocks all ajax requests to `https://www.facebook.com/ajax/bz` 40 | - **Clean Links** prevents opening links from `l.facebook.com` domain and instead will open the real domain you intended to open, without letting **Facebook** to track you. 41 | 42 | ## Google 43 | 44 | ### Click tracking 45 | 46 | - When you search for something on **Google** and you click on a search result, **Google** silently swaps the real url with `https://google.com/url?u=https://you-real-url&more-tracking-params` and tracks your click. 47 | 48 | ### What *Clean Links* do to prevent all this? 49 | 50 | - **Clean Links** prevents opening links like `https://google.com/url?u=...` and instead opens the real link you've intended to open, without letting google track you. 51 | --------------------------------------------------------------------------------