Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP Authentication #81

Merged
merged 22 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/SwiftLint-Linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: SwiftLint - Linux

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/realm/swiftlint:latest

steps:
- uses: actions/checkout@v1
with:
fetch-depth: 1

- name: Run SwiftLint and annotate
run: |
# Run SwiftLint and fail the job if any violations are found
swiftlint lint --reporter github-actions-logging || exit 1
42 changes: 42 additions & 0 deletions .github/workflows/SwiftLint-macOS.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: SwiftLint - macOS

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
lint:
name: Run SwiftLint
runs-on: macos-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0

- name: Install SwiftLint
run: brew install swiftlint

- name: Run SwiftLint and annotate
run: |
# Run SwiftLint and capture output
OUTPUT=$(swiftlint lint --reporter github-actions-logging)

# Print the output so GitHub Actions can pick up annotations
echo "$OUTPUT"

# Fail the job if any violations are found
if [ -n "$OUTPUT" ]; then
echo "SwiftLint violations found. Failing the action."
exit 1
else
echo "No SwiftLint violations found."
fi
32 changes: 0 additions & 32 deletions .github/workflows/swiftlint.yml

This file was deleted.

7 changes: 6 additions & 1 deletion Sources/FaviconFinder/FaviconFinder+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public extension FaviconFinder {
/// Indicates if we should check for a meta-refresh-redirect tag in the HTML header
public let checkForMetaRefreshRedirect: Bool

/// The HTTP headers we'll pass along to our HTTP request
public let httpHeaders: [String: String?]?

public let prefetchedHTML: Document?

// MARK: - Lifecycle
Expand All @@ -31,12 +34,14 @@ public extension FaviconFinder {
preferredSource: FaviconSourceType = .html,
preferences: [FaviconSourceType: String] = [:],
checkForMetaRefreshRedirect: Bool = false,
prefetchedHTML: Document? = nil
prefetchedHTML: Document? = nil,
httpHeaders: [String: String?]? = nil
) {
self.preferredSource = preferredSource
self.preferences = preferences
self.checkForMetaRefreshRedirect = checkForMetaRefreshRedirect
self.prefetchedHTML = prefetchedHTML
self.httpHeaders = httpHeaders
}
}

Expand Down
155 changes: 94 additions & 61 deletions Sources/FaviconFinder/Toolbox/FaviconURLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class FaviconURLSession {

static func dataTask(
with url: URL,
checkForMetaRefreshRedirect: Bool = false
checkForMetaRefreshRedirect: Bool = false,
httpHeaders: [String?: String]? = nil
) async throws -> Response {
#if os(Linux)
try await linuxDataTask(
Expand All @@ -56,87 +57,110 @@ private extension FaviconURLSession {

static func linuxDataTask(
with url: URL,
checkForMetaRefreshRedirect: Bool = false
checkForMetaRefreshRedirect: Bool = false,
httpHeaders: [String: String?]? = nil
) async throws -> Response {
let response = Response(try await URLSession.shared.data(from: url))

let data = response.data

if checkForMetaRefreshRedirect {
// Make sure we can parse the response into a string
guard let htmlStr = String(data: data, encoding: response.textEncoding) else {
return response
}

// Parse the string into a workable HTML object
let html = try SwiftSoup.parse(htmlStr)

// Get the head of the HTML
guard let head = html.head() else {
return response
}
let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
defer {
try? httpClient.syncShutdown()
}

// Get all meta-refresh-redirect tag
let httpEquivs = try head.getElementsByAttribute("http-equiv")
guard let httpEquiv = try httpEquivs.whereAttr("http-equiv", equals: "refresh") else {
return response
// Convert headers to HTTPHeaders
var headers = HTTPHeaders()
if let httpHeaders = httpHeaders {
for (key, value) in httpHeaders {
headers.add(name: key, value: value ?? "")
}
}

// Get the URL
var redirectURLStr = try httpEquiv.attr("content")

// Remove the 0;URL=
redirectURLStr = redirectURLStr.replacingOccurrences(of: "0;URL=", with: "")
// Create the request
var request = HTTPClientRequest(url: url.absoluteString)
request.method = .GET
request.headers = headers

// Determine if this is a whole new URL, or something we should append to the current one
let brandNewURL = Regex.testForHttpsOrHttp(input: redirectURLStr)
// Send the request
let response = try await httpClient.execute(request, timeout: .seconds(30))

// If this is a brand new URL
if brandNewURL {
// If we can't form a valid redirect URL, we'll just return the data from the original page
guard let redirectURL = URL(string: redirectURLStr) else {
return response
}
// Collect the response body
let byteBuffer = try await response.body.collect(upTo: Int.max)
let data = Data(buffer: byteBuffer)

let redirectResponse = Response(try await URLSession.shared.data(from: redirectURL))
return redirectResponse
// Check for meta-refresh redirect if needed
if checkForMetaRefreshRedirect {
// swiftlint:disable:next non_optional_string_data_conversion
guard let htmlStr = String(data: data, encoding: .utf8) else {
throw URLError(.badServerResponse)
}
let html = try SwiftSoup.parse(htmlStr)

// If this something we should append to our current URL
else {
let needsPrependingSlash = url.absoluteString.last != "/" && redirectURLStr.first != "/"
if needsPrependingSlash {
redirectURLStr = "\(url.absoluteString)/\(redirectURLStr)"
} else {
redirectURLStr = "\(url.absoluteString)\(redirectURLStr)"
}

// If we can't form a valid redirect URL, we'll just return the data from the original page
guard let redirectURL = URL(string: redirectURLStr) else {
return response
if let head = html.head() {
let httpEquiv = try head
.getElementsByAttribute("http-equiv")
.whereAttr("http-equiv", equals: "refresh")

if let httpEquiv {
var redirectURLStr = try httpEquiv
.attr("content")
.replacingOccurrences(of: "0;URL=", with: "")
let brandNewURL = Regex.testForHttpsOrHttp(input: redirectURLStr)

if brandNewURL, let redirectURL = URL(string: redirectURLStr) {
return try await linuxDataTask(
with: redirectURL,
checkForMetaRefreshRedirect: false,
httpHeaders: httpHeaders
)
} else {
let needsPrependingSlash = url.absoluteString.last != "/" && redirectURLStr.first != "/"
if needsPrependingSlash {
redirectURLStr = "\(url.absoluteString)/\(redirectURLStr)"
} else {
redirectURLStr = "\(url.absoluteString)\(redirectURLStr)"
}
if let redirectURL = URL(string: redirectURLStr) {
return try await linuxDataTask(
with: redirectURL,
checkForMetaRefreshRedirect: false,
httpHeaders: httpHeaders
)
}
}
}

let redirectResponse = Response(try await URLSession.shared.data(from: redirectURL))
return redirectResponse
}
} else {
// We're not supposed to check for the meta-refresh-redirect,
// so just return the data.
return response
}

// Return the response with data and headers
return Response((data, response.headers))
}

#else

// swiftlint:disable:next cyclomatic_complexity
static func appleDataTask(
with url: URL,
checkForMetaRefreshRedirect: Bool = false
checkForMetaRefreshRedirect: Bool = false,
httpHeaders: [String: String?]? = nil
) async throws -> Response {
let response = Response(try await URLSession.shared.data(from: url))
// Create our request
var request = URLRequest(url: url)

// If there's HTTP headers, add them
if let httpHeaders {
for (key, value) in httpHeaders {
request.setValue(value, forHTTPHeaderField: key)
}
}

let data = response.data
// Fetch our response
let response = Response(
try await URLSession.shared.data(for: request)
)

// If the user wants to check for meta-refresh-redirect, do so and
// if we find a redirect, follow that up
if checkForMetaRefreshRedirect {
let data = response.data

// Make sure we can parse the response into a string
guard let htmlStr = String(data: data, encoding: response.textEncoding) else {
return response
Expand Down Expand Up @@ -191,7 +215,16 @@ private extension FaviconURLSession {
return response
}

let redirectResponse = Response(try await URLSession.shared.data(from: redirectURL))
var redirectRequest = URLRequest(url: redirectURL)
if let httpHeaders {
for (key, value) in httpHeaders {
redirectRequest.setValue(value, forHTTPHeaderField: key)
}
}

let redirectResponse = Response(
try await URLSession.shared.data(from: redirectURL)
)
return redirectResponse
}
}
Expand Down
Loading