コード例 #1
0
def parse_nuspec(nuspec, ns=None):
    """
    Parameters
    ----------
    nuspec : :class:`lxml.etree.Element` object
        The parsed nuspec data.
    ns : string
        The namespace to search.

    Returns
    -------
    metadata : :class:`lxml.etree.Element`
    pkg_name : str
    version : str
    """
    metadata = nuspec.find('nuspec:metadata', ns)
    if metadata is None:
        msg = 'Unable to find the metadata tag!'
        logger.error(msg)
        raise ApiException(msg)

    # TODO: I think I need different error handling around `.text`.
    pkg_name = metadata.find('nuspec:id', ns)
    version = metadata.find('nuspec:version', ns)
    if pkg_name is None or version is None:
        logger.error("ID or version missing from NuSpec file.")
        raise ApiException("api_error: ID or version missing")  # TODO

    return metadata, pkg_name.text, version.text
コード例 #2
0
def require_auth(headers):
    """Ensure that the API key is valid."""
    key = headers.get('X-Nuget-Apikey', None)
    is_valid = key is not None and key in current_app.config['API_KEYS']
    if not is_valid:
        logger.error("Missing or Invalid API key")
    return is_valid
コード例 #3
0
def extract_nuspec(file):
    """
    Parameters
    ----------
    file : :class:`pathlib.Path` object or str
        The file as retrieved by Flask.
    """
    pkg = ZipFile(str(file), 'r')
    logger.debug("Parsing uploaded file.")
    nuspec_file = None
    pattern = re.compile(r'^.*\.nuspec$', re.IGNORECASE)
    nuspec_file = list(filter(pattern.search, pkg.namelist()))
    if len(nuspec_file) > 1:
        logger.error("Multiple NuSpec files found within the package.")
        raise ApiException("api_error: multiple nuspec files found")
    elif len(nuspec_file) == 0:
        logger.error("No NuSpec file found in the package.")
        raise ApiException("api_error: nuspec file not found")  # TODO
    nuspec_file = nuspec_file[0]

    with pkg.open(nuspec_file, 'r') as openf:
        nuspec_string = openf.read()
        logger.debug("NuSpec string:")
        logger.debug(nuspec_string)

    logger.debug("Parsing NuSpec file XML")
    nuspec = et.fromstring(nuspec_string)
    if not et.iselement(nuspec):
        msg = "`nuspec` expected to be type `xml...Element`. Got {}"
        raise TypeError(msg.format(type(nuspec)))

    return nuspec
コード例 #4
0
def save_file(file, pkg_name, version):
    """
    Parameters
    ----------
    file : :class:`werkzeug.datastructures.FileStorage` object
        The file as retrieved by Flask.
    pkg_name : str
    version : str

    Returns:
    --------
    local_path : :class:`pathlib.Path`
        The path to the saved file.
    """
    # Save the package file to the local package dir. Thus far it's
    # just been floating around in magic Flask land.
    server_path = Path(current_app.config['SERVER_PATH'])
    package_dir = Path(current_app.config['PACKAGE_DIR'])
    local_path = server_path / package_dir
    local_path = local_path / pkg_name / (version + ".nupkg")

    # Check if the package's directory already exists. Create if needed.
    create_parent_dirs(local_path)

    logger.debug("Saving uploaded file to filesystem.")
    try:
        file.save(str(local_path))
    except Exception as err:  # TODO: specify exceptions
        logger.error("Unknown exception: %s" % err)
        raise err
    else:
        logger.info("Succesfully saved package to '%s'" % str(local_path))

    return local_path
コード例 #5
0
ファイル: routes.py プロジェクト: pombredanne/pynuget
def find_by_id(func_args=None):
    """
    Used by `nuget install`.

    It looks like the NuGet client expects this to send back a list of all
    versions for the package, and then the client handles extraction of
    an individual version.

    Note that this is different from the newer API
        /Packages(Id='pkg_name',Version='0.1.3')
    which appears to move the version selection to server-side.
    """
    logger.debug("Route: /find_by_id")
    logger.debug("  args: {}".format(request.args))
    logger.debug("  header: {}".format(request.headers))

    if func_args is not None:
        # Using the newer API
        logger.debug(func_args)
        # Parse the args. From what I can tell, the only things sent here are:
        #   'Id': the name of the package
        #   'Version': the version string.
        # Looks like:
        #   "Id='NuGetTest',Version='0.0.2'"
        # Naive implementation
        regex = re.compile(r"^Id='(?P<name>.+)',Version='(?P<version>.+)'$")
        match = regex.search(func_args)
        if match is None:
            msg = "Unable to parse the arg string `{}`!"
            logger.error(msg.format(func_args))
            return msg.format(func_args), 500

        pkg_name = match.group('name')
        version = match.group('version')
        logger.debug("{}, {}".format(pkg_name, version))
    else:
        # old API
        pkg_name = request.args.get('id')
        sem_ver_level = request.args.get('semVerLevel', default=None)
        version = None

    # Some terms are quoted
    pkg_name = pkg_name.strip("'")

    results = db.find_by_pkg_name(session, pkg_name, version)
    logger.debug(results)
    feed = FeedWriter('FindPackagesById', request.url_root)
    resp = make_response(feed.write_to_output(results))
    resp.headers['Content-Type']

    # This will spam logs!
    #logger.debug(resp.data.decode('utf-8').replace('><',' >\n<'))

    return resp
コード例 #6
0
ファイル: routes.py プロジェクト: pombredanne/pynuget
def push():
    """
    Used by `nuget push`.
    """
    logger.debug("push()")
    logger.debug("  args: {}".format(request.args))
    logger.debug("  header: {}".format(request.headers))
    if not core.require_auth(request.headers):
        return "api_error: Missing or Invalid API key", 401

    logger.debug("Checking for uploaded file.")
    if 'package' not in request.files:
        logger.error("Package file was not uploaded.")
        return "error: File not uploaded", 409
    file = request.files['package']

    # Save the file to a temporary location
    file = core.save_file(file, "_temp", str(uuid4()))

    # Open the zip file that was sent and extract out the .nuspec file."
    try:
        nuspec = core.extract_nuspec(file)
    except Exception as err:
        logger.error("Exception: %s" % err)
        return "api_error: Zero or multiple nuspec files found", 400

    # The NuSpec XML file uses namespaces.
    ns = {'nuspec': core.extract_namespace(nuspec)}

    # Make sure both the ID and the version are provided in the .nuspec file.
    try:
        metadata, pkg_name, version = core.parse_nuspec(nuspec, ns)
    except ApiException as err:
        return str(err), 400
    except Exception as err:
        logger.error(err)
        return str(err), 400

    valid_id = re.compile('^[A-Z0-9\.\~\+\_\-]+$', re.IGNORECASE)

    # Make sure that the ID and version are sane
    if not re.match(valid_id, pkg_name) or not re.match(valid_id, version):
        logger.error("Invalid ID or version.")
        return "api_error: Invlaid ID or Version", 400

    # and that we don't already have that ID+version in our database
    if db.validate_id_and_version(session, pkg_name, version):
        logger.error("Package %s version %s already exists" %
                     (pkg_name, version))
        return "api_error: Package version already exists", 409

    # Hash the uploaded file and encode the hash in Base64. For some reason.
    try:
        # rename our file.
        # Check if the package's directory already exists. Create if needed.
        new = file.parent.parent / pkg_name / (version + ".nupkg")
        core.create_parent_dirs(new)
        logger.debug("Renaming %s to %s" % (str(file), new))
        file.rename(new)
        hash_, filesize = core.hash_and_encode_file(str(new))
    except Exception as err:
        logger.error("Exception: %s" % err)
        return "api_error: Unable to save file", 500

    try:
        dependencies = core.determine_dependencies(metadata, ns)
    except Exception as err:
        logger.error("Exception: %s" % err)
        return "api_error: Unable to parse dependencies.", 400

    logger.debug(dependencies)

    # and finaly, update our database.
    logger.debug("Updating database entries.")

    db.insert_or_update_package(session,
                                package_name=pkg_name,
                                title=et_to_str(
                                    metadata.find('nuspec:title', ns)),
                                latest_version=version)
    pkg_id = (session.query(
        db.Package).filter(db.Package.name == pkg_name).one()).package_id
    logger.debug("package_id = %d" % pkg_id)
    db.insert_version(
        session,
        authors=et_to_str(metadata.find('nuspec:authors', ns)),
        copyright_=et_to_str(metadata.find('nuspec:copyright', ns)),
        dependencies=dependencies,
        description=et_to_str(metadata.find('nuspec:description', ns)),
        package_hash=hash_,
        package_hash_algorithm='SHA512',
        package_size=filesize,
        icon_url=et_to_str(metadata.find('nuspec:iconUrl', ns)),
        is_prerelease='-' in version,
        license_url=et_to_str(metadata.find('nuspec:licenseUrl', ns)),
        owners=et_to_str(metadata.find('nuspec:owners', ns)),
        package_id=pkg_id,
        project_url=et_to_str(metadata.find('nuspec:projectUrl', ns)),
        release_notes=et_to_str(metadata.find('nuspec:releaseNotes', ns)),
        require_license_acceptance=et_to_str(
            metadata.find('nuspec:requireLicenseAcceptance', ns)) == 'true',
        tags=et_to_str(metadata.find('nuspec:tags', ns)),
        title=et_to_str(metadata.find('nuspec:id', ns)),
        version=version,
    )

    logger.info(
        "Sucessfully updated database entries for package %s version %s." %
        (pkg_name, version))

    resp = make_response('', 201)
    return resp