def test_sanitize_html(self):
        with open(os.path.join(os.path.dirname(__file__), 'contenttypes-notes-tosanitize.plist'), 'rb') as f:
            strings = load_plist(f)
        with open(os.path.join(os.path.dirname(__file__), 'contenttypes-notes-sanitized.txt')) as f:
            sanitized = f.readlines()

        for s in zip(strings, sanitized):
            _check_sanitized(s[0], s[1])
Exemple #2
0
def check_app_updates(app_info_list, raw_result=False):
    # This function expects a list of dicts with, at a minimum, the 'adam-id' key set
    # This ID is the unique product identifier for an app on the App Store and can be found in the store URL
    # when viewing the app's page in a web browser, like so:
    # https://itunes.apple.com/us/app/evernote/id406056744
    #                                            ^^^^^^^^^
    # The other 3 keys that will be passed in the search are: CFBundleIdentifier, CFBundleShortVersionString, and
    # installed-version-identifier (explained earlier). Lack of any of these keys will result in a filler value
    # being provided which, as long as the adam-id is present, the App Store update mechanism doesn't seem to
    # care about.
    update_url = 'https://su.itunes.apple.com/WebObjects/MZSoftwareUpdate.woa/wa/availableSoftwareUpdatesExtended'
    request = urllib.request.Request(update_url)
    # Headers #
    # This sets us to the US store. See:
    # http://blogs.oreilly.com/iphone/2008/08/scraping-appstore-reviews.html
    # http://secrets.blacktree.com/edit?id=129761
    request.add_header('X-Apple-Store-Front', '143441-1,13')
    # This indicates we're sending an XML plist
    request.add_header('Content-Type', 'application/x-apple-plist')
    # This seems to be the minimum string to be recognized as a valid app store update checker
    # Normally, it's of the form: User-Agent: MacAppStore/1.3 (Macintosh; OS X 10.9) AppleWebKit/537.71
    request.add_header('User-Agent', 'MacAppStore')
    # Build up the plist
    local_software = []
    for an_app in app_info_list:
        app_entry = {
            'CFBundleIdentifier':
            an_app.get('CFBundleIdentifier', '.'),
            'CFBundleShortVersionString':
            an_app.get('CFBundleShortVersionString', '0'),
            'adam-id':
            an_app['adam-id'],
            'installed-version-identifier':
            an_app.get('installed-version-identifier', 0)
        }
        local_software.append(app_entry)
    plist_dict = {'local-software': local_software}
    plist_str = dump_plist(plist_dict)
    request.add_data(plist_str)
    # Build the connection
    response_handle = urllib.request.urlopen(request)
    try:
        response = response_handle.read()
    except HTTPError as e:
        raise ProcessorError("Invalid adam-id %s" % e)
    response_handle.close()
    # Currently returning the raw response
    # Initial analysis:
    # - It appears that applications that need updating will be under the 'incompatible-items' key
    # - 'version-external-identifiers' is a list of historical versions, in order
    # - 'version-external-identifier' is the current version
    # - 'current-version' is the CFBundleShortVersionString
    # - 'bundle-id' is the CFBundleIdentifier
    # - 'preflight' is a *very* interesting 'pfpkg'. See details at bottom.
    return load_plist(response)
Exemple #3
0
def plist_yaml(in_path, out_path):
    """Convert plist to yaml."""
    with open(in_path, "rb") as in_file:
        input_data = load_plist(in_file)

    normalized = normalize_types(input_data)
    if sys.version_info.major == 3 and in_path.endswith(
        (".recipe", ".recipe.plist")):
        normalized = sort_autopkg_processes(normalized)
    output = convert(normalized)

    out_file = open(out_path, "w")
    out_file.writelines(output)
Exemple #4
0
def plist_yaml(in_path, out_path):
    """Convert plist to yaml."""
    with open(in_path, "rb") as in_file:
        input_data = load_plist(in_file)

    normalized = normalize_types(input_data)

    # handle conversion of AutoPkg recipes
    if sys.version_info.major == 3 and in_path.endswith(
        (".recipe", ".recipe.plist")):
        normalized = handle_autopkg_recipes.optimise_autopkg_recipes(
            normalized)
        output = convert(normalized)
        output = handle_autopkg_recipes.format_autopkg_recipes(output)
    else:
        output = convert(normalized)

    out_file = open(out_path, "w")
    out_file.writelines(output)
    print("Wrote to : {}\n".format(out_path))
Exemple #5
0
    def get_url(self, os_ver):
        plist_text = self.download(CHECK_URL)
        plist_filename = os.path.join(self.env['RECIPE_CACHE_DIR'], PLIST_FN)

        try:
            plist = load_plist(plist_text)
        except:
            raise ProcessorError('Could not read NVIDIA plist file %s' % plist_filename)

        # the Version is blank here due to the plist NSPredicate
        # testing it to not be the current version.
        pred_obj = {'Ticket': {'Version': ''}, 'SystemVersion': {'ProductVersion': os_ver}}

        url = None
        version = None
        for rule in plist['Rules']:
            if self.evaluate_predicate(pred_obj, rule['Predicate']):
                self.output('Satisfied predicate for OS version %s' % os_ver)
                url = rule['Codebase']
                version = rule['kServerVersion']

                # with a satisfied predicate, evaluate lower OS versions
                # so as to determine a minimum OS constraint, decrementing
                # one 10.x version at a time
                while self.evaluate_predicate(pred_obj, rule['Predicate']):
                    # record the currently-evaluated OS version as the minimum required
                    minimum_os_ver = os_ver
                    osx, major = os_ver.split('.')
                    os_ver = osx + '.' + str(int(major) - 1)
                    pred_obj['SystemVersion']['ProductVersion'] = os_ver
                    self.output('Evaluating predicate for lower OS version %s' % os_ver)
                self.output('OS version %s too low!' % os_ver)
                # highest required versions seem to be always first, so we break
                # after we've satisfied one Predicate
                break


        if not url:
            raise ProcessorError('No valid Predicate rules found!')
        return (url, version, minimum_os_ver)
    def process_hd_installer(self):
        '''
        Process HD installer -
            app_json: Path to the Application JSON from within the PKG
        '''

        #pylint: disable=too-many-locals, too-many-statements
        self.output('Processing HD installer')
        with open(self.env['app_json']) as json_file:
            load_json = json.load(json_file)

            # AppLaunch is not always in the same format, but is splittable
            if 'AppLaunch' in load_json:  # Bridge CC is HD but does not have AppLaunch
                app_launch = load_json['AppLaunch']
                self.output('app_launch: %s' % app_launch)
                app_details = list(re.split('/', app_launch))
                if app_details[2].endswith('.app'):
                    app_bundle = app_details[2]
                    app_path = app_details[1]
                else:
                    app_bundle = app_details[1]
                    app_path = list(
                        re.split('/', (load_json['InstallDir']['value'])))[1]
                self.output('app_bundle: %s' % app_bundle)
                self.output('app_path: %s' % app_path)

                installed_path = os.path.join('/Applications', app_path,
                                              app_bundle)
                self.output('installed_path: %s' % installed_path)

                if not app_path.endswith('CC') and not app_path.endswith(
                        '2021'):
                    self.env['display_name'] = app_path + ' 2021'
                elif app_path.endswith('CC') and not app_path.endswith('2021'):
                    self.env['display_name'] = app_path + ' 2021'
                else:
                    self.env['display_name'] = app_path
                self.output('display_name: %s' % self.env['display_name'])

                zip_file = load_json['Packages']['Package'][0]['PackageName']
                pimx_dir = '1'
                if zip_file.endswith('-LearnPanel'):
                    zip_file = load_json['Packages']['Package'][1][
                        'PackageName']
                    pimx_dir = '2'
                self.output('zip_file: %s' % zip_file)

                zip_path = os.path.join(self.env['PKG'], 'Contents/Resources/HD', \
                                    self.env['target_folder'], zip_file + '.zip')
                self.output('zip_path: %s' % zip_path)

                with zipfile.ZipFile(zip_path, mode='r') as myzip:
                    with myzip.open(zip_file + '.pimx') as mytxt:
                        txt = mytxt.read()
                        tree = ElementTree.fromstring(txt)
                        # Loop through .pmx's Assets, look for target=[INSTALLDIR],
                        # then grab Assets Source.
                        # Break when found .app/Contents/Info.plist
                        for elem in tree.findall('Assets'):
                            for i in elem.getchildren():
                                # Below special tweak for the non-Classic Lightroom bundle
                                if i.attrib['target'].upper().startswith('[INSTALLDIR]') and \
                                                   not i.attrib['target'].endswith('Icons'):
                                    bundle_location = i.attrib['source']
                                    self.output('bundle_location: %s' %
                                                bundle_location)
                                else:
                                    continue
                                if not bundle_location.startswith(
                                        '[StagingFolder]'):
                                    continue
                                elif bundle_location.endswith('Icons') or \
                                         bundle_location.endswith('AMT'):
                                    continue
                                else:
                                    bundle_location = bundle_location[16:]
                                    if bundle_location.endswith('.app'):
                                        zip_bundle = os.path.join(pimx_dir, bundle_location, \
                                                                      'Contents/Info.plist')
                                    else:
                                        zip_bundle = os.path.join(pimx_dir, bundle_location, \
                                                          app_bundle, 'Contents/Info.plist')
                                    try:
                                        with myzip.open(zip_bundle) as myplist:
                                            plist = myplist.read()
                                            data = load_plist(plist)
                                            # If the App is Lightroom (Classic or non-Classic)
                                            # we need to compare a different value in Info.plist
                                            if self.env['sap_code'] == 'LTRM' or \
                                                 self.env['sap_code'] == 'LRCC':
                                                self.env[
                                                    'vers_compare_key'] = 'CFBundleVersion'
                                            else:
                                                self.env['vers_compare_key'] = \
                                                  'CFBundleShortVersionString'
                                            self.output('vers_compare_key: %s' % \
                                                   self.env['vers_compare_key'])
                                            app_version = data[
                                                self.env['vers_compare_key']]
                                            self.output('staging_folder: %s' %
                                                        bundle_location)
                                            self.output(
                                                'staging_folder_path: %s' %
                                                zip_bundle)
                                            self.output('app_version: %s' %
                                                        app_version)
                                            self.output('app_bundle: %s' %
                                                        app_bundle)
                                            break
                                    except zipfile.BadZipfile:
                                        continue

                # Now we have the deets, let's use them
                self.create_pkginfo(app_bundle, app_version, installed_path)
    def read_info_plist(self, my_zip, pimx_dir, xml_tree, zip_path):
        '''
            Try to read info.plist from within zip_bundle
        '''

        # Loop through .pmx's Assets, look for target=[INSTALLDIR],
        # then grab Assets Source.
        # Break when found .app/Contents/Info.plist
        for xml_elem in xml_tree.findall('Assets'):
            for xml_item in xml_elem.getchildren():
                # Below special tweak for the non-Classic Lightroom bundle
                if (xml_item.attrib['target'].upper().startswith(
                        '[INSTALLDIR]')
                        and not xml_item.attrib['target'].endswith('Icons')):
                    # Get bundle_location
                    bundle_location = xml_item.attrib['source']
                    self.output("bundle_location: {}".format(bundle_location))
                else:
                    continue
                # Amend bundle_location as needed
                if not bundle_location.startswith('[StagingFolder]'):
                    continue
                if bundle_location.endswith('Icons') or \
                         bundle_location.endswith('AMT'):
                    continue
                bundle_location = bundle_location[16:]
                # Create zip_bundle
                if bundle_location.endswith('.app'):
                    zip_bundle = (os.path.join(pimx_dir, bundle_location,
                                               'Contents/Info.plist'))
                else:
                    zip_bundle = (os.path.join(pimx_dir, bundle_location,
                                               self.env['app_bundle'],
                                               'Contents/Info.plist'))
                # Try to read info.plist from within zip_bundle
                try:
                    with my_zip.open(zip_bundle) as my_plist:
                        info_plist = my_plist.read()
                        data = load_plist(info_plist)
                        # If the App is Lightroom (Classic or non-Classic)
                        # we need to compare a different value in Info.plist
                        if self.env['sap_code'] == 'LTRM' or \
                             self.env['sap_code'] == 'LRCC':
                            self.env['vers_compare_key'] = 'CFBundleVersion'
                        else:
                            self.env['vers_compare_key'] = (
                                'CFBundleShortVersionString')

                        # Get version from info.plist
                        app_version = data[self.env['vers_compare_key']]

                        # Get bundleid from info.plist
                        self.env['app_bundle_id'] = data['CFBundleIdentifier']

                        # Progress notifications
                        self.output("vers_compare_key: {}".format(
                            self.env['vers_compare_key']))
                        self.output("app_bundle_id: {}".format(
                            self.env['app_bundle_id']))
                        self.output(
                            "staging_folder: {}".format(bundle_location))
                        self.output(
                            "staging_folder_path: {}".format(zip_bundle))
                        self.env['app_version'] = app_version
                        self.output("app_version: {}".format(
                            self.env['app_version']))
                        break

                # If we cannot read the zip file
                except zipfile.BadZipfile as err_msg:
                    raise ProcessorError("Failed to open {}: {}".format(
                        zip_path, err_msg))
    default='1.0')
parser.add_argument(
    '--icon',
    help=
    'Specifies an existing icon in the Munki repo to display for the printer in Managed Software Center. Optional.'
)
parser.add_argument(
    '--csv',
    help=
    'Path to CSV file containing printer info. If CSV is provided, all other options are ignored.'
)
args = parser.parse_args()

pwd = os.path.dirname(os.path.realpath(__file__))
f = open(os.path.join(pwd, 'Printer-Template.plist'), 'rb')
templatePlist = load_plist(f)
f.close()

if args.csv:
    # A CSV was found, use that for all data.
    with open(args.csv, mode='r') as infile:
        reader = csv.reader(infile)
        next(reader, None)  # skip the header row
        for row in reader:
            newPlist = dict(templatePlist)
            # each row contains 10 elements:
            # Printer name, location, display name, address, driver, description, options, version, requires, icon
            # options in the form of "Option=Value Option2=Value Option3=Value"
            # requires in the form of "package1 package2" Note: the space seperator
            theOptionString = ''
            if row[6] != "":
Exemple #9
0
    def get_installer_info(self):
        """Gets info about an installer from MS metadata."""
        # Get the channel UUID, matching against a custom UUID if one is given
        channel_input = self.env.get("channel", DEFAULT_CHANNEL)
        rex = r"^([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})$"
        match_uuid = re.match(rex, channel_input)
        if not match_uuid and channel_input not in list(CHANNELS.keys()):
            raise ProcessorError(
                "'channel' input variable must be one of: %s or a custom "
                "uuid" % (", ".join(list(CHANNELS.keys()))))
        if match_uuid:
            channel = match_uuid.groups()[0]
        else:
            channel = CHANNELS[channel_input]
        base_url = BASE_URL % (channel, CULTURE_CODE +
                               PROD_DICT[self.env["product"]]['id'])

        # Get metadata URL
        self.output("Requesting xml: %s" % base_url)
        req = urllib.request.Request(base_url)
        # Add the MAU User-Agent, since MAU feed server seems to explicitly
        # block a User-Agent of 'Python-urllib/2.7' - even a blank User-Agent
        # string passes.
        req.add_header(
            "User-Agent",
            "Microsoft%20AutoUpdate/3.6.16080300 CFNetwork/760.6.3 Darwin/15.6.0 (x86_64)"
        )

        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        try:
            fdesc = urllib.request.urlopen(req, context=ctx)
            data = fdesc.read()
            fdesc.close()
        except BaseException as err:
            raise ProcessorError("Can't download %s: %s" % (base_url, err))

        metadata = load_plist(data)
        item = {}
        # Update feeds for a given 'channel' will have either combo or delta
        # pkg urls, with delta's additionally having a 'FullUpdaterLocation' key.
        # We populate the item dict with the appropriate section of the metadata
        # output, and do string replacement on the pattern of the URL in the
        # case of a Standalone app request.
        if self.env["version"] == "latest" or self.env[
                "version"] == "latest-standalone":
            item = [u for u in metadata if not u.get("FullUpdaterLocation")]
        elif self.env["version"] == "latest-delta":
            item = [u for u in metadata if u.get("FullUpdaterLocation")]
        if not item:
            raise ProcessorError("Could not find an applicable update in "
                                 "update metadata.")
        item = item[0]

        if self.env["version"] == "latest-standalone":
            p = re.compile(r'(^[a-zA-Z0-9:/.-]*_[a-zA-Z]*_)(.*)Updater.pkg')
            url = item["Location"]
            (firstGroup, secondGroup) = re.search(p, url).group(1, 2)
            item[
                "Location"] = firstGroup + "2019_" + secondGroup + "Installer.pkg"

        self.env["url"] = item["Location"]
        self.output("Found URL %s" % self.env["url"])
        self.output("Got update: '%s'" % item["Title"])
        # now extract useful info from the rest of the metadata that could
        # be used in a pkginfo
        pkginfo = {}
        # Get a copy of the description in our locale_id
        all_localizations = item.get("Localized")
        lcid = self.env["locale_id"]
        if lcid not in all_localizations:
            raise ProcessorError(
                "Locale ID %s not found in manifest metadata. Available IDs: "
                "%s. See %s for more details." % (lcid, ", ".join(
                    list(all_localizations.keys())), LOCALE_ID_INFO_URL))
        manifest_description = all_localizations[lcid]['Short Description']
        # Store the description in a separate output variable and in our pkginfo
        # directly.
        pkginfo["description"] = "<html>%s</html>" % manifest_description
        self.env["description"] = manifest_description

        # Minimum OS version key should exist always, but default to the current
        # minimum as of 16/11/03
        pkginfo["minimum_os_version"] = item.get('Minimum OS', '10.10.5')
        installs_items = self.get_installs_items(item)
        if installs_items:
            pkginfo["installs"] = installs_items

        # Extra work to do if this is a delta updater
        if self.env["version"] == "latest-delta":
            try:
                rel_versions = item["Triggers"]["Registered File"][
                    "VersionsRelative"]
            except KeyError:
                raise ProcessorError("Can't find expected VersionsRelative"
                                     "keys for determining minimum update "
                                     "required for delta update.")
            for expression in rel_versions:
                operator, ver_eval = expression.split()
                if operator == ">=":
                    self.min_delta_version = ver_eval
                    break
            if not self.min_delta_version:
                raise ProcessorError("Not able to determine minimum required "
                                     "version for delta update.")
            # Put minimum_update_version into installs item
            self.output("Adding minimum required version: %s" %
                        self.min_delta_version)
            pkginfo["installs"][0]["minimum_update_version"] = \
                self.min_delta_version
            required_update_name = self.env["NAME"]
            if self.env["munki_required_update_name"]:
                required_update_name = self.env["munki_required_update_name"]
            # Add 'requires' array
            pkginfo["requires"] = [
                "%s-%s" % (required_update_name, self.min_delta_version)
            ]

        self.env["version"] = self.get_version(item)
        self.env["minimum_os_version"] = pkginfo["minimum_os_version"]
        self.env["minimum_version_for_delta"] = self.min_delta_version
        self.env["additional_pkginfo"] = pkginfo
        self.env["url"] = item["Location"]
        self.output("Additional pkginfo: %s" % self.env["additional_pkginfo"])