func get()

in Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift [66:140]


    func get(_ source: Model.CollectionSource, callback: @escaping (Result<Model.Collection, Error>) -> Void) {
        guard case .json = source.type else {
            return callback(.failure(InternalError("JSONPackageCollectionProvider can only be used for fetching 'json' package collections")))
        }

        if let errors = source.validate()?.errors() {
            return callback(.failure(JSONPackageCollectionProviderError.invalidSource("\(errors)")))
        }

        // Source is a local file
        if let absolutePath = source.absolutePath {
            do {
                let fileContents = try localFileSystem.readFileContents(absolutePath)
                return fileContents.withData { data in
                    self.decodeAndRunSignatureCheck(source: source, data: data, certPolicyKeys: Self.defaultCertPolicyKeys, callback: callback)
                }
            } catch {
                return callback(.failure(error))
            }
        }

        // first do a head request to check content size compared to the maximumSizeInBytes constraint
        let headOptions = self.makeRequestOptions(validResponseCodes: [200])
        let headers = self.makeRequestHeaders()
        self.httpClient.head(source.url, headers: headers, options: headOptions) { result in
            switch result {
            case .failure(HTTPClientError.badResponseStatusCode(let statusCode)):
                if statusCode == 404 {
                    return callback(.failure(JSONPackageCollectionProviderError.collectionNotFound(source.url)))
                } else {
                    return callback(.failure(JSONPackageCollectionProviderError.collectionUnavailable(source.url, statusCode)))
                }
            case .failure(let error):
                return callback(.failure(error))
            case .success(let response):
                guard let contentLength = response.headers.get("Content-Length").first.flatMap(Int64.init) else {
                    return callback(.failure(JSONPackageCollectionProviderError.invalidResponse(source.url, "Missing Content-Length header")))
                }
                guard contentLength <= self.configuration.maximumSizeInBytes else {
                    return callback(.failure(JSONPackageCollectionProviderError.responseTooLarge(source.url, contentLength)))
                }
                // next do a get request to get the actual content
                var getOptions = self.makeRequestOptions(validResponseCodes: [200])
                getOptions.maximumResponseSizeInBytes = self.configuration.maximumSizeInBytes
                self.httpClient.get(source.url, headers: headers, options: getOptions) { result in
                    switch result {
                    case .failure(HTTPClientError.badResponseStatusCode(let statusCode)):
                        if statusCode == 404 {
                            return callback(.failure(JSONPackageCollectionProviderError.collectionNotFound(source.url)))
                        } else {
                            return callback(.failure(JSONPackageCollectionProviderError.collectionUnavailable(source.url, statusCode)))
                        }
                    case .failure(let error):
                        return callback(.failure(error))
                    case .success(let response):
                        // check content length again so we can record this as a bad actor
                        // if not returning head and exceeding size
                        // TODO: store bad actors to prevent server DoS
                        guard let contentLength = response.headers.get("Content-Length").first.flatMap(Int64.init) else {
                            return callback(.failure(JSONPackageCollectionProviderError.invalidResponse(source.url, "Missing Content-Length header")))
                        }
                        guard contentLength < self.configuration.maximumSizeInBytes else {
                            return callback(.failure(JSONPackageCollectionProviderError.responseTooLarge(source.url, contentLength)))
                        }
                        guard let body = response.body else {
                            return callback(.failure(JSONPackageCollectionProviderError.invalidResponse(source.url, "Body is empty")))
                        }

                        let certPolicyKeys = self.sourceCertPolicy.certificatePolicyKeys(for: source) ?? Self.defaultCertPolicyKeys
                        self.decodeAndRunSignatureCheck(source: source, data: body, certPolicyKeys: certPolicyKeys, callback: callback)
                    }
                }
            }
        }
    }