Posting a file in Swift - swift

I am trying to post a file to a web API from Swift. The request I send is received fine, but it doesn't have any file in it. I was able to peek at request from the C# controller and HttpContext.Current.Request.Files.Count is 0. I suspect that instead of attaching a file to the body of the request, it just puts in the file's contents as raw data.
Code creating request:
func upload(_ fileName:URL)
{
let requestUrl:URL = URL(string: uploadURL)!
let request = NSMutableURLRequest(url: requestUrl)
request.httpMethod = "POST"
request.httpBodyStream = InputStream(url: fileName)
let (data, response, error) = URLSession.shared.synchronousDataTaskWithRequest(request as URLRequest)
}
What am I doing wrong?
If it is helpful to understand what kind of request I am trying to build, this is the working code in JS which creates a FormData object from an HTML <input type='file'> element:
$.ajax({
url: $('#apiURL').val() + 'Upload'
type: 'POST',
contentType: false,
processData: false,
data: new FormData($('form')[0]),
success: function (data)
{
// doing successful things
},
error: function (jqXHR, textStatus, errorThrown)
{
alert("Failed");
}
});
I basically just want to send a file to the same controller it references but in swift...

My google-fu wasn't up to par. Copy-paste of the answer given with minor code changes to work with the data types I already had made it work:
https://stackoverflow.com/a/26163136/6825722

Related

Internal Server Error - 500. Object reference not set to an instance of an object. step 0

I am making a POST API call from my swiftUI code. I have made sure my API call code is working in SwiftUI and I tested that with other urls. The issue is the same API call works from my NativeScript Angular code but it doesn't work from my swiftUI code and throws this following error.
Internal Server Error - 500. Object reference not set to an instance of an object. step 0
This is how I am making a request in NativeScript Angular which works fine.
let url = "some_url/signIn?email=${userName}&password=${password}"
const httpOptions = {
headers: new HttpHeaders({ "Content-Type": "application/json" }),
};
this._http.post<User>(url, httpOptions).subscribe((results: any) => { /* data processing */ } )
Where this._http is injected in the constructor.
Following is how I am making the API call in swift which throws an error.
guard let url = URL(string: "some_url/signIn?email=\(userName)&password=\(password)") else {fatalError("Missing URL")}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard(response as? HTTPURLResponse)?.statusCode == 200 else {fatalError("Error while fetching data")}
This last line is where the error occurs.
I have never seen this error before and can't find any good results.
Any kind of information would be helpful.
Thank you in advance :)
I figured out the issue,
I just became aware that I was passing headers in my NativeScript Angular code, but not in my SwiftUI code.
I just simply added this line and the API call on SwiftUI works fine now.
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

Uploading a PDF File From Swift Yields a Blank PDF

Im trying to upload a PDF generated from a UIImage. I take a picture using my camera framework, uploads all ok but the PDF on S3 is blank. Below is the code I am using:
I use the following class to create the PDF from a UIImage, tested ok.
// Using PDFKit
func generatePDF(source: UIImage) -> PDFDocument {
let pdfDocument = PDFDocument()
let pdfPage = PDFPage(image: source)
pdfDocument.insert(pdfPage!, at: 0)
return pdfDocument
}
I used Alamofire to perform a multipart upload with the pdf, tested ok.
class NetworkManager {
static let shared = NetworkManager()
func upload(document: Data, name: String) {
let filename = "\(name).pdf"
let urlString = endpoint + "?filename=" + filename
let url = URL(string: urlString)!
var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 30)
request.method = .post
request.setValue(key, forHTTPHeaderField: api)
AF.upload(multipartFormData: { multiPart in
multiPart.append(document, withName: name, fileName: filename, mimeType: "application/pdf")
}, with: request)
.uploadProgress(queue: .main, closure: { progress in
print("Upload Progress: \(progress.fractionCompleted)")
})
.responseJSON(completionHandler: { data in
print("data: \(data)")
})
}
}
On AWS I am using the following framework to reconstruct the multipart data back to a PDF
lambda-multipart-parser
https://www.npmjs.com/package/lambda-multipart-parser
How Im using it in Lambda:
let filename = event.queryStringParameters.filename;
let documentData = await imageParser.parse(event);
let document = documentData.files[0];
var data = {
Bucket: 'order-scanned-copy',
Key: filename,
Body: document.content,
ContentType: "application/pdf",
};
let uploadFile = await s3.putObject(data).promise();
No errors, no warnings just a blank PDF. I can tell by the size around 500kb-1mb that it has something in it. Any help would be appreciated.
Thanks.
I figured it out, since I am using AWS specifically API Gateway and Lambda I needed to add the "Binary Media Type" to API Gateway. To do this I opened Api Gateway console and choose my API. Then, selected Settings and added the following types.
I did a redeploy of my api and everything worked perfect, so in the end it was not a code problem but configuration mistake on AWS.
This blog post was very helpful:
AWS SAM Configuration for API Gateway Binary Response / Payloads
Thanks for your help everyone.

URLSession Response doesn't contain headers from last redirect

I have an URL that I, when called in a webbrowser, will redirect me 2 times and in the response header of the second redirect it will send the Information that I want to extract.
So to automatically extract that information in swift, I wrote this short piece of code that makes the HTTP Request and then prints the response headers:
printv(text: "Loading JSID Location")
req = URLRequest.init(url: JSIDLocation!)
var task : URLSessionDataTask
task = URLSession.shared.dataTask(with: req) {(data, response, error) in
if let res = response as? HTTPURLResponse {
res.allHeaderFields.forEach { (arg0) in
let (key, value) = arg0
self.printv(text: "\(key): \(value)")
}
}
self.printv(text: String.init(data: data!, encoding: String.Encoding.utf8)!)
}
task.resume()
(printv is a function that will format the string and print it to a label)
So when I run this, I expect it to print the response headers and the body of the last redirect, but what actually happens is that i just prints response headers and body of the original URL. As those don't contain the information im looking for, that won't help me. I already googled my problem, and I found out that HTTP Redirects by default are activated in URLSessions and that you'd had to mess with URLSessionDelegates in order to deactivate them but that's definetly not something I did.
Thank you for your help!
If you want redirect information, you need to become the URLSessionDataTaskDelegate.
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
Then you need to implement, the redirection delegate function and be sure to call the completion handler with the given new redirect request:
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) {
// operate on response to learn about the headers here
completionHandler(request)
}

Loading PDF Binary retreived from injected AJAX GET in WKWebView to PDFKit

I'm trying to intercept an ajax delivered pdf file from an authentication-secured website. Since ajax delivers the file, the URL does not contain a ".pdf" link for downloading but rather delivers the PDF file as content-disposition attachment.
As of now, when the decidePolicyFor navigationResponse delegate method detects an "application/pdf" mimetype, I call an evaluateJavaScript method with the following code:
func readUrlContent(url: NSURL, completionHandler: #escaping (_ result: String) -> Void) {
self.evaluateJavaScript("(function() { var result = ''; $.ajax({type: 'GET', url: '\(url)', contentType: 'application/pdf; charset=utf-8', success: function(r) {result = r}, failure: function() {result = null}, async: false }); return result })()", completionHandler: { (response, error) -> Void in
let result = response as! String
completionHandler(result)
})
}
With this injected code (which I use because I had issues with cookies and authentication on the website using WKWebView), I can successfully print the PDF Binary string returned as "result" in the completion handler to the log. The issue now is that I need to get this pdf binary data into a PDF document using PDFKit.
When I take this binary string and do:
self.data = result.data(using: .utf8)
self.performSegue(withIdentifier: "pdfSegue", sender: nil)
And pass the binary PDF string to a PDFKit ViewController to display it, all pages appear blank and I get the following errors:
"encountered unexpected object type: 7."
"missing or invalid object number."
"missing or invalid cross-reference stream."
"invalid stream length 3555; should be 6444."
"FlateDecode: decoding error: incorrect header check."
etc. etc. etc.
How/can I get this PDF binary data returned and view it using PDFKit?

Pass an uploaded image directly from json file

I am trying to upload an image from my app to my heroku backend and then pass that to Stripe for verification. Here is my swift code to upload and pass the image:
#IBAction func registerAccountButtonWasPressed(sender: UIButton) {
let dob = self.dobTextField.text!.components(separatedBy: "/")
let URL = "https://splitterstripeservertest.herokuapp.com/account/create"
var imageData = UIImageJPEGRepresentation(photoID,1)
let params = ["year": UInt(dob[2])! as UInt] as [String : Any]
let manager = AFHTTPSessionManager()
let operation = manager.post(URL, parameters: params, constructingBodyWith: { (formData: AFMultipartFormData!) -> Void in
formData.appendPart(withFileData: imageData!, name: "file", fileName: "photoID.jpg", mimeType: "image/jpeg")
}, success: { (operation, responseObject) -> Void in
print(responseObject!)
}) { (operation, error) -> Void in
self.handleError(error as NSError)
}
}
I've deleted the list of params above out and left one for readability.
Is there a way to then receive this file and upload it to stripe without having to save it by passing the file parameter? like so:
Stripe::FileUpload.create(
{
:purpose => 'identity_document',
:file => params[file]
},
{:stripe_account => params[stripe_account]}
)
Also in the stripe docs it says to upload the file to 'https://uploads.stripe.com/v1/files' but then shows code to put in your backend, does Stripe::FileUpload.create do the uploading to stripe for me?
Any insight on either would be great thanks.
You need to first upload the file to Stripe's API using the "create file upload" call. You can then use the file upload's ID (file_...) to update the account's legal_entity.verification.document attribute.
(This process is explained here:
https://stripe.com/docs/connect/identity-verification#uploading-a-file
https://stripe.com/docs/connect/identity-verification#attaching-the-file)
Since the file is provided by the user, you have two choices for creating the file upload:
have your app upload the file to your backend server, then on your backend, use the file to create the file upload
create the file upload directly from the app (using your publishable API key), and send the resulting file_upload's ID (file_...) to your backend
Here's an example for creating file uploads client-side, using jQuery: https://jsfiddle.net/captainapollo/d8yd3761/.
You could do the same thing from your iOS app's code. Basically all you need to do is send a POST request to https://uploads.stripe.com/v1/files with an Authorization header with value Bearer pk_... (where pk_... is your publishable API key) and type multipart/form-data, with the file's contents as the request's body. This blog post should be helpful for sending multipart/form-data requests using Swift: http://www.kaleidosblog.com/how-to-upload-images-using-swift-2-send-multipart-post-request
Thanks to #Ywain I was able to come up with this solution for IOS Swift 4. I created the file upload directly from the app and retrieved the file_upload's ID to send to my backend to attach to the Connect Account. I did this by importing Alamofire and SwiftyJSON.
let heads = ["Authorization": "Bearer \(stripePublishableKey)"]
let imageData = image!.jpegData(compressionQuality: 1.0)
let fileName = "\(NSUUID().uuidString).jpeg"
Alamofire.upload(multipartFormData: { multipart in
multipart.append(imageData!, withName: "file", fileName: fileName, mimeType: "image/jpeg")
multipart.append(("identity_document").data(using: .utf8)!, withName :"purpose")
}, to: fileUrl!, method: .post, headers: heads) { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.responseJSON { answer in
let value = JSON(answer.value!)
let FileID = value["id"].stringValue
// Use fileID and Connect Account ID to create params to send to backend.
let params: [String: Any] = [
"acct_id": ConnectID!,
"fileID": FileID,
]
print("statusCode: \(String(describing: answer.response?.statusCode))")
}
case .failure(let encodingError):
print("multipart upload encodingError: \(encodingError)")
}
}