Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to block Alamofire from encoding of URL parameters

I have an url like that

https://www.mysite/v1/search?N=4249847587+1798040122

I use Alamofire like that

Almofire.request(.GET, "https://www.mysite/v1/search", parameters: ["N","4249847587+1798040122"], encoding: .URL)

Logging the request, I receive

https://www.mysite/v1/search?N=4249847587%2B1798040122

i.e

"%2B" instead "+"

But, I need to remain with

"+"

How can I avoid that encoding using Alamofire?

like image 759
Michael Katkov Avatar asked Feb 02 '17 18:02

Michael Katkov


2 Answers

Generally @rmaddy is right in his comment. But we can turn this encoding off as some fun exercise.

We will need to use custom encoder for this. Alamofire supports any custom encoder which implements ParameterEncoding protocol instead of encoding: .URL.

So ve can use a bit of copy&pasted code from original Alamofire codebase and create custom encoder

public struct NOURLEncoding: ParameterEncoding {

    //protocol implementation
    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
        var urlRequest = try urlRequest.asURLRequest()

        guard let parameters = parameters else { return urlRequest }

        if HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET") != nil {
            guard let url = urlRequest.url else {
                throw AFError.parameterEncodingFailed(reason: .missingURL)
            }

            if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
                let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
                urlComponents.percentEncodedQuery = percentEncodedQuery
                urlRequest.url = urlComponents.url
            }
        }

        return urlRequest
    }

    //append query parameters 
    private func query(_ parameters: [String: Any]) -> String {
        var components: [(String, String)] = []

        for key in parameters.keys.sorted(by: <) {
            let value = parameters[key]!
            components += queryComponents(fromKey: key, value: value)
        }

        return components.map { "\($0)=\($1)" }.joined(separator: "&")
    }

    //Alamofire logic for query components handling
    public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
        var components: [(String, String)] = []

        if let dictionary = value as? [String: Any] {
            for (nestedKey, value) in dictionary {
                components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
            }
        } else if let array = value as? [Any] {
            for value in array {
                components += queryComponents(fromKey: "\(key)[]", value: value)
            }
        } else if let value = value as? NSNumber {
            if value.isBool {
                components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
            } else {
                components.append((escape(key), escape("\(value)")))
            }
        } else if let bool = value as? Bool {
            components.append((escape(key), escape((bool ? "1" : "0"))))
        } else {
            components.append((escape(key), escape("\(value)")))
        }

        return components
    }

    //escaping function where we can select symbols which we want to escape 
    //(I just removed + for example)
    public func escape(_ string: String) -> String {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*,;="

        var allowedCharacterSet = CharacterSet.urlQueryAllowed
        allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

        var escaped = ""

        escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string

        return escaped
    }

} 

extension NSNumber {
    fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }
}

I don't have idea why it can be useful. But custom encoding can be added easily. Now you just can use Alamofire request with our new encoder

Alamofire.request("http://www.mysite/v1/search", method: .get, parameters: ["N": "4249847587+1798040122"], encoding: NOURLEncoding())
like image 164
oroom Avatar answered Oct 31 '22 21:10

oroom


Though the question has been answered just for those who are New to swift and probably using Alamofire for the first time and facing issues.

Here,

baseUrl = "https://www.baseUrl.com/",

path = "user/anything/status?current=Single" This is what I did (Swift 3.0 , Alamofire v4.4 )

let completeUrl = baseUrl.appending(path)
let urlWithoutPercent = completeUrl.removingPercentEncoding
let finalUrl = URL(string: urlWithoutPercent!)
var URLRequest = Foundation.URLRequest(url: finalUrl!)

or

 let wholeURL = baseUrl.appending(path)
 let urlwithPercent = wholeURL.addingPercentEncoding( withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
 var URLRequest = Foundation.URLRequest(url: URL(string: urlwithPercent!)!)

Now the Story is :

My url was getting Converted to something like this :

https://www.baseUrl.com/user/anything/status%3Fcurrent=Single

the response was coming as 403. after searching online for around 4 hours I couldn't find a simple and small fix. Then by removing percent encoding solved the problem. Also, this isn't the right approach as said in the comments by @rmaddy.

And this applies to all the special character encoding irregardless of whether it is a question mark, exclamation mark or anything..

Hope it helps someone.

like image 43
Yash Bedi Avatar answered Oct 31 '22 21:10

Yash Bedi