Parse-Swift
Parse-Swift copied to clipboard
Error while query users with emails that contains +(plus) character
New Issue Checklist
- [x] I am not disclosing a vulnerability.
- [x] I am not just asking a question.
- [x] I have searched through existing issues.
- [x] I can reproduce the issue with the latest versions of Parse Server and the Parse Swift SDK.
Issue Description
When I try to query object from my database (based on back4app) with email that contains +(plus) characters I got Parse Error "Object not found". If I try to query objects with email that contains other special characters- "_" or "." everything working ok.
Steps to reproduce
- create _User object with email that contains +(plus) character. For example "[email protected]"
- fetch parse object from swift code (example with login below )
func login(user: ParseUserObject) async throws {
guard let email = user.email?.lowercased(), let password = user.password else {
throw ParseError.Code.unknownError
}
// Fetch username from Parse(user provide only email/pass for login)
var constraints = [QueryConstraint]()
constraints.append(equalTo(key: "email", value: email))
let query = ParseUserObject.query(constraints)
let parseUser = try await query.first()
// or you can use
// let query = ParseUserObject.query("email" == email.lowercased())
guard let username = parseUser.username else {
throw ParseError.Code.unknownError
}
try await ParseUserObject.login(username: username, password: password)
setIsAuthorized(true)
}
Actual Outcome
try await query.first() throw an Parse Error (object not found)
Expected Outcome
Existing object
Environment
Xcode, iOS simulator (or physical iPhone), parse database based on back4app service
Client
- Parse Swift SDK version:
4.14.2 - Xcode version:
15.4 - Operating system (iOS, macOS, watchOS, etc.):
iOS - Operating system version:
18.0
Server
- Parse Server version:
3.10.0 - Operating system:
Linux(maybe) - Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc):
back4app server
Database
- System (MongoDB or Postgres):
MongoDB - Database version:
3.6 - Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc):
back4app server
Logs
▿ ParseError code=101 error=Object not found on the server.
- code : ParseSwift.ParseError.Code.objectNotFound
- message : "Object not found on the server."
- otherCode : nil
- error : nil
Thanks for opening this issue!
- 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.
as workaround for someone ... You can you back4app REST API and decode object manually.
func getUserWithEmail(_ email: String) async throws -> ParseUserObject? {
// Configure query json value
let whereQuery: [String: Any] = ["email": email]
let queryJSONData = try JSONSerialization.data(withJSONObject: whereQuery, options: [])
guard let jsonString = String(data: queryJSONData, encoding: .utf8) else { throw URLError(.badURL) }
// Configure url and encode "+" character manualy
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "parseapi.back4app.com"
urlComponents.path = "/users"
urlComponents.queryItems = [.init(name: "where", value: jsonString)]
urlComponents.percentEncodedQuery = urlComponents.percentEncodedQuery?
.replacingOccurrences(of: "+", with: "%2B")
guard let url = urlComponents.url else { throw URLError(.badURL) }
// Performing request
var request = URLRequest(url: url)
request.httpMethod = "GET"
guard let appID = Bundle.main.object(forInfoDictionaryKey: "ParseAppID") as? String,
let restApiKey = Bundle.main.object(forInfoDictionaryKey: "ParseRestApiKey") as? String
else {
fatalError("Cannot find Parse SDK credentials")
}
request.addValue(appID, forHTTPHeaderField: "X-Parse-Application-Id")
request.addValue(restApiKey, forHTTPHeaderField: "X-Parse-REST-API-Key")
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode)
else {
throw URLError(.badServerResponse)
}
let resposeArray = try JSONDecoder().decode(ParseResponse<ParseUserObject>.self, from: data)
guard let parseUser = resposeArray.results.first else { throw ParseError.Code.userWithEmailNotFound}
return parseUser
}
struct ParseResponse<T>: Codable where T: Codable {
let results: [T]
}
struct ParseUserObject: ParseUser {
enum CodingKeys: String, CodingKey {
case username, email, emailVerified, password, authData, originalData
case objectId, createdAt, updatedAt, ACL, displayName
}
var username: String?
var email: String?
var emailVerified: Bool?
var password: String?
var authData: [String : [String : String]?]?
var originalData: Data?
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var displayName: String?
init() {}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.username = try container.decodeIfPresent(String.self, forKey: .username)
self.email = try container.decodeIfPresent(String.self, forKey: .email)
self.emailVerified = try container.decodeIfPresent(Bool.self, forKey: .emailVerified)
self.password = try container.decodeIfPresent(String.self, forKey: .password)
self.authData = try container.decodeIfPresent([String: [String: String]?].self, forKey: .authData)
self.originalData = try container.decodeIfPresent(Data.self, forKey: .originalData)
self.objectId = try container.decodeIfPresent(String.self, forKey: .objectId)
self.ACL = try container.decodeIfPresent(ParseACL.self, forKey: .ACL)
self.displayName = try container.decodeIfPresent(String.self, forKey: .displayName)
self.createdAt = try? container.decodeIfPresent(Date.self, forKey: .createdAt)
self.updatedAt = try? container.decodeIfPresent(Date.self, forKey: .updatedAt)
}
}
extension ParseUserObject {
init(email: String, password: String) {
self.email = email
self.password = password
}
}