# typed: strict# frozen_string_literal: truerequire"sorbet-runtime"require"dependabot/python/update_checker"require"dependabot/python/authed_url_builder"require"dependabot/errors"moduleDependabotmodulePythonmodulePackageclassPackageRegistryFinderextendT::SigPYPI_BASE_URL="https://pypi.org/simple/"ENVIRONMENT_VARIABLE_REGEX=/\$\{.+\}/UrlsHash=T.type_alias{{main: T.nilable(String),extra: T::Array[String]}}sigdoparams(dependency_files: T::Array[Dependabot::DependencyFile],credentials: T::Array[Dependabot::Credential],dependency: Dependabot::Dependency).voidenddefinitialize(dependency_files:,credentials:,dependency:)@dependency_files=T.let(dependency_files,T::Array[Dependabot::DependencyFile])@credentials=T.let(credentials,T::Array[Dependabot::Credential])@dependency=T.let(dependency,Dependabot::Dependency)endsig{returns(T::Array[String])}defregistry_urlsextra_index_urls=config_variable_index_urls[:extra]+pipfile_index_urls[:extra]+requirement_file_index_urls[:extra]+pip_conf_index_urls[:extra]+pyproject_index_urls[:extra]extra_index_urls=extra_index_urls.mapdo|url|clean_check_and_remove_environment_variables(url)end# URL encode any `@` characters within registry URL creds.# TODO: The test that fails if the `map` here is removed is likely a# bug in Ruby's URI parser, and should be fixed there.[main_index_url,*extra_index_urls].mapdo|url|url.rpartition("@").tap{|a|a.first.gsub!("@","%40")}.joinend.uniqendprivatesig{returns(T::Array[Dependabot::DependencyFile])}attr_reader:dependency_filessig{returns(T::Array[Dependabot::Credential])}attr_reader:credentialssig{returns(String)}defmain_index_urlurl=config_variable_index_urls[:main]||pipfile_index_urls[:main]||requirement_file_index_urls[:main]||pip_conf_index_urls[:main]||pyproject_index_urls[:main]||PYPI_BASE_URLclean_check_and_remove_environment_variables(url)endsig{returns(UrlsHash)}defrequirement_file_index_urlsurls=T.let({main: nil,extra: []},UrlsHash)requirements_files.eachdo|file|content=T.must(file.content)ifcontent.match?(/^--index-url\s+['"]?([^\s'"]+)['"]?/)urls[:main]=T.must(content.match(/^--index-url\s+['"]?([^\s'"]+)['"]?/)).captures.first&.stripendextra_urls=urls[:extra]extra_urls+=content.scan(/^--extra-index-url\s+['"]?([^\s'"]+)['"]?/).flatten.map(&:strip)urls[:extra]=extra_urlsendurlsendsig{returns(UrlsHash)}defpip_conf_index_urlsurls=T.let({main: nil,extra: []},UrlsHash)returnurlsunlesspip_confcontent=T.must(pip_conf).contentreturnurlsunlesscontentifcontent.match?(/^index-url\s*=/x)urls[:main]=T.must(content.match(/^index-url\s*=\s*(.+)/)).captures.firstendextra_urls=urls[:extra]extra_urls+=content.scan(/^extra-index-url\s*=(.+)/).flattenurls[:extra]=extra_urlsurlsendsig{returns(UrlsHash)}defpipfile_index_urlsurls=T.let({main: nil,extra: []},UrlsHash)beginreturnurlsunlesspipfilecontent=T.must(pipfile).contentreturnurlsunlesscontentpipfile_object=TomlRB.parse(content)urls[:main]=pipfile_object["source"]&.first&.fetch("url",nil)pipfile_object["source"]&.eachdo|source|urls[:extra]<<source.fetch("url")ifsource["url"]endurls[:extra]=urls[:extra].uniqurlsrescueTomlRB::ParseError,TomlRB::ValueOverwriteErrorurlsendendsig{returns(UrlsHash)}defpyproject_index_urlsurls=T.let({main: nil,extra: []},UrlsHash)beginreturnurlsunlesspyprojectsources=TomlRB.parse(T.must(T.must(pyproject).content)).dig("tool","poetry","source")||[]sources.eachdo|source|# If source is PyPI, skip it, and let it pick the default URInextifsource["name"].casecmp?("PyPI")if@dependency.all_sources.include?(source["name"])# If dependency has specified this source, use itreturn{main: source["url"],extra: []}elsifsource["default"]urls[:main]=source["url"]elsifsource["priority"]!="explicit"# if source is not explicit, add it to extraurls[:extra]<<source["url"]endendurls[:extra]=urls[:extra].uniqurlsrescueTomlRB::ParseError,TomlRB::ValueOverwriteErrorurlsendendsig{returns(UrlsHash)}defconfig_variable_index_urlsurls=T.let({main: nil,extra: []},UrlsHash)index_url_creds=credentials.select{|cred|cred["type"]=="python_index"}if(main_cred=index_url_creds.find(&:replaces_base?))urls[:main]=AuthedUrlBuilder.authed_url(credential: main_cred)endurls[:extra]=index_url_creds.reject(&:replaces_base?).map{|cred|AuthedUrlBuilder.authed_url(credential: cred)}urlsendsig{params(url: String).returns(String)}defclean_check_and_remove_environment_variables(url)url=url.strip.sub(%r{/+$},"")+"/"returnauthed_base_url(url)unlessurl.match?(ENVIRONMENT_VARIABLE_REGEX)config_variable_urls=[config_variable_index_urls[:main],*config_variable_index_urls[:extra]].compact.map{|u|u.strip.gsub(%r{/*$},"")+"/"}regexp=url.sub(%r{(?<=://).+@},"").sub(%r{https?://},"").split(ENVIRONMENT_VARIABLE_REGEX).map{|part|Regexp.quote(part)}.join(".+")authed_url=config_variable_urls.find{|u|u.match?(regexp)}returnauthed_urlifauthed_urlcleaned_url=url.gsub(%r{#{ENVIRONMENT_VARIABLE_REGEX}/?}o,"")authed_url=authed_base_url(cleaned_url)returnauthed_urlifcredential_for(cleaned_url)raisePrivateSourceAuthenticationFailure,urlendsig{params(base_url: String).returns(String)}defauthed_base_url(base_url)cred=credential_for(base_url)returnbase_urlunlesscredAuthedUrlBuilder.authed_url(credential: cred).gsub(%r{/*$},"")+"/"endsig{params(url: String).returns(T.nilable(Dependabot::Credential))}defcredential_for(url)credentials.select{|c|c["type"]=="python_index"}.finddo|c|cred_url=c.fetch("index-url").gsub(%r{/*$},"")+"/"cred_url.include?(url)endendsig{returns(T.nilable(Dependabot::DependencyFile))}defpip_confdependency_files.find{|f|f.name=="pip.conf"}endsig{returns(T.nilable(Dependabot::DependencyFile))}defpipfiledependency_files.find{|f|f.name=="Pipfile"}endsig{returns(T.nilable(Dependabot::DependencyFile))}defpyprojectdependency_files.find{|f|f.name=="pyproject.toml"}endsig{returns(T::Array[Dependabot::DependencyFile])}defrequirements_filesdependency_files.select{|f|f.name.match?(/requirements/x)}endsig{returns(T::Array[Dependabot::DependencyFile])}defpip_compile_filesdependency_files.select{|f|f.name.end_with?(".in")}endendendendend