diff --git a/Model/Accounts/AccountsBridge.swift b/Model/Accounts/AccountsBridge.swift
index 70fe94e3..8997dade 100644
--- a/Model/Accounts/AccountsBridge.swift
+++ b/Model/Accounts/AccountsBridge.swift
@@ -10,11 +10,28 @@ struct AccountsBridge: Defaults.Bridge {
return nil
}
+ // Parse the urlString to check for embedded username and password
+ var sanitizedUrlString = value.urlString
+ if var urlComponents = URLComponents(string: value.urlString) {
+ if let user = urlComponents.user, let password = urlComponents.password {
+ // Sanitize the embedded username and password
+ let sanitizedUser = user.addingPercentEncoding(withAllowedCharacters: .urlUserAllowed) ?? user
+ let sanitizedPassword = password.addingPercentEncoding(withAllowedCharacters: .urlPasswordAllowed) ?? password
+
+ // Update the URL components with sanitized credentials
+ urlComponents.user = sanitizedUser
+ urlComponents.password = sanitizedPassword
+
+ // Reconstruct the sanitized URL
+ sanitizedUrlString = urlComponents.string ?? value.urlString
+ }
+ }
+
return [
"id": value.id,
"instanceID": value.instanceID ?? "",
"name": value.name,
- "apiURL": value.urlString,
+ "apiURL": sanitizedUrlString,
"username": value.username,
"password": value.password ?? ""
]
diff --git a/Model/Applications/InvidiousAPI.swift b/Model/Applications/InvidiousAPI.swift
index c3c1084e..edb854a9 100644
--- a/Model/Applications/InvidiousAPI.swift
+++ b/Model/Applications/InvidiousAPI.swift
@@ -247,27 +247,27 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
func feed(_ page: Int?) -> Resource? {
- resource(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
+ resourceWithAuthCheck(baseURL: account.url, path: "\(Self.basePath)/auth/feed")
.withParam("page", String(page ?? 1))
}
var feed: Resource? {
- resource(baseURL: account.url, path: basePathAppending("auth/feed"))
+ resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/feed"))
}
var subscriptions: Resource? {
- resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
+ resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
}
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
- resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
+ resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.post)
.onCompletion { _ in onCompletion() }
}
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void) {
- resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
+ resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.delete)
.onCompletion { _ in onCompletion() }
@@ -308,11 +308,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
return nil
}
- return resource(baseURL: account.url, path: basePathAppending("auth/playlists"))
+ return resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists"))
}
func playlist(_ id: String) -> Resource? {
- resource(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
+ resourceWithAuthCheck(baseURL: account.url, path: basePathAppending("auth/playlists/\(id)"))
}
func playlistVideos(_ id: String) -> Resource? {
@@ -445,6 +445,9 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
urlComponents.scheme = instanceURLComponents.scheme
urlComponents.host = instanceURLComponents.host
+ urlComponents.user = instanceURLComponents.user
+ urlComponents.password = instanceURLComponents.password
+ urlComponents.port = instanceURLComponents.port
guard let url = urlComponents.url else {
return nil
@@ -553,6 +556,30 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
)
}
+ // Determines if the request requires Basic Auth credentials to be removed
+ private func needsBasicAuthRemoval(for path: String) -> Bool {
+ return path.hasPrefix("\(Self.basePath)/auth/")
+ }
+
+ // Creates a resource URL with consideration for removing Basic Auth credentials
+ private func createResourceURL(baseURL: URL, path: String) -> URL {
+ var resourceURL = baseURL
+
+ // Remove Basic Auth credentials if required
+ if needsBasicAuthRemoval(for: path), var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) {
+ urlComponents.user = nil
+ urlComponents.password = nil
+ resourceURL = urlComponents.url ?? baseURL
+ }
+
+ return resourceURL.appendingPathComponent(path)
+ }
+
+ func resourceWithAuthCheck(baseURL: URL, path: String) -> Resource {
+ let sanitizedURL = createResourceURL(baseURL: baseURL, path: path)
+ return super.resource(absoluteURL: sanitizedURL)
+ }
+
private func extractThumbnails(from details: JSON) -> [Thumbnail] {
details["videoThumbnails"].arrayValue.compactMap { json in
guard let url = json["url"].url,
@@ -563,13 +590,20 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
return nil
}
- // some of instances are not configured properly and return thumbnails links
- // with incorrect scheme
+ // Some instances are not configured properly and return thumbnail links
+ // with an incorrect scheme or a missing port.
components.scheme = accountUrlComponents.scheme
+ components.port = accountUrlComponents.port
+
+ // If basic HTTP authentication is used,
+ // the username and password need to be prepended to the URL.
+ components.user = accountUrlComponents.user
+ components.password = accountUrlComponents.password
guard let thumbnailUrl = components.url else {
return nil
}
+ print("Final thumbnail URL: \(thumbnailUrl)")
return Thumbnail(url: thumbnailUrl, quality: .init(rawValue: quality)!)
}
diff --git a/Shared/Settings/InstanceForm.swift b/Shared/Settings/InstanceForm.swift
index c78e1056..c74bb052 100644
--- a/Shared/Settings/InstanceForm.swift
+++ b/Shared/Settings/InstanceForm.swift
@@ -86,6 +86,7 @@ struct InstanceForm: View {
.autocapitalization(.none)
.keyboardType(.URL)
#endif
+ .disableAutocorrection(true)
#if os(tvOS)
VStack {
diff --git a/Yattee.xcodeproj/xcshareddata/xcschemes/Yattee (iOS).xcscheme b/Yattee.xcodeproj/xcshareddata/xcschemes/Yattee (iOS).xcscheme
index dccf93ab..f526290d 100644
--- a/Yattee.xcodeproj/xcshareddata/xcschemes/Yattee (iOS).xcscheme
+++ b/Yattee.xcodeproj/xcshareddata/xcschemes/Yattee (iOS).xcscheme
@@ -66,6 +66,11 @@
value = "Yes"
isEnabled = "YES">
+
+