Merge remote-tracking branch 'origin/master' into meta-dev
# Conflicts: # .github/workflows/main.yml # ClashX.xcodeproj/project.pbxproj # ClashX/goClash/go.mod # ClashX/goClash/go.sum
This commit is contained in:
commit
fe814bf803
|
@ -22,6 +22,7 @@
|
|||
01E33AB229B5BF4200FD1006 /* NSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01E33AB129B5BF4200FD1006 /* NSColor+Extension.swift */; };
|
||||
01E33AB529B5C5E400FD1006 /* menu_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 01E33AB429B5C5E300FD1006 /* menu_icon@2x.png */; };
|
||||
185CBAEDFE986E6E1B836359 /* libPods-ClashX Meta.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AA63125BBF03DC1A291D3351 /* libPods-ClashX Meta.a */; };
|
||||
4908087B29F8F405007A4944 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4908087A29F8F3FF007A4944 /* libresolv.tbd */; };
|
||||
4913C82321157D0200F6B87C /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4913C82221157D0200F6B87C /* Notification.swift */; };
|
||||
491E6203258A424D00313AEF /* CommonUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 491E61FD258A424500313AEF /* CommonUtils.m */; };
|
||||
49228457270AADE20027A4B6 /* RemoteConfigUpdateIntervalSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49228456270AADE20027A4B6 /* RemoteConfigUpdateIntervalSettingView.swift */; };
|
||||
|
@ -156,6 +157,7 @@
|
|||
01E33AB129B5BF4200FD1006 /* NSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor+Extension.swift"; sourceTree = "<group>"; };
|
||||
01E33AB429B5C5E300FD1006 /* menu_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icon@2x.png"; sourceTree = "<group>"; };
|
||||
3F86DA2DA3CC14731BE1ABF7 /* Pods-ClashX Meta.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ClashX Meta.release.xcconfig"; path = "Pods/Target Support Files/Pods-ClashX Meta/Pods-ClashX Meta.release.xcconfig"; sourceTree = "<group>"; };
|
||||
4908087A29F8F3FF007A4944 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
4913C82221157D0200F6B87C /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||
491E61FC258A424500313AEF /* CommonUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonUtils.h; sourceTree = "<group>"; };
|
||||
491E61FD258A424500313AEF /* CommonUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CommonUtils.m; sourceTree = "<group>"; };
|
||||
|
@ -266,6 +268,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4908087B29F8F405007A4944 /* libresolv.tbd in Frameworks */,
|
||||
185CBAEDFE986E6E1B836359 /* libPods-ClashX Meta.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -544,6 +547,7 @@
|
|||
CF1AC9FACC36FCE7663C5583 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4908087A29F8F3FF007A4944 /* libresolv.tbd */,
|
||||
AA63125BBF03DC1A291D3351 /* libPods-ClashX Meta.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
|
|
|
@ -154,7 +154,7 @@ class PrivilegedHelperManager {
|
|||
case needUpdate
|
||||
}
|
||||
|
||||
private func getHelperStatus(callback:@escaping ((HelperStatus) -> Void)) {
|
||||
private func getHelperStatus(callback: @escaping ((HelperStatus) -> Void)) {
|
||||
var called = false
|
||||
let reply: ((HelperStatus) -> Void) = {
|
||||
status in
|
||||
|
@ -278,7 +278,6 @@ private enum DaemonInstallResult {
|
|||
case kSMErrorToolNotValid: return "blessError: kSMErrorToolNotValid"
|
||||
case kSMErrorJobNotFound: return "blessError: kSMErrorJobNotFound"
|
||||
case kSMErrorServiceUnavailable: return "blessError: kSMErrorServiceUnavailable"
|
||||
case kSMErrorJobNotFound: return "blessError: kSMErrorJobNotFound"
|
||||
case kSMErrorJobMustBeEnabled: return "ClashX Helper is disabled by other process. Please run \"sudo launchctl enable system/\(PrivilegedHelperManager.machServiceName)\" in your terminal. The command has been copied to your pasteboard"
|
||||
case kSMErrorInvalidPlist: return "blessError: kSMErrorInvalidPlist"
|
||||
default:
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
import Foundation
|
||||
enum Settings {
|
||||
@UserDefault("mmdbDownloadUrl", defaultValue: "")
|
||||
static var mmdbDownloadUrl:String
|
||||
static var mmdbDownloadUrl: String
|
||||
|
||||
@UserDefault("filterInterface", defaultValue: true)
|
||||
static var filterInterface:Bool
|
||||
static var filterInterface: Bool
|
||||
|
||||
@UserDefault("disableNoti", defaultValue: false)
|
||||
static var disableNoti:Bool
|
||||
static var disableNoti: Bool
|
||||
|
||||
@UserDefault("usePacMode", defaultValue: false)
|
||||
static var usePacMode:Bool
|
||||
static var usePacMode: Bool
|
||||
|
||||
@UserDefault("configAutoUpdateInterval", defaultValue: 48*60*60)
|
||||
static var configAutoUpdateInterval: TimeInterval
|
||||
|
|
|
@ -78,7 +78,7 @@ class NetworkChangeNotifier {
|
|||
return (httpProxy, httpsProxy, socksProxy)
|
||||
}
|
||||
|
||||
static func isCurrentSystemSetToClash(looser:Bool = false) -> Bool {
|
||||
static func isCurrentSystemSetToClash(looser: Bool = false) -> Bool {
|
||||
let (http, https, socks) = NetworkChangeNotifier.currentSystemProxySetting()
|
||||
let currentPort = ConfigManager.shared.currentConfig?.usedHttpPort ?? 0
|
||||
let currentSocks = ConfigManager.shared.currentConfig?.usedSocksPort ?? 0
|
||||
|
@ -99,7 +99,7 @@ class NetworkChangeNotifier {
|
|||
let sets = SCPreferencesGetValue(prefRef, kSCPrefNetworkServices) {
|
||||
for key in sets.allKeys {
|
||||
let dict = sets[key] as? NSDictionary
|
||||
let proxySettings = dict?["Proxies"] as? [String:Any]
|
||||
let proxySettings = dict?["Proxies"] as? [String: Any]
|
||||
if currentPort != nil {
|
||||
if proxySettings?[kCFNetworkProxiesHTTPPort as String] as? Int == currentPort ||
|
||||
proxySettings?[kCFNetworkProxiesHTTPSPort as String] as? Int == currentPort {
|
||||
|
|
|
@ -69,7 +69,7 @@ public final class LoginServiceKit {
|
|||
guard isExistLoginItems(at: path) == false else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if #available(macOS 13.0, *) {
|
||||
do {
|
||||
try SMAppService.mainApp.register()
|
||||
|
@ -79,7 +79,7 @@ public final class LoginServiceKit {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
guard let (list, _) = snapshot else {
|
||||
return false
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ public final class LoginServiceKit {
|
|||
guard isExistLoginItems(at: path) == true else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if #available(macOS 13.0, *) {
|
||||
do {
|
||||
try SMAppService.mainApp.unregister()
|
||||
|
|
|
@ -216,7 +216,7 @@ class CustomWKWebView: WKWebView {
|
|||
var dragableAreaHeight: CGFloat = 30
|
||||
let alwaysDragableLeftAreaWidth: CGFloat = 150
|
||||
|
||||
private func isInDargArea(with event:NSEvent?) -> Bool {
|
||||
private func isInDargArea(with event: NSEvent?) -> Bool {
|
||||
guard let event = event else { return false }
|
||||
let x = event.locationInWindow.x
|
||||
let y = (window?.frame.size.height ?? 0) - event.locationInWindow.y
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import AppKit
|
||||
import SwiftUI
|
||||
@available(macOS 10.15, *)
|
||||
class NewStatusMenuView:NSHostingView<SwiftUIView>, StatusItemViewProtocol {
|
||||
class NewStatusMenuView: NSHostingView<SwiftUIView>, StatusItemViewProtocol {
|
||||
private var viewModel: StatusMenuViewModel!
|
||||
|
||||
static func create(on button: NSView) -> NewStatusMenuView {
|
||||
|
@ -45,7 +45,7 @@ class NewStatusMenuView:NSHostingView<SwiftUIView>, StatusItemViewProtocol {
|
|||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
class StatusMenuViewModel:ObservableObject {
|
||||
class StatusMenuViewModel: ObservableObject {
|
||||
@Published var image = StatusItemTool.getMenuImage(enableProxy: false)
|
||||
@Published var upSpeed = "0KB/s"
|
||||
@Published var downSpeed = "0KB/s"
|
||||
|
@ -57,7 +57,9 @@ struct SwiftUIView: View {
|
|||
@ObservedObject var viewModel: StatusMenuViewModel
|
||||
var body: some View {
|
||||
HStack(alignment:.center) {
|
||||
Image(nsImage: $viewModel.image.wrappedValue).renderingMode(.template)
|
||||
Image(nsImage: $viewModel.image.wrappedValue).renderingMode(.template)
|
||||
.resizable().aspectRatio(contentMode: .fit).frame(width: 16,height: 16)
|
||||
|
||||
if $viewModel.showSpeed.wrappedValue {
|
||||
Spacer(minLength: 0)
|
||||
VStack(alignment: .trailing) {
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
#! /usr/bin/python
|
||||
#
|
||||
#! /usr/bin/python3
|
||||
#
|
||||
# File: SMJobBlessUtil.py
|
||||
#
|
||||
#
|
||||
# Contains: Tool for checking and correcting apps that use SMJobBless.
|
||||
#
|
||||
#
|
||||
# Written by: DTS
|
||||
#
|
||||
#
|
||||
# Copyright: Copyright (c) 2012 Apple Inc. All Rights Reserved.
|
||||
#
|
||||
#
|
||||
# Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
|
||||
# ("Apple") in consideration of your agreement to the following
|
||||
# terms, and your use, installation, modification or
|
||||
# redistribution of this Apple software constitutes acceptance of
|
||||
# these terms. If you do not agree with these terms, please do
|
||||
# not use, install, modify or redistribute this Apple software.
|
||||
#
|
||||
#
|
||||
# In consideration of your agreement to abide by the following
|
||||
# terms, and subject to these terms, Apple grants you a personal,
|
||||
# non-exclusive license, under Apple's copyrights in this
|
||||
|
@ -32,14 +32,14 @@
|
|||
# are granted by Apple herein, including but not limited to any
|
||||
# patent rights that may be infringed by your derivative works or
|
||||
# by other works in which the Apple Software may be incorporated.
|
||||
#
|
||||
# The Apple Software is provided by Apple on an "AS IS" basis.
|
||||
#
|
||||
# The Apple Software is provided by Apple on an "AS IS" basis.
|
||||
# APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
|
||||
# WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
|
||||
# THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
|
||||
# COMBINATION WITH YOUR PRODUCTS.
|
||||
#
|
||||
#
|
||||
# IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
|
||||
# INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
|
@ -49,7 +49,7 @@
|
|||
# OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
|
||||
# OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
#
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
@ -57,17 +57,18 @@ import getopt
|
|||
import subprocess
|
||||
import plistlib
|
||||
import operator
|
||||
import platform
|
||||
|
||||
class UsageException (Exception):
|
||||
"""
|
||||
Raised when the progam detects a usage issue; the top-level code catches this
|
||||
Raised when the progam detects a usage issue; the top-level code catches this
|
||||
and prints a usage message.
|
||||
"""
|
||||
pass
|
||||
|
||||
class CheckException (Exception):
|
||||
"""
|
||||
Raised when the "check" subcommand detects a problem; the top-level code catches
|
||||
Raised when the "check" subcommand detects a problem; the top-level code catches
|
||||
this and prints a nice error message.
|
||||
"""
|
||||
def __init__(self, message, path=None):
|
||||
|
@ -77,38 +78,38 @@ class CheckException (Exception):
|
|||
def checkCodeSignature(programPath, programType):
|
||||
"""Checks the code signature of the referenced program."""
|
||||
|
||||
# Use the codesign tool to check the signature. The second "-v" is required to enable
|
||||
# verbose mode, which causes codesign to do more checking. By default it does the minimum
|
||||
# amount of checking ("Is the program properly signed?"). If you enabled verbose mode it
|
||||
# does other sanity checks, which we definitely want. The specific thing I'd like to
|
||||
# detect is "Does the code satisfy its own designated requirement?" and I need to enable
|
||||
# Use the codesign tool to check the signature. The second "-v" is required to enable
|
||||
# verbose mode, which causes codesign to do more checking. By default it does the minimum
|
||||
# amount of checking ("Is the program properly signed?"). If you enabled verbose mode it
|
||||
# does other sanity checks, which we definitely want. The specific thing I'd like to
|
||||
# detect is "Does the code satisfy its own designated requirement?" and I need to enable
|
||||
# verbose mode to get that.
|
||||
|
||||
args = [
|
||||
# "false",
|
||||
"codesign",
|
||||
"-v",
|
||||
# "false",
|
||||
"codesign",
|
||||
"-v",
|
||||
"-v",
|
||||
programPath
|
||||
]
|
||||
try:
|
||||
subprocess.check_call(args, stderr=open("/dev/null"))
|
||||
except subprocess.CalledProcessError, e:
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise CheckException("%s code signature invalid" % programType, programPath)
|
||||
|
||||
|
||||
def readDesignatedRequirement(programPath, programType):
|
||||
"""Returns the designated requirement of the program as a string."""
|
||||
args = [
|
||||
# "false",
|
||||
"codesign",
|
||||
"-d",
|
||||
"-r",
|
||||
"-",
|
||||
# "false",
|
||||
"codesign",
|
||||
"-d",
|
||||
"-r",
|
||||
"-",
|
||||
programPath
|
||||
]
|
||||
try:
|
||||
req = subprocess.check_output(args, stderr=open("/dev/null"))
|
||||
except subprocess.CalledProcessError, e:
|
||||
req = subprocess.check_output(args, stderr=open("/dev/null"), encoding="utf-8")
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise CheckException("%s designated requirement unreadable" % programType, programPath)
|
||||
|
||||
reqLines = req.splitlines()
|
||||
|
@ -119,77 +120,97 @@ def readDesignatedRequirement(programPath, programType):
|
|||
def readInfoPlistFromPath(infoPath):
|
||||
"""Reads an "Info.plist" file from the specified path."""
|
||||
try:
|
||||
info = plistlib.readPlist(infoPath)
|
||||
with open(infoPath, 'rb') as fp:
|
||||
info = plistlib.load(fp)
|
||||
except:
|
||||
raise CheckException("'Info.plist' not readable", infoPath)
|
||||
if not isinstance(info, dict):
|
||||
raise CheckException("'Info.plist' root must be a dictionary", infoPath)
|
||||
return info
|
||||
|
||||
|
||||
def readPlistFromToolSection(toolPath, segmentName, sectionName):
|
||||
"""Reads a dictionary property list from the specified section within the specified executable."""
|
||||
|
||||
|
||||
# Run otool -s to get a hex dump of the section.
|
||||
|
||||
|
||||
args = [
|
||||
# "false",
|
||||
"otool",
|
||||
"-s",
|
||||
segmentName,
|
||||
sectionName,
|
||||
# "false",
|
||||
"otool",
|
||||
"-V",
|
||||
"-arch",
|
||||
platform.machine(),
|
||||
"-s",
|
||||
segmentName,
|
||||
sectionName,
|
||||
toolPath
|
||||
]
|
||||
try:
|
||||
plistDump = subprocess.check_output(args)
|
||||
except subprocess.CalledProcessError, e:
|
||||
plistDump = subprocess.check_output(args, encoding="utf-8")
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise CheckException("tool %s / %s section unreadable" % (segmentName, sectionName), toolPath)
|
||||
|
||||
# Convert that hex dump to an property list.
|
||||
|
||||
plistLines = plistDump.splitlines()
|
||||
if len(plistLines) < 3 or plistLines[1] != ("Contents of (%s,%s) section" % (segmentName, sectionName)):
|
||||
# Convert that dump to an property list.
|
||||
|
||||
plistLines = plistDump.strip().splitlines(keepends=True)
|
||||
|
||||
if len(plistLines) < 3:
|
||||
raise CheckException("tool %s / %s section dump malformed (1)" % (segmentName, sectionName), toolPath)
|
||||
|
||||
header = plistLines[1].strip()
|
||||
|
||||
if not header.endswith("(%s,%s) section" % (segmentName, sectionName)):
|
||||
raise CheckException("tool %s / %s section dump malformed (2)" % (segmentName, sectionName), toolPath)
|
||||
|
||||
del plistLines[0:2]
|
||||
|
||||
try:
|
||||
bytes = []
|
||||
for line in plistLines:
|
||||
# line looks like this:
|
||||
#
|
||||
# '0000000100000b80\t3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 '
|
||||
columns = line.split("\t")
|
||||
assert len(columns) == 2
|
||||
for hexStr in columns[1].split():
|
||||
bytes.append(int(hexStr, 16))
|
||||
plist = plistlib.readPlistFromString(bytearray(bytes))
|
||||
|
||||
if header.startswith('Contents of'):
|
||||
data = []
|
||||
for line in plistLines:
|
||||
# line looks like this:
|
||||
#
|
||||
# '100000000 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 |<?xml version="1|'
|
||||
parts = line.split('|')
|
||||
assert len(parts) == 3
|
||||
columns = parts[0].split()
|
||||
assert len(columns) >= 2
|
||||
del columns[0]
|
||||
for hexStr in columns:
|
||||
data.append(int(hexStr, 16))
|
||||
data = bytes(data)
|
||||
else:
|
||||
data = bytes("".join(plistLines), encoding="utf-8")
|
||||
|
||||
plist = plistlib.loads(data)
|
||||
except:
|
||||
raise CheckException("tool %s / %s section dump malformed (2)" % (segmentName, sectionName), toolPath)
|
||||
raise CheckException("tool %s / %s section dump malformed (3)" % (segmentName, sectionName), toolPath)
|
||||
|
||||
# Check the root of the property list.
|
||||
|
||||
|
||||
if not isinstance(plist, dict):
|
||||
raise CheckException("tool %s / %s property list root must be a dictionary" % (segmentName, sectionName), toolPath)
|
||||
|
||||
return plist
|
||||
|
||||
|
||||
def checkStep1(appPath):
|
||||
"""Checks that the app and the tool are both correctly code signed."""
|
||||
|
||||
|
||||
if not os.path.isdir(appPath):
|
||||
raise CheckException("app not found", appPath)
|
||||
|
||||
|
||||
# Check the app's code signature.
|
||||
|
||||
|
||||
checkCodeSignature(appPath, "app")
|
||||
|
||||
|
||||
# Check the tool directory.
|
||||
|
||||
|
||||
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
|
||||
if not os.path.isdir(toolDirPath):
|
||||
raise CheckException("tool directory not found", toolDirPath)
|
||||
|
||||
# Check each tool's code signature.
|
||||
|
||||
|
||||
toolPathList = []
|
||||
for toolName in os.listdir(toolDirPath):
|
||||
if toolName != ".DS_Store":
|
||||
|
@ -200,50 +221,50 @@ def checkStep1(appPath):
|
|||
toolPathList.append(toolPath)
|
||||
|
||||
# Check that we have at least one tool.
|
||||
|
||||
|
||||
if len(toolPathList) == 0:
|
||||
raise CheckException("no tools found", toolDirPath)
|
||||
|
||||
return toolPathList
|
||||
|
||||
|
||||
def checkStep2(appPath, toolPathList):
|
||||
"""Checks the SMPrivilegedExecutables entry in the app's "Info.plist"."""
|
||||
|
||||
# Create a map from the tool name (not path) to its designated requirement.
|
||||
|
||||
|
||||
toolNameToReqMap = dict()
|
||||
for toolPath in toolPathList:
|
||||
req = readDesignatedRequirement(toolPath, "tool")
|
||||
toolNameToReqMap[os.path.basename(toolPath)] = req
|
||||
|
||||
|
||||
# Read the Info.plist for the app and extract the SMPrivilegedExecutables value.
|
||||
|
||||
|
||||
infoPath = os.path.join(appPath, "Contents", "Info.plist")
|
||||
info = readInfoPlistFromPath(infoPath)
|
||||
if not info.has_key("SMPrivilegedExecutables"):
|
||||
if "SMPrivilegedExecutables" not in info:
|
||||
raise CheckException("'SMPrivilegedExecutables' not found", infoPath)
|
||||
infoToolDict = info["SMPrivilegedExecutables"]
|
||||
if not isinstance(infoToolDict, dict):
|
||||
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", infoPath)
|
||||
|
||||
|
||||
# Check that the list of tools matches the list of SMPrivilegedExecutables entries.
|
||||
|
||||
|
||||
if sorted(infoToolDict.keys()) != sorted(toolNameToReqMap.keys()):
|
||||
raise CheckException("'SMPrivilegedExecutables' and tools in 'Contents/Library/LaunchServices' don't match")
|
||||
|
||||
|
||||
# Check that all the requirements match.
|
||||
|
||||
# This is an interesting policy choice. Technically the tool just needs to match
|
||||
# the requirement listed in SMPrivilegedExecutables, and we can check that by
|
||||
|
||||
# This is an interesting policy choice. Technically the tool just needs to match
|
||||
# the requirement listed in SMPrivilegedExecutables, and we can check that by
|
||||
# putting the requirement into tmp.req and then running
|
||||
#
|
||||
# $ codesign -v -R tmp.req /path/to/tool
|
||||
#
|
||||
# However, for a Developer ID signed tool we really want to have the SMPrivilegedExecutables
|
||||
# entry contain the tool's designated requirement because Xcode has built a
|
||||
# more complex DR that does lots of useful and important checks. So, as a matter
|
||||
# However, for a Developer ID signed tool we really want to have the SMPrivilegedExecutables
|
||||
# entry contain the tool's designated requirement because Xcode has built a
|
||||
# more complex DR that does lots of useful and important checks. So, as a matter
|
||||
# of policy we require that the value in SMPrivilegedExecutables match the tool's DR.
|
||||
|
||||
|
||||
for toolName in infoToolDict:
|
||||
if infoToolDict[toolName] != toolNameToReqMap[toolName]:
|
||||
raise CheckException("tool designated requirement (%s) doesn't match entry in 'SMPrivilegedExecutables' (%s)" % (toolNameToReqMap[toolName], infoToolDict[toolName]))
|
||||
|
@ -252,29 +273,29 @@ def checkStep3(appPath, toolPathList):
|
|||
"""Checks the "Info.plist" embedded in each helper tool."""
|
||||
|
||||
# First get the app's designated requirement.
|
||||
|
||||
|
||||
appReq = readDesignatedRequirement(appPath, "app")
|
||||
|
||||
# Then check that the tool's SMAuthorizedClients value matches it.
|
||||
|
||||
# Then check that the tool's SMAuthorizedClients value matches it.
|
||||
|
||||
for toolPath in toolPathList:
|
||||
info = readPlistFromToolSection(toolPath, "__TEXT", "__info_plist")
|
||||
|
||||
if not info.has_key("CFBundleInfoDictionaryVersion") or info["CFBundleInfoDictionaryVersion"] != "6.0":
|
||||
if "CFBundleInfoDictionaryVersion" not in info or info["CFBundleInfoDictionaryVersion"] != "6.0":
|
||||
raise CheckException("'CFBundleInfoDictionaryVersion' in tool __TEXT / __info_plist section must be '6.0'", toolPath)
|
||||
|
||||
if not info.has_key("CFBundleIdentifier") or info["CFBundleIdentifier"] != os.path.basename(toolPath):
|
||||
if "CFBundleIdentifier" not in info or info["CFBundleIdentifier"] != os.path.basename(toolPath):
|
||||
raise CheckException("'CFBundleIdentifier' in tool __TEXT / __info_plist section must match tool name", toolPath)
|
||||
|
||||
if not info.has_key("SMAuthorizedClients"):
|
||||
if "SMAuthorizedClients" not in info:
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section not found", toolPath)
|
||||
infoClientList = info["SMAuthorizedClients"]
|
||||
if not isinstance(infoClientList, list):
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must be an array", toolPath)
|
||||
if len(infoClientList) != 1:
|
||||
raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must have one entry", toolPath)
|
||||
|
||||
# Again, as a matter of policy we require that the SMAuthorizedClients entry must
|
||||
|
||||
# Again, as a matter of policy we require that the SMAuthorizedClients entry must
|
||||
# match exactly the designated requirement of the app.
|
||||
|
||||
if infoClientList[0] != appReq:
|
||||
|
@ -286,22 +307,22 @@ def checkStep4(appPath, toolPathList):
|
|||
for toolPath in toolPathList:
|
||||
launchd = readPlistFromToolSection(toolPath, "__TEXT", "__launchd_plist")
|
||||
|
||||
if not launchd.has_key("Label") or launchd["Label"] != os.path.basename(toolPath):
|
||||
if "Label" not in launchd or launchd["Label"] != os.path.basename(toolPath):
|
||||
raise CheckException("'Label' in tool __TEXT / __launchd_plist section must match tool name", toolPath)
|
||||
|
||||
# We don't need to check that the label matches the bundle identifier because
|
||||
# we know it matches the tool name and step 4 checks that the tool name matches
|
||||
# We don't need to check that the label matches the bundle identifier because
|
||||
# we know it matches the tool name and step 4 checks that the tool name matches
|
||||
# the bundle identifier.
|
||||
|
||||
def checkStep5(appPath):
|
||||
"""There's nothing to do here; we effectively checked for this is steps 1 and 2."""
|
||||
pass
|
||||
|
||||
|
||||
def check(appPath):
|
||||
"""Checks the SMJobBless setup of the specified app."""
|
||||
|
||||
# Each of the following steps matches a bullet point in the SMJobBless header doc.
|
||||
|
||||
|
||||
toolPathList = checkStep1(appPath)
|
||||
|
||||
checkStep2(appPath, toolPathList)
|
||||
|
@ -314,7 +335,7 @@ def check(appPath):
|
|||
|
||||
def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
|
||||
"""
|
||||
Reads information from the built app and uses it to set the SMJobBless setup
|
||||
Reads information from the built app and uses it to set the SMJobBless setup
|
||||
in the specified app and tool Info.plist source files.
|
||||
"""
|
||||
|
||||
|
@ -328,20 +349,16 @@ def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
|
|||
raise CheckException("app 'Info.plist' not found", toolInfoPlistPath)
|
||||
|
||||
# Get the designated requirement for the app and each of the tools.
|
||||
|
||||
|
||||
appReq = readDesignatedRequirement(appPath, "app")
|
||||
|
||||
toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
|
||||
if not os.path.isdir(toolDirPath):
|
||||
raise CheckException("tool directory not found", toolDirPath)
|
||||
|
||||
|
||||
toolNameToReqMap = {}
|
||||
print os.listdir(toolDirPath)
|
||||
for toolName in os.listdir(toolDirPath):
|
||||
req = readDesignatedRequirement(os.path.join(toolDirPath, toolName), "tool")
|
||||
print '-----'
|
||||
print toolName
|
||||
print '-----'
|
||||
toolNameToReqMap[toolName] = req
|
||||
|
||||
if len(toolNameToReqMap) > len(toolInfoPlistPaths):
|
||||
|
@ -350,58 +367,60 @@ def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
|
|||
raise CheckException("tool directory has fewer tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
|
||||
|
||||
# Build the new value for SMPrivilegedExecutables.
|
||||
|
||||
|
||||
appToolDict = {}
|
||||
toolInfoPlistPathToToolInfoMap = {}
|
||||
for toolInfoPlistPath in toolInfoPlistPaths:
|
||||
toolInfo = readInfoPlistFromPath(toolInfoPlistPath)
|
||||
toolInfoPlistPathToToolInfoMap[toolInfoPlistPath] = toolInfo
|
||||
if not toolInfo.has_key("CFBundleIdentifier"):
|
||||
if "CFBundleIdentifier" not in toolInfo:
|
||||
raise CheckException("'CFBundleIdentifier' not found", toolInfoPlistPath)
|
||||
bundleID = toolInfo["CFBundleIdentifier"]
|
||||
if not isinstance(bundleID, basestring):
|
||||
if not isinstance(bundleID, str):
|
||||
raise CheckException("'CFBundleIdentifier' must be a string", toolInfoPlistPath)
|
||||
appToolDict[bundleID] = toolNameToReqMap[bundleID]
|
||||
|
||||
# Set the SMPrivilegedExecutables value in the app "Info.plist".
|
||||
|
||||
appInfo = readInfoPlistFromPath(appInfoPlistPath)
|
||||
needsUpdate = not appInfo.has_key("SMPrivilegedExecutables")
|
||||
needsUpdate = "SMPrivilegedExecutables" not in appInfo
|
||||
if not needsUpdate:
|
||||
oldAppToolDict = appInfo["SMPrivilegedExecutables"]
|
||||
if not isinstance(oldAppToolDict, dict):
|
||||
raise CheckException("'SMPrivilegedExecutables' must be a dictionary", appInfoPlistPath)
|
||||
appToolDictSorted = sorted(appToolDict.iteritems(), key=operator.itemgetter(0))
|
||||
oldAppToolDictSorted = sorted(oldAppToolDict.iteritems(), key=operator.itemgetter(0))
|
||||
appToolDictSorted = sorted(appToolDict.items(), key=operator.itemgetter(0))
|
||||
oldAppToolDictSorted = sorted(oldAppToolDict.items(), key=operator.itemgetter(0))
|
||||
needsUpdate = (appToolDictSorted != oldAppToolDictSorted)
|
||||
|
||||
|
||||
if needsUpdate:
|
||||
appInfo["SMPrivilegedExecutables"] = appToolDict
|
||||
plistlib.writePlist(appInfo, appInfoPlistPath)
|
||||
print >> sys.stdout, "%s: updated" % appInfoPlistPath
|
||||
|
||||
with open(appInfoPlistPath, 'wb') as fp:
|
||||
plistlib.dump(appInfo, fp)
|
||||
print ("%s: updated" % appInfoPlistPath, file = sys.stdout)
|
||||
|
||||
# Set the SMAuthorizedClients value in each tool's "Info.plist".
|
||||
|
||||
toolAppListSorted = [ appReq ] # only one element, so obviously sorted (-:
|
||||
for toolInfoPlistPath in toolInfoPlistPaths:
|
||||
toolInfo = toolInfoPlistPathToToolInfoMap[toolInfoPlistPath]
|
||||
|
||||
needsUpdate = not toolInfo.has_key("SMAuthorizedClients")
|
||||
|
||||
needsUpdate = "SMAuthorizedClients" not in toolInfo
|
||||
if not needsUpdate:
|
||||
oldToolAppList = toolInfo["SMAuthorizedClients"]
|
||||
if not isinstance(oldToolAppList, list):
|
||||
raise CheckException("'SMAuthorizedClients' must be an array", toolInfoPlistPath)
|
||||
oldToolAppListSorted = sorted(oldToolAppList)
|
||||
needsUpdate = (toolAppListSorted != oldToolAppListSorted)
|
||||
|
||||
|
||||
if needsUpdate:
|
||||
toolInfo["SMAuthorizedClients"] = toolAppListSorted
|
||||
plistlib.writePlist(toolInfo, toolInfoPlistPath)
|
||||
print >> sys.stdout, "%s: updated" % toolInfoPlistPath
|
||||
with open(toolInfoPlistPath, 'wb') as f:
|
||||
plistlib.dump(toolInfo, f)
|
||||
print("%s: updated" % toolInfoPlistPath, file = sys.stdout)
|
||||
|
||||
def main():
|
||||
options, appArgs = getopt.getopt(sys.argv[1:], "d")
|
||||
|
||||
|
||||
debug = False
|
||||
for opt, val in options:
|
||||
if opt == "-d":
|
||||
|
@ -426,16 +445,16 @@ def main():
|
|||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except CheckException, e:
|
||||
except CheckException as e:
|
||||
if e.path is None:
|
||||
print >> sys.stderr, "%s: %s" % (os.path.basename(sys.argv[0]), e.message)
|
||||
print("%s: %s" % (os.path.basename(sys.argv[0]), e.message), file = sys.stderr)
|
||||
else:
|
||||
path = e.path
|
||||
if path.endswith("/"):
|
||||
path = path[:-1]
|
||||
print >> sys.stderr, "%s: %s" % (path, e.message)
|
||||
sys.exit(1)
|
||||
except UsageException, e:
|
||||
print >> sys.stderr, "usage: %s check /path/to/app" % os.path.basename(sys.argv[0])
|
||||
print >> sys.stderr, " %s setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist..." % os.path.basename(sys.argv[0])
|
||||
print("%s: %s" % (path, e.message), file = sys.stderr)
|
||||
sys.exit(1)
|
||||
except UsageException as e:
|
||||
print("usage: %s check /path/to/app" % os.path.basename(sys.argv[0]), file = sys.stderr)
|
||||
print(" %s setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist..." % os.path.basename(sys.argv[0]), file = sys.stderr)
|
||||
sys.exit(1)
|
Loading…
Reference in New Issue