You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							490 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							490 lines
						
					
					
						
							17 KiB
						
					
					
				
								#!/bin/bash
							 | 
						|
								
							 | 
						|
								# Copyright (c) 2024, crasowas.
							 | 
						|
								#
							 | 
						|
								# Use of this source code is governed by a MIT-style license
							 | 
						|
								# that can be found in the LICENSE file or at
							 | 
						|
								# https://opensource.org/licenses/MIT.
							 | 
						|
								
							 | 
						|
								set -e
							 | 
						|
								
							 | 
						|
								# Absolute path of the script and the tool's root directory
							 | 
						|
								script_path="$(realpath "$0")"
							 | 
						|
								tool_root_path="$(dirname "$script_path")"
							 | 
						|
								
							 | 
						|
								# Load common constants and utils
							 | 
						|
								source "$tool_root_path/Common/constants.sh"
							 | 
						|
								source "$tool_root_path/Common/utils.sh"
							 | 
						|
								
							 | 
						|
								# Force replace the existing privacy manifest when the `-f` option is enabled
							 | 
						|
								force=false
							 | 
						|
								
							 | 
						|
								# Enable silent mode to disable build output when the `-s` option is enabled
							 | 
						|
								silent=false
							 | 
						|
								
							 | 
						|
								# Parse command-line options
							 | 
						|
								while getopts ":fs" opt; do
							 | 
						|
								    case $opt in
							 | 
						|
								        f)
							 | 
						|
								            force=true
							 | 
						|
								            ;;
							 | 
						|
								        s)
							 | 
						|
								            silent=true
							 | 
						|
								            ;;
							 | 
						|
								        \?)
							 | 
						|
								            echo "Invalid option: -$OPTARG" >&2
							 | 
						|
								            exit 1
							 | 
						|
								            ;;
							 | 
						|
								    esac
							 | 
						|
								done
							 | 
						|
								
							 | 
						|
								shift $((OPTIND - 1))
							 | 
						|
								
							 | 
						|
								# Path of the app produced by the build process
							 | 
						|
								app_path="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
							 | 
						|
								
							 | 
						|
								# Check if the app exists
							 | 
						|
								if [ ! -d "$app_path" ] || [[ "$app_path" != *.app ]]; then
							 | 
						|
								    echo "Unable to find the app: $app_path"
							 | 
						|
								    exit 1
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Check if the app is iOS or macOS
							 | 
						|
								is_ios_app=true
							 | 
						|
								frameworks_dir="$app_path/Frameworks"
							 | 
						|
								if [ -d "$app_path/Contents/MacOS" ]; then
							 | 
						|
								    is_ios_app=false
							 | 
						|
								    frameworks_dir="$app_path/Contents/Frameworks"
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Default template directories
							 | 
						|
								templates_dir="$tool_root_path/Templates"
							 | 
						|
								user_templates_dir="$tool_root_path/Templates/UserTemplates"
							 | 
						|
								
							 | 
						|
								# Use user-defined app privacy manifest template if it exists, otherwise fallback to default
							 | 
						|
								app_template_file="$user_templates_dir/$APP_TEMPLATE_FILE_NAME"
							 | 
						|
								if [ ! -f "$app_template_file" ]; then
							 | 
						|
								    app_template_file="$templates_dir/$APP_TEMPLATE_FILE_NAME"
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Use user-defined framework privacy manifest template if it exists, otherwise fallback to default
							 | 
						|
								framework_template_file="$user_templates_dir/$FRAMEWORK_TEMPLATE_FILE_NAME"
							 | 
						|
								if [ ! -f "$framework_template_file" ]; then
							 | 
						|
								    framework_template_file="$templates_dir/$FRAMEWORK_TEMPLATE_FILE_NAME"
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Disable build output in silent mode
							 | 
						|
								if [ "$silent" == false ]; then
							 | 
						|
								    # Script used to generate the privacy access report
							 | 
						|
								    report_script="$tool_root_path/Report/report.sh"
							 | 
						|
								    # An array to record template usage for generating the privacy access report
							 | 
						|
								    template_usage_records=()
							 | 
						|
								    
							 | 
						|
								    # Build output directory
							 | 
						|
								    build_dir="$tool_root_path/Build/${PRODUCT_NAME}-${CONFIGURATION}_${MARKETING_VERSION}_${CURRENT_PROJECT_VERSION}_$(date +%Y%m%d%H%M%S)"
							 | 
						|
								    # Ensure the build directory exists
							 | 
						|
								    mkdir -p "$build_dir"
							 | 
						|
								
							 | 
						|
								    # Redirect both stdout and stderr to a log file while keeping console output
							 | 
						|
								    exec > >(tee "$build_dir/fix.log") 2>&1
							 | 
						|
								fi
							 | 
						|
								
							 | 
						|
								# Get the path to the `Info.plist` file for the specified app or framework
							 | 
						|
								function get_plist_file() {
							 | 
						|
								    local path="$1"
							 | 
						|
								    local version_path="$2"
							 | 
						|
								    local plist_file=""
							 | 
						|
								    
							 | 
						|
								    if [[ "$path" == *.app ]]; then
							 | 
						|
								        if [ "$is_ios_app" == true ]; then
							 | 
						|
								            plist_file="$path/Info.plist"
							 | 
						|
								        else
							 | 
						|
								            plist_file="$path/Contents/Info.plist"
							 | 
						|
								        fi
							 | 
						|
								    elif [[ "$path" == *.framework ]]; then
							 | 
						|
								        local framework_path="$(get_framework_path "$path" "$version_path")"
							 | 
						|
								        
							 | 
						|
								        if [ "$is_ios_app" == true ]; then
							 | 
						|
								            plist_file="$framework_path/Info.plist"
							 | 
						|
								        else
							 | 
						|
								            plist_file="$framework_path/Resources/Info.plist"
							 | 
						|
								        fi
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    echo "$plist_file"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Get the path to the executable for the specified app or framework
							 | 
						|
								function get_executable_path() {
							 | 
						|
								    local path="$1"
							 | 
						|
								    local version_path="$2"
							 | 
						|
								    local executable_path=""
							 | 
						|
								    
							 | 
						|
								    local plist_file="$(get_plist_file "$path" "$version_path")"
							 | 
						|
								    local executable_name="$(get_plist_executable "$plist_file")"
							 | 
						|
								    
							 | 
						|
								    if [[ "$path" == *.app ]]; then
							 | 
						|
								        if [ "$is_ios_app" == true ]; then
							 | 
						|
								            executable_path="$path/$executable_name"
							 | 
						|
								        else
							 | 
						|
								            executable_path="$path/Contents/MacOS/$executable_name"
							 | 
						|
								        fi
							 | 
						|
								    elif [[ "$path" == *.framework ]]; then
							 | 
						|
								        local framework_path="$(get_framework_path "$path" "$version_path")"
							 | 
						|
								        executable_path="$framework_path/$executable_name"
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    echo "$executable_path"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Analyze the specified binary file for API symbols and their categories
							 | 
						|
								function analyze_binary_file() {
							 | 
						|
								    local path="$1"
							 | 
						|
								    local -a results=()
							 | 
						|
								
							 | 
						|
								    # Check if the API symbol exists in the binary file using `nm` and `strings`
							 | 
						|
								    local nm_output=$(nm "$path" 2>/dev/null | xcrun swift-demangle)
							 | 
						|
								    local strings_output=$(strings "$path")
							 | 
						|
								    local combined_output="$nm_output"$'\n'"$strings_output"
							 | 
						|
								
							 | 
						|
								    for api_symbol in "${API_SYMBOLS[@]}"; do
							 | 
						|
								        local substrings=($(split_string_by_delimiter "$api_symbol"))
							 | 
						|
								        local category=${substrings[0]}
							 | 
						|
								        local api=${substrings[1]}
							 | 
						|
								
							 | 
						|
								        if echo "$combined_output" | grep -E "$api\$" >/dev/null; then
							 | 
						|
								            local index=-1
							 | 
						|
								            for ((i=0; i < ${#results[@]}; i++)); do
							 | 
						|
								                local result="${results[i]}"
							 | 
						|
								                local result_substrings=($(split_string_by_delimiter "$result"))
							 | 
						|
								                # If the category matches an existing result, update it
							 | 
						|
								                if [ "$category" == "${result_substrings[0]}" ]; then
							 | 
						|
								                    index=i
							 | 
						|
								                    results[i]="${result_substrings[0]}$DELIMITER${result_substrings[1]},$api$DELIMITER${result_substrings[2]}"
							 | 
						|
								                    break
							 | 
						|
								                fi
							 | 
						|
								            done
							 | 
						|
								
							 | 
						|
								            # If no matching category found, add a new result
							 | 
						|
								            if [[ $index -eq -1 ]]; then
							 | 
						|
								                results+=("$category$DELIMITER$api$DELIMITER$(encode_path "$path")")
							 | 
						|
								            fi
							 | 
						|
								        fi
							 | 
						|
								    done
							 | 
						|
								    
							 | 
						|
								    echo "${results[@]}"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Analyze API usage in a binary file
							 | 
						|
								function analyze_api_usage() {
							 | 
						|
								    local path="$1"
							 | 
						|
								    local version_path="$2"
							 | 
						|
								    local -a results=()
							 | 
						|
								    
							 | 
						|
								    local binary_file="$(get_executable_path "$path" "$version_path")"
							 | 
						|
								    
							 | 
						|
								    if [ -f "$binary_file" ]; then
							 | 
						|
								        results+=($(analyze_binary_file "$binary_file"))
							 | 
						|
								    fi
							 | 
						|
								
							 | 
						|
								    echo "${results[@]}"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								# Get unique categories from analysis results
							 | 
						|
								function get_categories() {
							 | 
						|
								    local results=("$@")
							 | 
						|
								    local -a categories=()
							 | 
						|
								    
							 | 
						|
								    for result in "${results[@]}"; do
							 | 
						|
								        local substrings=($(split_string_by_delimiter "$result"))
							 | 
						|
								        local category=${substrings[0]}
							 | 
						|
								        if [[ ! "${categories[@]}" =~ "$category" ]]; then
							 | 
						|
								            categories+=("$category")
							 | 
						|
								        fi
							 | 
						|
								    done
							 | 
						|
								    
							 | 
						|
								    echo "${categories[@]}"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Get template file for the specified app or framework
							 | 
						|
								function get_template_file() {
							 | 
						|
								    local path="$1"
							 | 
						|
								    local version_path="$2"
							 | 
						|
								    local template_file=""
							 | 
						|
								    
							 | 
						|
								    if [[ "$path" == *.app ]]; then
							 | 
						|
								        template_file="$app_template_file"
							 | 
						|
								    else
							 | 
						|
								        # Give priority to the user-defined framework privacy manifest template
							 | 
						|
								        local dep_name="$(get_dependency_name "$path")"
							 | 
						|
								        if [ -n "$version_path" ]; then
							 | 
						|
								            dep_name="$dep_name.$(basename "$version_path")"
							 | 
						|
								        fi
							 | 
						|
								        
							 | 
						|
								        local dep_template_file="$user_templates_dir/${dep_name}.xcprivacy"
							 | 
						|
								        if [ -f "$dep_template_file" ]; then
							 | 
						|
								            template_file="$dep_template_file"
							 | 
						|
								        else
							 | 
						|
								            template_file="$framework_template_file"
							 | 
						|
								        fi
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    echo "$template_file"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Check if the specified template file should be modified
							 | 
						|
								#
							 | 
						|
								# The following template files will be modified based on analysis:
							 | 
						|
								# * Templates/AppTemplate.xcprivacy
							 | 
						|
								# * Templates/FrameworkTemplate.xcprivacy
							 | 
						|
								# * Templates/UserTemplates/FrameworkTemplate.xcprivacy
							 | 
						|
								function is_template_modifiable() {
							 | 
						|
								    local template_file="$1"
							 | 
						|
								    
							 | 
						|
								    local template_file_name="$(basename "$template_file")"
							 | 
						|
								
							 | 
						|
								    if [[ "$template_file" != "$user_templates_dir"* ]] || [ "$template_file_name" == "$FRAMEWORK_TEMPLATE_FILE_NAME" ]; then
							 | 
						|
								        return 0
							 | 
						|
								    else
							 | 
						|
								        return 1
							 | 
						|
								    fi
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Check if `Hardened Runtime` is enabled for the specified path
							 | 
						|
								function is_hardened_runtime_enabled() {
							 | 
						|
								    local path="$1"
							 | 
						|
								
							 | 
						|
								    # Check environment variable first
							 | 
						|
								    if [ "${ENABLE_HARDENED_RUNTIME:-}" == "YES" ]; then
							 | 
						|
								        return 0
							 | 
						|
								    fi
							 | 
						|
								
							 | 
						|
								    # Check the code signature flags if environment variable is not set
							 | 
						|
								    local flags=$(codesign -dvvv "$path" 2>&1 | grep "flags=")
							 | 
						|
								    if echo "$flags" | grep -q "runtime"; then
							 | 
						|
								        return 0
							 | 
						|
								    else
							 | 
						|
								        return 1
							 | 
						|
								    fi
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Re-sign the target app or framework if code signing is enabled
							 | 
						|
								function resign() {
							 | 
						|
								    local path="$1"
							 | 
						|
								
							 | 
						|
								    if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ] && [ "${CODE_SIGNING_REQUIRED:-}" != "NO" ] && [ "${CODE_SIGNING_ALLOWED:-}" != "NO" ]; then
							 | 
						|
								        echo "Re-signing $path with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME:-$EXPANDED_CODE_SIGN_IDENTITY}"
							 | 
						|
								
							 | 
						|
								        local codesign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements"
							 | 
						|
								
							 | 
						|
								        if [ "$is_ios_app" == true ]; then
							 | 
						|
								            $codesign_cmd "$path"
							 | 
						|
								        else
							 | 
						|
								            if is_hardened_runtime_enabled "$path"; then
							 | 
						|
								                codesign_cmd="$codesign_cmd -o runtime"
							 | 
						|
								            fi
							 | 
						|
								            
							 | 
						|
								            if [ -d "$path/Contents/MacOS" ]; then
							 | 
						|
								                find "$path/Contents/MacOS" -type f -name "*.dylib" | while read -r dylib_file; do
							 | 
						|
								                    $codesign_cmd "$dylib_file"
							 | 
						|
								                done
							 | 
						|
								            fi
							 | 
						|
								            
							 | 
						|
								            $codesign_cmd "$path"
							 | 
						|
								        fi
							 | 
						|
								    fi
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Fix the privacy manifest for the app or specified framework
							 | 
						|
								# To accelerate the build, existing privacy manifests will be left unchanged unless the `-f` option is enabled
							 | 
						|
								# After fixing, the app or framework will be automatically re-signed
							 | 
						|
								function fix() {
							 | 
						|
								    local path="$1"
							 | 
						|
								    local version_path="$2"
							 | 
						|
								    local force_resign="$3"
							 | 
						|
								    local privacy_manifest_file=""
							 | 
						|
								    
							 | 
						|
								    if [[ "$path" == *.app ]]; then
							 | 
						|
								        # Per the documentation, the privacy manifest should be placed at the root of the app’s bundle for iOS, while for macOS, it should be located in `Contents/Resources/` within the app’s bundle
							 | 
						|
								        # Reference: https://developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to-your-app-or-third-party-sdk#Add-a-privacy-manifest-to-your-app
							 | 
						|
								        if [ "$is_ios_app" == true ]; then
							 | 
						|
								            privacy_manifest_file="$path/$PRIVACY_MANIFEST_FILE_NAME"
							 | 
						|
								        else
							 | 
						|
								            privacy_manifest_file="$path/Contents/Resources/$PRIVACY_MANIFEST_FILE_NAME"
							 | 
						|
								        fi
							 | 
						|
								    else
							 | 
						|
								        # Per the documentation, the privacy manifest should be placed at the root of the iOS framework, while for a macOS framework with multiple versions, it should be located in the `Resources` directory within the corresponding version
							 | 
						|
								        # Some SDKs don’t follow the guideline, so we use a search-based approach for now
							 | 
						|
								        # Reference: https://developer.apple.com/documentation/bundleresources/adding-a-privacy-manifest-to-your-app-or-third-party-sdk#Add-a-privacy-manifest-to-your-framework
							 | 
						|
								        local framework_path="$(get_framework_path "$path" "$version_path")"
							 | 
						|
								        local privacy_manifest_files=($(search_privacy_manifest_files "$framework_path"))
							 | 
						|
								        privacy_manifest_file="$(get_privacy_manifest_file "${privacy_manifest_files[@]}")"
							 | 
						|
								        
							 | 
						|
								        if [ -z "$privacy_manifest_file" ]; then
							 | 
						|
								            if [ "$is_ios_app" == true ]; then
							 | 
						|
								                privacy_manifest_file="$framework_path/$PRIVACY_MANIFEST_FILE_NAME"
							 | 
						|
								            else
							 | 
						|
								                privacy_manifest_file="$framework_path/Resources/$PRIVACY_MANIFEST_FILE_NAME"
							 | 
						|
								            fi
							 | 
						|
								        fi
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    # Check if the privacy manifest file exists
							 | 
						|
								    if [ -f "$privacy_manifest_file" ]; then
							 | 
						|
								        echo "💡 Found privacy manifest file: $privacy_manifest_file"
							 | 
						|
								        
							 | 
						|
								        if [ "$force" == false ]; then
							 | 
						|
								            if [ "$force_resign" == true ]; then
							 | 
						|
								                resign "$path"
							 | 
						|
								            fi
							 | 
						|
								            echo "✅ Privacy manifest file already exists, skipping fix."
							 | 
						|
								            return
							 | 
						|
								        fi
							 | 
						|
								    else
							 | 
						|
								        echo "⚠️  Missing privacy manifest file!"
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    local results=($(analyze_api_usage "$path" "$version_path"))
							 | 
						|
								    echo "API usage analysis result(s): ${#results[@]}"
							 | 
						|
								    print_array "${results[@]}"
							 | 
						|
								    
							 | 
						|
								    local template_file="$(get_template_file "$path" "$version_path")"
							 | 
						|
								    template_usage_records+=("$(basename "$path")$version_path$DELIMITER$template_file")
							 | 
						|
								    
							 | 
						|
								    # Copy the template file to the privacy manifest location, overwriting if it exists
							 | 
						|
								    cp "$template_file" "$privacy_manifest_file"
							 | 
						|
								    
							 | 
						|
								    if is_template_modifiable "$template_file"; then
							 | 
						|
								        local categories=($(get_categories "${results[@]}"))
							 | 
						|
								        local remove_categories=()
							 | 
						|
								        
							 | 
						|
								        # Check if categories is non-empty
							 | 
						|
								        if [[ ${#categories[@]} -gt 0 ]]; then
							 | 
						|
								            # Convert categories to a single space-separated string for easy matching
							 | 
						|
								            local categories_set=" ${categories[*]} "
							 | 
						|
								            
							 | 
						|
								            # Iterate through each element in `API_CATEGORIES`
							 | 
						|
								            for element in "${API_CATEGORIES[@]}"; do
							 | 
						|
								                # If element is not found in `categories_set`, add it to `remove_categories`
							 | 
						|
								                if [[ ! $categories_set =~ " $element " ]]; then
							 | 
						|
								                    remove_categories+=("$element")
							 | 
						|
								                fi
							 | 
						|
								            done
							 | 
						|
								        else
							 | 
						|
								            # If categories is empty, add all of `API_CATEGORIES` to `remove_categories`
							 | 
						|
								            remove_categories=("${API_CATEGORIES[@]}")
							 | 
						|
								        fi
							 | 
						|
								
							 | 
						|
								        # Remove extra spaces in the XML file to simplify node removal
							 | 
						|
								        xmllint --noblanks "$privacy_manifest_file" -o "$privacy_manifest_file"
							 | 
						|
								
							 | 
						|
								        # Build a sed command to remove all matching nodes at once
							 | 
						|
								        local sed_pattern=""
							 | 
						|
								        for category in "${remove_categories[@]}"; do
							 | 
						|
								            # Find the node for the current category
							 | 
						|
								            local remove_node="$(xmllint --xpath "//dict[string='$category']" "$privacy_manifest_file" 2>/dev/null || true)"
							 | 
						|
								            
							 | 
						|
								            # If the node is found, escape special characters and append it to the sed pattern
							 | 
						|
								            if [ -n "$remove_node" ]; then
							 | 
						|
								                local escaped_node=$(echo "$remove_node" | sed 's/[\/&]/\\&/g')
							 | 
						|
								                sed_pattern+="s/$escaped_node//g;"
							 | 
						|
								            fi
							 | 
						|
								        done
							 | 
						|
								
							 | 
						|
								        # Apply the combined sed pattern to the file if it's not empty
							 | 
						|
								        if [ -n "$sed_pattern" ]; then
							 | 
						|
								            sed -i "" "$sed_pattern" "$privacy_manifest_file"
							 | 
						|
								        fi
							 | 
						|
								
							 | 
						|
								        # Reformat the XML file to restore spaces for readability
							 | 
						|
								        xmllint --format "$privacy_manifest_file" -o "$privacy_manifest_file"
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    resign "$path"
							 | 
						|
								    
							 | 
						|
								    echo "✅ Privacy manifest file fixed: $privacy_manifest_file."
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Fix privacy manifests for all frameworks
							 | 
						|
								function fix_frameworks() {
							 | 
						|
								    if ! [ -d "$frameworks_dir" ]; then
							 | 
						|
								        return
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    echo "🛠️ Fixing Frameworks..."
							 | 
						|
								    for path in "$frameworks_dir"/*; do
							 | 
						|
								        if [ -d "$path" ]; then
							 | 
						|
								            local dep_name="$(get_dependency_name "$path")"
							 | 
						|
								            local versions_dir="$path/Versions"
							 | 
						|
								            
							 | 
						|
								            if [ -d "$versions_dir" ]; then
							 | 
						|
								                for version in $(ls -1 "$versions_dir" | grep -vE '^Current$'); do
							 | 
						|
								                    local version_path="Versions/$version"
							 | 
						|
								                    echo "Analyzing $dep_name ($version_path) ..."
							 | 
						|
								                    fix "$path" "$version_path" false
							 | 
						|
								                    echo ""
							 | 
						|
								                done
							 | 
						|
								            else
							 | 
						|
								                echo "Analyzing $dep_name ..."
							 | 
						|
								                fix "$path" "" false
							 | 
						|
								                echo ""
							 | 
						|
								            fi
							 | 
						|
								        fi
							 | 
						|
								    done
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Fix the privacy manifest for the app
							 | 
						|
								function fix_app() {
							 | 
						|
								    echo "🛠️ Fixing $(basename "$app_path" .app) App..."
							 | 
						|
								    # Since the framework may have undergone fixes, the app must be forcefully re-signed
							 | 
						|
								    fix "$app_path" "" true
							 | 
						|
								    echo ""
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Generate the privacy access report for the app
							 | 
						|
								function generate_report() {
							 | 
						|
								    local original="$1"
							 | 
						|
								    
							 | 
						|
								    if [ "$silent" == true ]; then
							 | 
						|
								        return
							 | 
						|
								    fi
							 | 
						|
								
							 | 
						|
								    local app_name="$(basename "$app_path")"
							 | 
						|
								    local name="${app_name%.*}"
							 | 
						|
								    local report_name=""
							 | 
						|
								
							 | 
						|
								    # Adjust output names if the app is flagged as original
							 | 
						|
								    if [ "$original" == true ]; then
							 | 
						|
								        app_name="${name}-original.app"
							 | 
						|
								        report_name="report-original.html"
							 | 
						|
								    else
							 | 
						|
								        app_name="$name.app"
							 | 
						|
								        report_name="report.html"
							 | 
						|
								    fi
							 | 
						|
								    
							 | 
						|
								    local target_app_path="$build_dir/$app_name"
							 | 
						|
								    local report_path="$build_dir/$report_name"
							 | 
						|
								    
							 | 
						|
								    echo "Copy app to $target_app_path"
							 | 
						|
								    rsync -a "$app_path/" "$target_app_path/"
							 | 
						|
								    
							 | 
						|
								    # Generate the privacy access report using the script
							 | 
						|
								    sh "$report_script" "$target_app_path" "$report_path" "${template_usage_records[@]}"
							 | 
						|
								    echo ""
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								start_time=$(date +%s)
							 | 
						|
								
							 | 
						|
								generate_report true
							 | 
						|
								fix_frameworks
							 | 
						|
								fix_app
							 | 
						|
								generate_report false
							 | 
						|
								
							 | 
						|
								end_time=$(date +%s)
							 | 
						|
								
							 | 
						|
								echo "🎉 All fixed! ⏰ $((end_time - start_time)) seconds"
							 | 
						|
								echo "🌟 If you found this script helpful, please consider giving it a star on GitHub. Your support is appreciated. Thank you!"
							 | 
						|
								echo "🔗 Homepage: https://github.com/crasowas/app_privacy_manifest_fixer"
							 | 
						|
								echo "🐛 Report issues: https://github.com/crasowas/app_privacy_manifest_fixer/issues"
							 | 
						|
								
							 |