How to create RAW-Email-Message on AWS-SES in Swift 5 - swift

I want to create a RAW-Email inside my iOS-App.
Looking to the documentation,
I need to encode my message to MIME-Standard, but I'm not so familiar with this topic. In the documentation there is also example-code for python and java.
How can I achieve this in SWIFT?
func sendRawMail(){
let sender = "sender#mail.com"
let recipient = "recipient#mail.com"
let rawMessage = AWSSESRawMessage()
// rawMessage?.data = "I guess HERE I have to put the MIME- Data?!"
let rawRequest = AWSSESSendRawEmailRequest()
rawRequest?.destinations = [recipient]
rawRequest?.source = sender
rawRequest?.rawMessage = rawMessage
AWSSES.default().sendRawEmail(rawRequest!) { (response, error) in
if let response = response{
print(response)
}
if let error = error{
print(error)
}
}
}
Sending an empty mail with my code works so far.

After a lot of reading, I finally get it working.
You need to write a big string, based on MIME-Standards.
This big string you have to encode to BASE64.
Attachments needed also to transform from data to an BASE64-String.
I will post my code, which can send Mails with an .png image. I will test other fileTypes as well, but it have to be the same principle.
First I created a enum for my fileTypes.
enum DataTypes{
case png
case jpg
case pdf
}
Now I created a function to get the string-value for the specific DataType
func getMIMEDataType(dataType:DataTypes) -> String{
var MIMEData = String()
switch dataType {
case .png:
MIMEData = "image/png"
case .jpg:
MIMEData = "image/jpg"
case .pdf:
MIMEData = "application/pdf"
}
return MIMEData
}
Finally the function to send the raw-mail. There are already variables in the message-string so you can use this function flexible.
func sendRawMail(sender:String,reciepients:[String],subject:String,message:String,attachment:Data?,dataType:DataTypes?,attachmentName:String?,completion: #escaping (_ messageCode:String?,_ error:Error?) -> ()){
let attachmentString = attachment!.base64EncodedString(options: .lineLength64Characters)
let MIMEDataType = getMIMEDataType(dataType: dataType!)
let message:String = """
Subject: \(subject)
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="XXXXboundary text"
This is a multipart message in MIME format.
--XXXXboundary text
Content-Type: text/plain
\(message)
--XXXXboundary text
Content-Type: \(MIMEDataType);
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="\(attachmentName!).\(MIMEDataType.components(separatedBy: "/")[1])"
\(attachmentString)
--XXXXboundary text--
"""
let data = message.data(using: .utf8)
let rawMessage = AWSSESRawMessage()
rawMessage?.data = data
let rawRequest = AWSSESSendRawEmailRequest()
rawRequest?.destinations = reciepients
rawRequest?.source = sender
rawRequest?.rawMessage = rawMessage
AWSSES.default().sendRawEmail(rawRequest!) { (response, error) in
if let response = response{
completion(response.messageId,nil)
}
if let error = error{
completion(nil,error)
}
}
}
You can use it like this and can handle the result in the completionHandler.
sendRawMail(sender: "sender.mail#mail.com", reciepients: ["recipient.mail#mail.com"], subject: "This is a test", message: "TestMessage", attachment: attachment.data, dataType: .png, attachmentName: "testpicture") { (messageID, error) in
if let messageID = messageID{
print(messageID)
}
if let error = error{
print(error)
}
I hope it will help somebody in future :)

Related

How to convert the email body to something readable?

Iam working on a simple Apple mailkit extension but cant get something readable out of my mails.
func allowMessageSendForSession(_ session: MEComposeSession, completion: #escaping (Error?) -> Void) {
let mailMessage = session.mailMessage;
let subject = mailMessage.subject
let sender = mailMessage.fromAddress.addressString ?? "undefined";
let data = String(data: mailMessage.rawData!, encoding: .utf8)
In data is the header and the mail body. But its filled with so many 'quoted-printable' strings.
Something like this Viele Gr=C3=BC=C3=\n=9Fe =F0=9F=A4=9D. It should be Viele Grüße 🀝.
I already tried the code in this answer https://stackoverflow.com/a/32827598/1407823 but it seems to only work with single words. I cannot get it to work with a whole text.
Is there no built in way to parse text like this?
There is no built-in way to decode the message, you need a RFC822 parser for example MimeParser on GitHub, available as Swift Package.
This is an example how to decode the body as plain text, messageData represents the raw data of the message
import MimeParser
do {
let messageString = String(data: messageData, encoding: .utf8)!
let parser = MimeParser()
let mime = try parser.parse(messageString)
switch mime.content {
case .body(let body): print(body.raw)
case .alternative(let mimes), .mixed(let mimes):
if let plainTextMime = mimes.first(where: {$0.header.contentType?.subtype == "plain"}),
let decodedBody = try plainTextMime.decodedContentString() {
print(decodedBody)
}
}
} catch {
print(error)
}
With subtype == "html" you get the HTML text, if available

(Swift) upload Image to backend not working and Worked in (Postman) & (Admin Page)

Trying to Upload Image From Swift APP to backend
tested on Admin Page working fine I can upload image.
tested on Postman and its working fine I can upload image.
when I try to upload from swift I got invalid_image
I am Taking UIImage and converted using pngData and send it in URLSession:
param["src"] contains UIImage
var request = URLRequest(url: URL(string: ServerURL)!,timeoutInterval: Double.infinity)
let boundary = "Boundary-\(UUID().uuidString)"
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpMethod = "PUT"
var body = ""
for param in parameters {
if param["disabled"] == nil {
let paramName = param["key"]!
body += "--\(boundary)\r\n"
body += "Content-Disposition: form-data; name=\"\(paramName)\""
if param["contentType"] != nil {
body += "\r\nContent-Type: \(param["contentType"] as! String)"
}
let paramType = param["type"] as! String
if paramType == "text" {
let paramValue = param["value"] as! String
body += "\r\n\r\n\(paramValue)\r\n"
} else {
let paramSrc = param["src"] as! UIImage
let fileData = (String(data: fileData, encoding: .utf8) ?? "Converting to UTF8 Fail") as String
body += "; filename=\"myImg.png\"\r\n"
body += "Content-Type: image/png\r\n\r\n\(fileData)\r\n"
Checked paramSrc and its content the image but fileData got nil
I have try :
let fileData = paramSrc.pngData()! as NSData
and fileData was contain the data not nil but still getting
ErrorDetail(string='Upload a valid image. The file you uploaded was either not an image or a corrupted image.', code='invalid_image')
SOLVE it ( send it as Data ) :
thanks for #pastre for helping find where is the problem happen
I had similar problem while uploading image to REST Framework API and fixed it by :
views.py
from rest_framework.parsers import FormParser, MultiPartParser
class ClassName(APIView):
parser_classes = (MultiPartParser, FormParser, )
def put(self, request, format=None):
instance = request.user
serializer = ClassNameSerializer(instance, data=request.data)
if not serializer.is_valid(raise_exception=True):
return Response({
"error": serializer.errors
})
serializer.save()
return Response({
"detail": "Model object updated",
})
serializers.py
class ClassNameSerializer(serializers.ModelSerializer):
class Meta:
model = ClassName
fields = ['profile_image', 'full_name', 'address', 'email', 'address']
def update(self, instance, validated_data):
if 'profile_image' in validated_data.keys():
instance.profile_image = validated_data['profile_image']
if 'full_name' in validated_data.keys():
instance.full_name = validated_data['full_name']
if 'email' in validated_data.keys():
instance.email = validated_data['email'].lower()
if 'address' in validated_data.keys():
instance.address = validated_data['address']
instance.save()
return instance
so as you can see I had not done much file handling in serializers, so in your case add these and try again:
views.py
from rest_framework.parsers import FormParser, MultiPartParser
class PUser(APIView):
parser_classes = (MultiPartParser, FormParser, )
def patch(self, request, format=None):
Please try and see if it works for you.
if any one got on same problem this is how I solve it
I just change the body from string to data
var body = Data()
and then
if let fileData = paramSrc.pngData() {
body.append("; filename=\"blah.png\"\r\n".data(using: .utf8)!)
body.append("Content-Type: \"content-type header\"\r\n\r\n".data(using: .utf8)!)
body.append(fileData)
}

swift euc-kr korean encoding not working. But works in python

I am writing some code to parse korean text from server encoded with euc-kr korean encoder.
When I just do the same encoding in Python, it works as expected.
But when I do it as following, encoding doesn't work. The result is unreadable.
In Python :
string = u'μ•ˆλ…•ν•˜μ„Έμš”.'.encode('eucKR')
In Swift :
let encoding:UInt = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(
CFStringEncodings.EUC_KR.rawValue))
let encodedData = "μ•ˆλ…•ν•˜μ„Έμš”.".data(using: String.Encoding(rawValue: encoding))!
What the difference between those 2 encodings ?
Following are full source codes for both python and swift. I still stuck on the encoding part. Is the problem related to alamofire post request?
Python:
import requests
from pattern import web
string = u'μ €λŠ” 내일 λ°”λΉ μ„œ 학ꡐ에 λͺ»κ°‘λ‹ˆλ‹€.'.encode('eucKR')
r = requests.post("http://nlp.korea.ac.kr/~demo/dglee/komatag.php", data={'formradio1': '', 'formradio2': 'ems', 'textarea': string})
dom = web.Element(r.text)
main = dom('tr')
for item in main:
result = web.plaintext(item.source)
a = result.encode('ISO-8859-1')
t=a.decode('eucKR')
print(t)
Swift:
override func viewDidLoad() {
let string: NSString = NSString(string: "μ•ˆλ…•ν•˜μ„Έμš”")
let encodedEucKr = stringToEuckrString(stringValue: string as String)
print(encodedEucKr)
Alamofire.request("http://nlp.korea.ac.kr/~demo/dglee/komatag.php", method: .post, parameters: ["formradio1":"", "formradio2":"ems", "textarea": encodedEucKr], headers: nil).responseString { response in
switch(response.result) {
case .success(_):
if let data = response.result.value{
print(response.result.value)
}
break
case .failure(_):
print(response.result.error)
break
}
}
}
func stringToEuckrString(stringValue: String) -> String {
let encoding:UInt = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(
CFStringEncodings.EUC_KR.rawValue))
let encodedData = stringValue.data(using: String.Encoding(rawValue: encoding))!
let attributedString = try? NSAttributedString(data: encodedData, options:[:], documentAttributes: nil)
if let _ = attributedString {
return attributedString!.string
} else {
return ""
}
}
It was not easy for two reasons...
Sending form data in EUC-KR is not considered to be standard-compliant in modern web technologies and standards.
The response sent from your server is sort of broken, in that Swift cannot decode the result as a valid EUC-KR text.
(This seems to be a bug of your server side code.)
Anyway, when you need to send a web form based request to your server in EUC-KR:
Create a EUC-KR byte sequence from the original
Percent-escape it. You may need to do it by yourself
Put entire request in an HTTP request body
Add proper MIME type header
Some details depend on the server. I have never used Alamofire, so I do not know if Alamofire supports such things.
Here I show you an example using a normal URLSession:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
sendRequest(string: "μ•ˆλ…•ν•˜μ„Έμš”")
}
func sendRequest(string: String) {
let rawEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.EUC_KR.rawValue))
let encoding = String.Encoding(rawValue: rawEncoding)
let url = URL(string: "http://nlp.korea.ac.kr/~demo/dglee/komatag.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
//Create an EUC-KR byte sequece
let eucKRStringData = string.data(using: encoding) ?? Data()
//Percent-escape, you need to do it by yourself
//(Though, most servers accept non-escaped binary data with its own rules...)
let eucKRStringPercentEscaped = eucKRStringData.map {byte->String in
if byte >= UInt8(ascii: "A") && byte <= UInt8(ascii: "Z")
|| byte >= UInt8(ascii: "a") && byte <= UInt8(ascii: "z")
|| byte >= UInt8(ascii: "0") && byte <= UInt8(ascii: "9")
|| byte == UInt8(ascii: "_") || byte == UInt8(ascii: ".") || byte == UInt8(ascii: "-")
{
return String(Character(UnicodeScalar(UInt32(byte))!))
} else if byte == UInt8(ascii: " ") {
return "+"
} else {
return String(format: "%%%02X", byte)
}
}.joined()
//In application/x-www-form-urlencoded format, you send data in a URL-query like format.
let paramString = "formradio1=&formradio2=ems&textarea=\(eucKRStringPercentEscaped)"
//As all non-ASCII characters are percent-escaped, .isoLatin1 works well here.
let bodyData = paramString.data(using: .isoLatin1)!
//Form data needs to be sent as a body of HTTP protocol.
request.httpBody = bodyData
//MIME type for usual form data is "application/x-www-form-urlencoded".
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
//URLRequest is ready and you can start dataTask here.
let task = URLSession.shared.dataTask(with: request) {data, response, error in
if let error = error {
print("Error:", error)
}
if let response = response {
print("Response:", response)
}
//The response may not be valid EUC-KR; you need to decode it while accepting invalid bytes.
if let data = data {
var result = ""
var i = 0
while i < data.count{
let ch = data[i]
if ch < 0x80 {
result += String(Character(UnicodeScalar(UInt32(ch))!))
} else if
i + 2 <= data.count,
let ch2 = String(data: data.subdata(in: i..<i+2), encoding: encoding)
{
result += ch2
i += 1
} else {
result += "?"
}
i += 1
}
print("Result:", result)
}
}
//Do not forget to resume the created task.
task.resume()
//And remember you should not do anything after you invoke an async task.
}
If your server side can handle UTF-8 requests and responses properly, the code above can be far more simple. Using EUC-KR in web services is sort of outdated. You'd better adopt UTF-8 soon.

(Swift) How to check if content-encoding is gzip

Is there any way in Swift to find out if data from a request returns in gzip?
I want to write a test method to check if Content-Encoding returns gzip file:
You can get this info from the header fields in the HTTPURLResponse:
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let response = response as? HTTPURLResponse {
if let encoding = response.allHeaderFields["Content-Encoding"] as? String {
print(encoding)
print(encoding == "gzip")
}
}
}.resume()
Note that this downloads the headers and the data.
If you want to only get the headers without downloading the data, a better solution is to use an URLRequest set to "HEAD" like this:
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
URLSession.shared.dataTask(with: request) { (_, response, _) in
if let response = response as? HTTPURLResponse {
if let enc = response.allHeaderFields["Content-Encoding"] as? String {
print(enc)
print(enc == "gzip")
}
}
}.resume()
This way, only the headers are downloaded.
If you read the first 4 bytes of the file, you should get a Magic Number
So you should be able to do something like:
var magicNumber = [UInt](count: 4, repeatedValue: 0)
data.getBytes(&magicNumber, length: 4 * sizeof(UInt))
If it's GZipped, it will have a Magic Number of 1f 8b, so check for that.

Spaces between words are getting lost after I send post method [duplicate]

I am learning Swift and I don't know how to send parameters to server using Swift.
In Objective-C we can do this by using "%#" as the placeholder.
But what should be done in case of Swift, suppose I have a login webservice which requires email and password.
Now I want to know is that how will i send the logintextfield and passwordtextfield text to the server, such as,
var bodyData = "email=logintextfield.text&password=passwordtextfield.text"
When creating a HTTP request that includes user input, one should generally percent escape it in case there are any reserved characters in the user's input, thus:
let login = logintextfield.text?.addingPercentEncodingForURLQueryValue() ?? ""
let password = passwordtextfield.text?.addingPercentEncodingForURLQueryValue() ?? ""
let bodyData = "email=\(login)&password=\(password)"
Note, you'd really want to check to see if login and password were nil or not. Anyway, the percent-escaping is done as follows:
extension String {
/// Percent escapes values to be added to a URL query as specified in RFC 3986
///
/// This percent-escapes all characters besides the alphanumeric character set and "-", ".", "_", and "~".
///
/// http://www.ietf.org/rfc/rfc3986.txt
///
/// :returns: Returns percent-escaped string.
func addingPercentEncodingForURLQueryValue() -> String? {
let allowedCharacters = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
return self.addingPercentEncoding(withAllowedCharacters: allowedCharacters)
}
}
See this answer for another rendition of this extension.
If you wanted to see a demonstration of the use of the above, imagine the following request:
let keyData = "AIzaSyCRLa4LQZWNQBcjCYcIVYA45i9i8zfClqc"
let sensorInformation = false
let types = "building"
let radius = 1000000
let locationCoordinate = CLLocationCoordinate2D(latitude:40.748716, longitude: -73.985643)
let name = "Empire State Building, New York, NY"
let floors = 102
let now = Date()
let params:[String: Any] = [
"key" : keyData,
"sensor" : sensorInformation,
"typesData" : types,
"radius" : radius,
"location" : locationCoordinate,
"name" : name,
"floors" : floors,
"when" : now,
"pi" : M_PI]
let url = URL(string: "http://some.web.site.com/inquiry")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = params.dataFromHttpParameters()
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard data != nil && error == nil else {
print("error submitting request: \(error)")
return
}
if let httpResponse = response as? HTTPURLResponse where httpResponse.statusCode != 200 {
print("response was not 200: \(response)")
return
}
// handle the data of the successful response here
}
task.resume()
I'm including lots of parameters that were not included in your example, but simply as a way of illustrating the routine's handling of a diverse array of parameter types.
Incidentally, the above uses my datafromHttpParameters function:
extension Dictionary {
/// This creates a String representation of the supplied value.
///
/// This converts NSDate objects to a RFC3339 formatted string, booleans to "true" or "false",
/// and otherwise returns the default string representation.
///
/// - parameter value: The value to be converted to a string
///
/// - returns: String representation
private func httpStringRepresentation(_ value: Any) -> String {
switch value {
case let date as Date:
return date.rfc3339String()
case let coordinate as CLLocationCoordinate2D:
return "\(coordinate.latitude),\(coordinate.longitude)"
case let boolean as Bool:
return boolean ? "true" : "false"
default:
return "\(value)"
}
}
/// Build `Data` representation of HTTP parameter dictionary of keys and objects
///
/// This percent escapes in compliance with RFC 3986
///
/// http://www.ietf.org/rfc/rfc3986.txt
///
/// :returns: String representation in the form of key1=value1&key2=value2 where the keys and values are percent escaped
func dataFromHttpParameters() -> Data {
let parameterArray = self.map { (key, value) -> String in
let percentEscapedKey = (key as! String).addingPercentEncodingForURLQueryValue()!
let percentEscapedValue = httpStringRepresentation(value).addingPercentEncodingForURLQueryValue()!
return "\(percentEscapedKey)=\(percentEscapedValue)"
}
return parameterArray.joined(separator: "&").data(using: .utf8)!
}
}
Here, because I'm dealing with an array of parameter strings, I use the join function to concatenate them separated by &, but the idea the same.
Feel free to customize that function to handle whatever data types you may be passing into it (e.g. I don't generally have CLLocationCoordinate2D in there, but your example included one, so I wanted to show what it might look like). But the key is that if you're supplying any fields that include user input, make sure to percent-escape it.
FYI, this is my rfc3339String function which is used above. (Clearly, if you don't need to transmit dates, you don't need this, but I'm including it for the sake of completeness for a more generalized solution.)
extension Date {
/// Get RFC 3339/ISO 8601 string representation of the date.
///
/// For more information, see:
///
/// https://developer.apple.com/library/ios/qa/qa1480/_index.html
///
/// - returns: Return RFC 3339 representation of date string
func rfc3339String() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter.string(from: self)
}
}
To see Swift 2 rendition, see previous rendition of this answer.
It can be done by passing the required parameter in the service like this,
var urlPath = NSString(format: "https://maps.googleapis.com/maps/api/place/search/json?key=AIzaSyCRLa4LQZWNQBcjCYcIVYA45i9i8zfClqc&sensor=false&types=restaurant&radius=100000&location=\(locationCoord)")
Here urlPath is the url containing web service and locationCoord (as last parameter) is the run time value for the location parameter for the web service. The parameter key, sensor, radius and types are fixed.
I am calling the json on login button click
#IBAction func loginClicked(sender : AnyObject){
var request = NSMutableURLRequest(URL: NSURL(string: kLoginURL)) // Here, kLogin contains the Login API.
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var err: NSError?
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(self.criteriaDic(), options: nil, error: &err) // This Line fills the web service with required parameters.
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
// println("Response: \(response)")
var strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)")
var err1: NSError?
var json2 = NSJSONSerialization.JSONObjectWithData(strData.dataUsingEncoding(NSUTF8StringEncoding), options: .MutableLeaves, error:&err1 ) as NSDictionary
println("json2 :\(json2)")
if(err) {
println(err!.localizedDescription)
}
else {
var success = json2["success"] as? Int
println("Succes: \(success)")
}
})
task.resume()
}
Here, I have made a seperate dictionary for the parameters.
var params = ["format":"json", "MobileType":"IOS","MIN":"f8d16d98ad12acdbbe1de647414495ec","UserName":emailTxtField.text,"PWD":passwordTxtField.text,"SigninVia":"SH"]as NSDictionary
return params
}
Based on the above I ended up with this, to get a token within a Set-Cookie element.
Where the URLResponse was
<NSHTTPURLResponse: 0x17403ef20> { URL: http://bla.co.uk//auth/authenticate?email=bob#isp.eu&password=xcode } { status code: 200, headers {
"Cache-Control" = "private, must-revalidate";
Connection = "keep-alive";
"Content-Type" = "application/json";
Date = "Fri, 17 Feb 2017 10:51:41 GMT";
Expires = "-1";
Pragma = "no-cache";
Server = nginx;
"Set-Cookie" = "token=Cu4CmOaverylongstring0mCu4CmOpBGg; expires=Fri, 17-Feb-2017 20:51:41 GMT; Max-Age=36000; path=auth; httponly";
"Transfer-Encoding" = Identity;
"X-Powered-By" = "PHP/5.5.9-1ubuntu4.19, PleskLin";
} }
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
let httpResponse = response as! HTTPURLResponse
let statusCode = httpResponse.statusCode
if statusCode == 200 {
let keyValues = httpResponse.allHeaderFields.map { (String(describing: $0.key).lowercased(), String(describing: $0.value)) }
// Now filter the array, searching for your header-key, also lowercased
if let myHeaderValue = keyValues.filter({ $0.0 == "Set-Cookie".lowercased() }).first {
print(myHeaderValue.1)
let cookies = myHeaderValue.1
let cookieDict = cookies.components(separatedBy: ";")
print("\(cookieDict)")
let tokenEntryParameter = cookieDict.filter({$0 .contains("token")})
let tokenEntry = tokenEntryParameter.first
token = (tokenEntry?.components(separatedBy: "=").last)!
}
}
}