Пример #1
0
def load_manifest_dict(manifest, manifest_kind, default_project_type='native'):
    """ Load data from a manifest dictionary
    :param manifest: a dictionary of settings
    :param manifest_kind: 'package.json' or 'appinfo.json'
    :return: a tuple of (models.Project options dictionary, the media map, the dependencies dictionary)
    """
    project = {}
    dependencies = {}
    if manifest_kind == APPINFO_MANIFEST:
        project['app_short_name'] = manifest['shortName']
        project['app_long_name'] = manifest['longName']
        project['app_company_name'] = manifest['companyName']
        project['app_version_label'] = manifest['versionLabel']
        project['app_keys'] = dict_to_pretty_json(manifest.get('appKeys', {}))
        project['sdk_version'] = manifest.get('sdkVersion', '2')
        project['app_modern_multi_js'] = manifest.get('enableMultiJS', False)

    elif manifest_kind == PACKAGE_MANIFEST:
        project['app_short_name'] = manifest['name']
        project['app_company_name'] = manifest['author']
        project['semver'] = manifest['version']
        project['app_long_name'] = manifest['pebble']['displayName']
        if settings.NPM_MANIFEST_SUPPORT:
            project['app_keys'] = dict_to_pretty_json(manifest['pebble'].get(
                'messageKeys', []))
        else:
            project['app_keys'] = dict_to_pretty_json(manifest['pebble'].get(
                'messageKeys', {}))
            if isinstance(json.loads(project['app_keys']), list):
                raise InvalidProjectArchiveException(
                    "Auto-assigned (array) messageKeys are not yet supported.")
        project['keywords'] = manifest.get('keywords', [])
        dependencies = manifest.get('dependencies', {})
        manifest = manifest['pebble']
        project['app_modern_multi_js'] = manifest.get('enableMultiJS', True)
        project['sdk_version'] = manifest.get('sdkVersion', '3')
    else:
        raise InvalidProjectArchiveException(
            _('Invalid manifest kind: %s') % manifest_kind[-12:])

    project['app_uuid'] = manifest['uuid']
    project['app_is_watchface'] = manifest.get('watchapp',
                                               {}).get('watchface', False)
    project['app_is_hidden'] = manifest.get('watchapp',
                                            {}).get('hiddenApp', False)
    project['app_is_shown_on_communication'] = manifest.get(
        'watchapp', {}).get('onlyShownOnCommunication', False)
    project['app_capabilities'] = ','.join(manifest.get('capabilities', []))

    if 'targetPlatforms' in manifest:
        project['app_platforms'] = ','.join(manifest['targetPlatforms'])
    if 'resources' in manifest and 'media' in manifest['resources']:
        media_map = manifest['resources']['media']
    else:
        media_map = {}
    project['project_type'] = manifest.get('projectType', default_project_type)
    return project, media_map, dependencies
Пример #2
0
 def make_valid_filename(zip_entry):
     entry_filename = zip_entry.filename
     if entry_filename[:dir_end] != base_dir:
         return False
     entry_filename = entry_filename[dir_end:]
     if entry_filename == '':
         return False
     if not os.path.normpath('/SENTINEL_DO_NOT_ACTUALLY_USE_THIS_NAME/%s' % entry_filename).startswith('/SENTINEL_DO_NOT_ACTUALLY_USE_THIS_NAME/'):
         raise SuspiciousOperation("Invalid zip file contents.")
     if zip_entry.file_size > 5242880:  # 5 MB
         raise InvalidProjectArchiveException("Excessively large compressed file.")
     return entry_filename
Пример #3
0
def do_import_archive(project_id, archive, delete_project=False):
    project = Project.objects.get(pk=project_id)
    try:
        with tempfile.NamedTemporaryFile(suffix='.zip') as archive_file:
            archive_file.write(archive)
            archive_file.flush()
            with zipfile.ZipFile(str(archive_file.name), 'r') as z:
                contents = z.infolist()
                # Requirements:
                # - Find the folder containing the project. This may or may not be at the root level.
                # - Read in the source files, resources and resource map.
                # Observations:
                # - Legal projects must keep their source in a directory called 'src' containing at least one *.c file.
                # - Legal projects must have a resource map at resources/src/resource_map.json
                # Strategy:
                # - Find the shortest common prefix for 'resources/src/resource_map.json' and 'src/'.
                #   - This is taken to be the project directory.
                # - Import every file in 'src/' with the extension .c or .h as a source file
                # - Parse resource_map.json and import files it references
                SRC_DIR = 'src/'
                WORKER_SRC_DIR = 'worker_src/'
                INCLUDE_SRC_DIR = 'include/'

                if len(contents) > 400:
                    raise InvalidProjectArchiveException("Too many files in zip file.")

                archive_items = [ArchiveProjectItem(z, x) for x in contents]
                base_dir, manifest_item = find_project_root_and_manifest(archive_items)
                dir_end = len(base_dir)

                def make_valid_filename(zip_entry):
                    entry_filename = zip_entry.filename
                    if entry_filename[:dir_end] != base_dir:
                        return False
                    entry_filename = entry_filename[dir_end:]
                    if entry_filename == '':
                        return False
                    if not os.path.normpath('/SENTINEL_DO_NOT_ACTUALLY_USE_THIS_NAME/%s' % entry_filename).startswith('/SENTINEL_DO_NOT_ACTUALLY_USE_THIS_NAME/'):
                        raise SuspiciousOperation("Invalid zip file contents.")
                    if zip_entry.file_size > 5242880:  # 5 MB
                        raise InvalidProjectArchiveException("Excessively large compressed file.")
                    return entry_filename

                manifest_kind = make_valid_filename(manifest_item.entry)
                manifest_dict = json.loads(manifest_item.read())

                # Now iterate over the things we found, filter out invalid files and look for the manifest.
                filtered_contents = []
                for entry in contents:
                    filename = make_valid_filename(entry)
                    if not filename or filename in MANIFEST_KINDS:
                        continue
                    else:
                        filtered_contents.append((filename, entry))

                with transaction.atomic():
                    # We have a resource map! We can now try importing things from it.
                    project_options, media_map, dependencies = load_manifest_dict(manifest_dict, manifest_kind)

                    for k, v in project_options.iteritems():
                        setattr(project, k, v)
                    project.full_clean()
                    project.set_dependencies(dependencies)

                    RES_PATH = project.resources_path

                    tag_map = {v: k for k, v in ResourceVariant.VARIANT_STRINGS.iteritems() if v}

                    desired_resources = {}
                    resources_files = {}
                    resource_variants = {}
                    file_exists_for_root = {}

                    # Go through the media map and look for resources
                    for resource in media_map:
                        file_name = resource['file']
                        identifier = resource['name']
                        # Pebble.js and simply.js both have some internal resources that we don't import.
                        if project.project_type in {'pebblejs', 'simplyjs'}:
                            if identifier in {'MONO_FONT_14', 'IMAGE_MENU_ICON', 'IMAGE_LOGO_SPLASH', 'IMAGE_TILE_SPLASH'}:
                                continue
                        tags, root_file_name = get_filename_variant(file_name, tag_map)
                        if (len(tags) != 0):
                            raise ValueError("Generic resource filenames cannot contain a tilde (~)")
                        if file_name not in desired_resources:
                            desired_resources[root_file_name] = []

                        desired_resources[root_file_name].append(resource)
                        file_exists_for_root[root_file_name] = False

                    # Go through the zip file process all resource and source files.
                    for filename, entry in filtered_contents:
                        if filename.startswith(RES_PATH):
                            base_filename = filename[len(RES_PATH) + 1:]
                            # Let's just try opening the file
                            try:
                                extracted = z.open("%s%s/%s" % (base_dir, RES_PATH, base_filename))
                            except KeyError:
                                logger.debug("Failed to open %s", base_filename)
                                continue

                            # Now we know the file exists and is in the resource directory - is it the one we want?
                            tags, root_file_name = get_filename_variant(base_filename, tag_map)
                            tags_string = ",".join(str(int(t)) for t in tags)

                            if root_file_name in desired_resources:
                                medias = desired_resources[root_file_name]

                                # Because 'kind' and 'is_menu_icons' are properties of ResourceFile in the database,
                                # we just use the first one.
                                resource = medias[0]
                                # Make only one resource file per base resource.
                                if root_file_name not in resources_files:
                                    kind = resource['type']
                                    is_menu_icon = resource.get('menuIcon', False)
                                    resources_files[root_file_name] = ResourceFile.objects.create(
                                        project=project,
                                        file_name=os.path.basename(root_file_name),
                                        kind=kind,
                                        is_menu_icon=is_menu_icon)

                                # But add a resource variant for every file
                                actual_file_name = resource['file']
                                resource_variants[actual_file_name] = ResourceVariant.objects.create(resource_file=resources_files[root_file_name], tags=tags_string)
                                resource_variants[actual_file_name].save_file(extracted)
                                file_exists_for_root[root_file_name] = True
                        else:
                            try:
                                base_filename, target = SourceFile.get_details_for_path(project.project_type, filename)
                            except ValueError:
                                # We'll just ignore any out of place files.
                                continue
                            source = SourceFile.objects.create(project=project, file_name=base_filename, target=target)

                            with z.open(entry.filename) as f:
                                source.save_text(f.read().decode('utf-8'))

                    # Now add all the resource identifiers
                    for root_file_name in desired_resources:
                        for resource in desired_resources[root_file_name]:
                            target_platforms = json.dumps(resource['targetPlatforms']) if 'targetPlatforms' in resource else None
                            ResourceIdentifier.objects.create(
                                resource_file=resources_files[root_file_name],
                                resource_id=resource['name'],
                                target_platforms=target_platforms,
                                # Font options
                                character_regex=resource.get('characterRegex', None),
                                tracking=resource.get('trackingAdjust', None),
                                compatibility=resource.get('compatibility', None),
                                # Bitmap options
                                memory_format=resource.get('memoryFormat', None),
                                storage_format=resource.get('storageFormat', None),
                                space_optimisation=resource.get('spaceOptimization', None)
                            )

                    # Check that at least one variant of each specified resource exists.
                    for root_file_name, loaded in file_exists_for_root.iteritems():
                        if not loaded:
                            raise KeyError("No file was found to satisfy the manifest filename: {}".format(root_file_name))
                    project.save()
                    send_td_event('cloudpebble_zip_import_succeeded', project=project)

        # At this point we're supposed to have successfully created the project.
        return True
    except Exception as e:
        if delete_project:
            try:
                Project.objects.get(pk=project_id).delete()
            except:
                pass
        send_td_event('cloudpebble_zip_import_failed', data={
            'data': {
                'reason': str(e)
            }
        }, user=project.owner)
        raise