Alamofire post request with params in the URL in swift - swift

I have a post method with base url and various parameters appended in the base url itself.
The code is given below:
let todosEndpoint:String = "https://xxxxxxx/api/post_schedule_form_data?service_type=nanny&start_date=07/20/2020&start_time=06:00&end_date=07/20/2020&end_time=09:00&work_description=Work Description&special_instructions=Special Instructions&location_address=location_address&postal_code=abc123&current_profile_id=10"
let header: HTTPHeaders = ["Content-Type":"application/json","x-token":self.usertoken!]
print("the url is",todosEndpoint)
AF.request(todosEndpoint, method: .post, encoding: JSONEncoding.default, headers: header)
.responseJSON { response in
switch response.result {
case .success(let json):
print("Validation Successful",json)
case let .failure(error):
print(error)
}
}
I do not get any response in the code.What could the error be?

Your headers are not going through correctly...
Also, you're passing " " (spaces) in your URL, just replace them with "%20".
The correct way to do this would be:
let url:String = "https://xxxxxxx/api/post_schedule_form_data?service_type=nanny&start_date=07/20/2020&start_time=06:00&end_date=07/20/2020&end_time=09:00&work_description=Work Description&special_instructions=Special Instructions&location_address=location_address&postal_code=abc123&current_profile_id=10".replacingOccurrences(of: " ", with: "%20")
var request = URLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(self.usertoken!, forHTTPHeaderField: "x-token")
AF.request(request).responseJSON { response in
switch response.result {
case .success(let json):
print("Validation Successful",json)
case let .failure(error):
print(error)
}
}
You are force unwrapping self.usertoken, so beware of that as well!
Also seems like the date in the URL has "/" which you must change to "%2F" in the URL or better the format in the backend!

Looks to me like you've got a number of illegal characters in your query params (e.g. ":", "/", " ").
I suggest you build your URL using URLComponents. Pass in the endpoint and the query params and then get back the path. That will show you what needs to be escaped.
Edit:
I guess I was wrong about ":" and "/". It looks like those are legal as part of a query param value.
This code:
var components = URLComponents()
components.scheme = "https"
components.host = "xxxxxxx"
components.path = "/api/post_schedule_form_data"
components.queryItems = [
URLQueryItem(name: "service_type", value: "nanny"),
URLQueryItem(name: "start_date", value: "07/20/2020"),
URLQueryItem(name: "start_time", value: "06:00"),
URLQueryItem(name: "end_date", value: "07/20/2020"),
URLQueryItem(name: "end_time", value: "09:00"),
URLQueryItem(name: "work_description", value: "Work Description"),
URLQueryItem(name: "special_instructions", value: "Special Instructions"),
URLQueryItem(name: "location_address", value: "location_address"),
URLQueryItem(name: "postal_code", value: "abc123"),
URLQueryItem(name: "current_profile_id", value: "10"),
]
print(components.string ?? "nil")
Outputs the string
https://xxxxxxx/api/post_schedule_form_data?service_type=nanny&start_date=07/20/2020&start_time=06:00&end_date=07/20/2020&end_time=09:00&work_description=Work%20Description&special_instructions=Special%20Instructions&location_address=location_address&postal_code=abc123&current_profile_id=10
Note that the spaces get escaped as %20.
That is probably the only illegal part of your URL string.
URLComponents is your friend because it applies escaping to the different parts of the URL as needed, and enforces the correct escaping rules for each of the different parts of the URL.
Edit #2:
This code:
let urlString: String = "https://xxxxxxx/api/post_schedule_form_data?service_type=nanny&start_date=07/20/2020&start_time=06:00&end_date=07/20/2020&end_time=09:00&work_description=Work Description&special_instructions=Special Instructions&location_address=location_address&postal_code=abc123&current_profile_id=10"
let componentsFromString = URLComponents(string: urlString)
print(componentsFromString?.string ?? "nil" )
displays "nil"
That tells you there is something illegal about your URL string.

Related

Swift: padding error with base64 encoded string

I'm trying to upload a file as an attachment to my Frappe instance and running into a couple of problems. The first of which is the server reporting a padding error.
I'm attempting to read a file (on iPad simulator) as data, then convert to a base64 encoded string, and then using this as part of my httpbody. I've tried this with a couple of different file types, but for the purpose of this example i'm just using a local settings file.
The Frappe instance is (sometimes depending on the data) returning the following error:
File \"/usr/lib/python3.7/base64.py\", line 87, in b64decode\n return binascii.a2b_base64(s)\nbinascii.Error: Incorrect padding
Get data from local URL:
let settingsLocation = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("SettingsList.plist")
let fileData = try Data.init(contentsOf: settingsLocation)
This is then passed to the following function:
public func attachFileToCloudResource(resourceType: String, resourceName: String, attachment: Data) {
let fileAsString = attachment.base64EncodedString().replacingOccurrences(of: "+", with: "%2B")
var request = URLRequest(url: URL(string: FRAPPE_INSTANCE + FRAPPE_METHODS + FRAPPE_UPLOAD_ATTACHMENT)!)
var components = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)!
components.queryItems = [
URLQueryItem(name: FRAPPE_DOCTYPE, value: resourceType),
URLQueryItem(name: FRAPPE_DOCNAME, value: resourceName),
URLQueryItem(name: FRAPPE_FILENAME, value: "testFile.xml"),
URLQueryItem(name: FRAPPE_DATA, value: fileAsString),
URLQueryItem(name: FRAPPE_PRIVATE, value: "1"),
URLQueryItem(name: FRAPPE_DECODE_BASE64, value: "1")
]
let query = components.url!.query
request.httpMethod = "POST"
request.addValue("token \(API_KEY):\(API_SECRET)", forHTTPHeaderField: "Authorization")
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = Data(query!.utf8)
// Session and dataTask send request below but not relevant to example
}
After a bit of googling, I discovered I can resolve the error by appending "==" to the file string, but this feels nasty/wrong.
let fileAsString = (attachment.base64EncodedString().replacingOccurrences(of: "+", with: "%2B") + "==")
Can someone please point out where I might be going wrong and how to do this properly?
I'm not particularly happy with this answer, but its the one I used, and from the small number of xml and pdf files I have tried is working for me.
From what can work out, there is no Swift method that automatically generates the padding and it needs to be done manually.
I based my solution on the answers to this question and wrote an extension to Data to provide the padding:
extension Data {
func base64EncodedStringWithPadding() -> String {
let base64String = self.base64EncodedString()
let remainder = base64String.count % 4
if remainder == 0 {
return base64String
} else {
let paddedLength = base64String.count + 4 - remainder
return base64String.padding(toLength: paddedLength, withPad: "=", startingAt: 0)
}
}
}
Which I then used in my function like this:
let fileAsString = attachment.base64EncodedStringWithPadding()

Alamofire multiple parameters (Query and Form) Swift 4

I'm having a problem sending a POST request with Alamofire.
I need to send the usser and password fields as application/x-www-form-urlencode and also some query data in the url.
I am creating a URLRequest to handle the process, but I'm getting always a 400 response from the server, so I guess the problem must be in the way I create the request.
This is the example in Postman:
I need to send a param in the url and two more in as application/x-www-form-urlencode
Postman 1 - Parameters
Postman 2 - ContentType
I need to do this (that i have in Android)
#FormUrlEncoded
#POST(Constants.AUTH_LDAP)
Call<ResponseBody> authLdap(
#Query(value = Constants.PARAM_REQ, encoded = true) String req,
#Field(Constants.PARAM_LOGIN) String login,
#Field(Constants.PARAM_PASSWORD) String password
);
And this is what I have in swift
let queryParamters = [Constants.Params.PARAM_REQ:req]
let headers = ["Content-Type": "application/x-www-form-urlencoded"]
let fieldParameters = [
Constants.Params.PARAM_LOGIN : user,
Constants.Params.PARAM_PASSWORD : pass]
let url = URL(string: Constants.EndPoints.AUTH_LDAP)
let request = URLRequest(url: url!)
let encoding = try URLEncoding.default.encode(request, with: queryParamters as Parameters)
let encodingpa = try URLEncoding.httpBody.encode(request, with: fieldParameters as Parameters)
var urlRequest = encodingpa
urlRequest.url = encoding.url
urlRequest.allHTTPHeaderFields = headers
Alamofire.request(urlRequest).responseData(completionHandler: { response in
switch response.result {
case .success:
print("sucess")
print(response.response)
case .failure(let error):
print(error)
}
})
Thanks for your help.
Try to create url from queryParameters using URLComponents like
var urlComponents = URLComponents(string: Constants.EndPoints.AUTH_LDAP)!
urlComponents.queryItems = [
URLQueryItem(name: Constants.Params.PARAM_REQ, value: req)
]
let headers = ["Content-Type": "application/x-www-form-urlencoded"]
var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: fieldParameters)
request.allHTTPHeaderFields = headers
Alamofire.request(request).responseJSON { response in
}

Image to String using Base64 in swift 4

my php code creates an empty image on server
here is my code (swift4) :
var encoded_img = imageTobase64(image: image1.image!)
func convertImageToBase64(image: UIImage) -> String {
let imageData = UIImagePNGRepresentation(image)!
return imageData.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters)
}
php code :
$decodimg = base64_decode(_POST["encoded_img"]);
file_put_contents("images/".$imgname,$decodimg);
And the code to prepare the request:
#IBAction func BtnSend(_ sender: UIButton) {
var url = "http://xxxxxx/msg.php"
var encoded_img = imageTobase64(image: image1.image!)
let postData = NSMutableData(data: ("message=" + message).data(using: String.Encoding.utf8)!)
postData.append(("&encoded_img=" + encoded_img).data(using: String.Encoding.utf8)!)
request = NSMutableURLRequest(url: NSURL(string: url)! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 20.0)
request.httpMethod = "POST"
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with:
request as URLRequest, completionHandler:
{ (data, response, error)-> Void in
...
})
dataTask.resume()
The fundamental issue is that your x-www-form-urlencoded request is not well-formed. You have explicitly requested it to create base64 string with newline characters in it, but those are not allowed in x-www-form-urlencoded unless you percent encode them. Plus, we don't know what sort of characters are inside message.
I would suggest:
Not request newline characters to be added to the base64 string unless you really needed them; but
Percent escape the string values, anyway, as I don't know what sort of values you have for message.
Thus:
let parameters = [
"message": message,
"encoded_img": convertToBase64(image: image1.image!)
]
let session = URLSession.shared
let url = URL(string: "http://xxxxxx/msg.php")!
var request = URLRequest(url: url, timeoutInterval: 20.0)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") // not necessary, but best practice
request.setValue("application/json", forHTTPHeaderField: "Accept") // again, not necessary, but best practice; set this to whatever format you're expecting the response to be
request.httpBody = parameters.map { key, value in
let keyString = key.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
let valueString = value.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
return keyString + "=" + valueString
}.joined(separator: "&").data(using: .utf8)
let dataTask = session.dataTask(with:request) { data, response, error in
guard error == nil,
let data = data,
let httpResponse = response as? HTTPURLResponse,
(200 ..< 300) ~= httpResponse.statusCode else {
print(error ?? "Unknown error", response ?? "Unknown response")
return
}
// process `data` here
}
dataTask.resume()
where
func convertToBase64(image: UIImage) -> String {
return UIImagePNGRepresentation(image)!
.base64EncodedString()
}
and
extension CharacterSet {
/// Character set containing characters allowed in query value as outlined in RFC 3986.
///
/// RFC 3986 states that the following characters are "reserved" characters.
///
/// - General Delimiters: ":", "#", "[", "]", "#", "?", "/"
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
///
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
/// should be percent-escaped in the query string.
///
/// - parameter string: The string to be percent-escaped.
///
/// - returns: The percent-escaped string.
static var urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)
return allowed
}()
}
Alternatively, you could consider using Alamofire which gets you out of the weeds of creating well-formed x-www-form-urlencoded requests.

How to post parameter with (+ plus sign) in Alamofire

when post A+ or O+ or any (+) bloods types with + character, I receive a "not valid blood" error.
The blood value is in JSON dictionary: How do I post + character in Alamofire?
let dictionary = ["fname": "name",
"lname": "family",
"blood": "A+"]
let updateData = try! JSONEncoder().encode(dictionary)
let jsonString = String(data: updateData, encoding: .utf8)!
var components = URLComponents(string: registerUrl)!
components.queryItems = [
URLQueryItem(name: "api_key", value: apiKey),
URLQueryItem(name: "profile", value: jsonString)
]
Alamofire.request(components.url!, method: .post).responseJSON {
response in
if response.result.isSuccess {
let json: JSON = JSON(response.result.value!)
print(json)
}
}
Unfortunately, URLComponents will not percent encode + character although many (most?) web services require it to be (because, pursuant to the x-www-form-urlencoded spec, they replace + with space character). When I posted bug report on this, Apple's response was that this was by design, and that one should manually percent encode the + character:
var components = URLComponents(string: "https://www.wolframalpha.com/input/")!
components.queryItems = [
URLQueryItem(name: "i", value: "1+2")
]
components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
Obviously, if you were doing a standard application/json request, with the JSON in the body of the request, no such percent encoding is needed. But if you're going to include the JSON in the URL like this, then you will have to percent encode the + character yourself.
Alternatively, you can let Alamofire do this for you:
let parameters = [
"api_key": apiKey,
"profile": jsonString
]
Alamofire.request(url, method: .post, parameters: parameters).responseJSON { response in
...
}
This, admittedly puts the properly percent encoded value in the body of the POST request, not the URL as in your example, but generally in POST requests, that's what we want.

Alamofire Custom Parameter Encoding

I'm using Alamofire and want to encode my parameters with content type "text/html; charset=utf-8". I followed the documentation https://github.com/Alamofire/Alamofire for custom parameter encoding and created
let custom: (URLRequestConvertible, parameters: [String: AnyObject]?) -> (NSURLRequest, NSError?) = {
(URLRequest, parameters) in
let mutableURLRequest = URLRequest.URLRequest.mutableCopy() as! NSMutableURLRequest
mutableURLRequest.setValue("text/html; charset=utf-8", forHTTPHeaderField: "Content-Type")
mutableURLRequest.body = // don't know if I need to set this
return (mutableURLRequest, nil)
}
func postData(){
Alamofire.request(.POST, baseUrl, parameters: parameters, encoding: .Custom(custom))
.responseString{ (request, response, data, error) in
println("blah")
}
}
I have a problem when I try to use custom in my Alamofire request and get the error "Cannot make responseString with argument list of type ( _, _, _, _)-> _ )" However, this isn't a problem if the encoding is changed to a preset like .URL so the issue seems to be in my implementation of custom?
If it makes a difference my parameters are set here:
var parameters = [String: AnyObject]()
func setParams(){
parameters = [
"CONTRACT ID" : chosenContract!.iD.toInt()!,
"FEE AMOUNT" : 0,
"TRANSACT DATE" : today
]
}
You have a couple questions in here. Let's break them down 1x1.
Compiler Issue
Your compiler issue is due to the fact that your return tuple is the wrong type. In Alamofire 1.3.0, we changed the return type to be an NSMutableURLRequest which ends up making things much easier overall. That should fix your compiler issue.
Setting the HTTPBody
Now you have a couple options here.
Option 1 - Encode Data as JSON
let options = NSJSONWritingOptions()
let data = try NSJSONSerialization.dataWithJSONObject(parameters!, options: options)
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
mutableURLRequest.HTTPBody = data
From what you posted I'm assuming you actually want .URL encoding for the parameters.
Option 2 - Use the .URL Case
let parameters: [String: AnyObject] = [:] // fill in
let encodableURLRequest = NSURLRequest(URL: URL)
let encodedURLRequest = ParameterEncoding.URL.encode(encodableURLRequest, parameters).0
let mutableURLRequest = NSMutableURLRequest(URL: encodedURLRequest.URLString)
mutableURLRequest.HTTPMethod = "POST"
mutableURLRequest.setValue("text/html; charset=utf-8", forHTTPHeaderField: "Content-Type")
Alamofire.request(mutableURLRequest)
.response { request, response, data, error in
print(request)
print(response)
print(error)
}
Hopefully that helps get you going. Best of luck!