Upload file to Box.com via RESTful API using NSURLSession / Swift 2.0 - swift

I would like to upload a log file from our iOS app directly to a folder on Box.com. Reading the api for Box this functionality is provided with REST webservices. The examples in the api are cURL which work out of the box (sorry, no pun intended) from the terminal.
Our app is Swift based, and I am using NSURLSession to make the request. After too many attempts to count, and close examination of the limited blogs on this subject (iOS / Box) I still have not managed to upload a test file.
One frustrating thing is that the api appears to be wrong, as seen here, but following the format suggested in this answer did not help.
Here is the NSURLSession request code from my latest attempt:
let path = NSBundle.mainBundle().pathForResource("testFile", ofType: "txt")
let data: NSData? = NSData(contentsOfFile: path!)
let url = NSURL(string: "https://upload.box.com/api/2.0/files/content")!
let request = NSMutableURLRequest(URL: url)
let boundary = generateBoundaryString()
request.HTTPMethod = "POST"
request.addValue("Bearer q1ckXDSTC0EcsCJiLtW638o5x5roWabb", forHTTPHeaderField: "Authorization")
let body = NSMutableData()
body.appendData("Content-Type: multipart/form-data; boundary=\(boundary)\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("--\(boundary)\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"parent_id\"\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Type: text/plain\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("0\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("--\(boundary)\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"filename\"; filename=\"testFile.txt\"\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Type: text/plain\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(data!)
body.appendData("\n--\(boundary)--".dataUsingEncoding(NSUTF8StringEncoding)!)
request.HTTPBody = body
let task = NSURLSession.sharedSession().uploadTaskWithRequest(request, fromData: data!, completionHandler: {(data,response,error) in
if(error != nil) {
NSLog("Error message = \(error.debugDescription)")
}
if(response != nil) {
NSLog("Response = \(response)")
}
if(data != nil) {
NSLog("Data = \(data!)")
}
});
task.resume()
The server responds with a 405 error.
Response = Optional(<NSHTTPURLResponse: 0x157de26d0> { URL: https://upload.box.com/api/2.0/files/content } { status code: 405, headers {
Age = 0;
Allow = "GET, OPTIONS, HEAD";
Connection = "keep-alive";
"Content-Length" = 0;
"Content-Type" = "text/html; charset=UTF-8";
Date = "Thu, 13 Oct 2016 14:49:49 GMT";
Server = ATS;
} })
From the example above the request header is:
Request headers - Optional(["Authorization": "Bearer q1ckXDSTC0EcsCJiLtW638o5x5roWabb"])!
And the request body is:
body - Content-Type: multipart/form-data; boundary=0B1D5883-9EA6-4D6B-9FEC-C51D817D5975
--0B1D5883-9EA6-4D6B-9FEC-C51D817D5975
Content-Disposition: form-data; name="parent_id"
Content-Type: text/plain
0
--0B1D5883-9EA6-4D6B-9FEC-C51D817D5975
Content-Disposition: form-data; name="filename"; filename="testFile.txt"
Content-Type: text/plain
Hello
--0B1D5883-9EA6-4D6B-9FEC-C51D817D5975--
We have tried to disseminate the cURL request with Wireshark but as it is an SSL request this didn't help much.
The verbose version of the cURL request looks like this:
0000: POST /api/2.0/files/content HTTP/1.1
0026: Host: upload.box.com
003c: User-Agent: curl/7.47.0
0055: Accept: */*
0062: Authorization: Bearer o6ojjxpfECdKrBzIUhiRyRKHTTXg8Sll
009a: Content-Length: 368
00af: Expect: 100-continue
00c5: Content-Type: multipart/form-data; boundary=--------------------
0105: ----ed667cff7b6570a1
011b:
== Info: Done waiting for 100-continue
=> Send data, 285 bytes (0x11d)
0000: --------------------------ed667cff7b6570a1
002c: Content-Disposition: form-data; name="attributes"
005f:
0061: {"name":"testFile.txt", "parent":{"id":"0"}}
008f: --------------------------ed667cff7b6570a1
00bb: Content-Disposition: form-data; name="file"; filename="testFile.
00fb: txt"
0101: Content-Type: text/plain
011b:
=> Send data, 35 bytes (0x23)
0000: This is a test file.
=> Send data, 48 bytes (0x30)
0000:
0002: --------------------------ed667cff7b6570a1--
Which we have also tried to replicate exactly, but the problem remains.
If anyone has achieved this successfully I would really appreciate some direction or suggestions
We contacted Box support team but they have yet to respond.

Related

I added a cookie to my URLSession, but it's missing in my URLRequest

I'm stuck on an issue with cookies in a URLSession URLRequest. I have a Swift Playground where I add a cookie to my URLSession, and I expect to see it in the request to my local server, but it's not there. What am I missing? Here is my playground:
import Foundation
let config = URLSessionConfiguration.default
config.httpCookieStorage?.setCookie(
HTTPCookie(properties: [
HTTPCookiePropertyKey.domain: "http://localhost:8000",
HTTPCookiePropertyKey.path: "/",
HTTPCookiePropertyKey.name: "Eugene",
HTTPCookiePropertyKey.value: "Test",
HTTPCookiePropertyKey.secure: "TRUE",
HTTPCookiePropertyKey.expires: Date(timeIntervalSinceNow: 1000000)
])!
)
config.httpShouldSetCookies = true
config.httpCookieAcceptPolicy = .always
let session = URLSession(configuration: config)
print(session.configuration.httpCookieStorage?.cookies)
var request = URLRequest(url: URL(string: "http://localhost:8000")!)
let task = session.dataTask(with: request) { data, urlResponse, error in
print(data)
print(urlResponse)
print(error)
}
task.resume()
This is the output of my playground:
Optional([<NSHTTPCookie
version:0
name:Eugene
value:Test
expiresDate:'2021-06-05 15:25:19 +0000'
created:'2021-05-25 01:38:39 +0000'
sessionOnly:FALSE
domain:http://localhost:8000
partition:none
sameSite:none
path:/
isSecure:TRUE
path:"/" isSecure:TRUE>])
Optional(1318 bytes)
Optional(<NSHTTPURLResponse: 0x6000005c5500> { URL: http://localhost:8000/ } { Status Code: 200, Headers {
"Content-Length" = (
1318
);
"Content-Type" = (
"text/html; charset=utf-8"
);
Date = (
"Tue, 25 May 2021 01:38:40 GMT"
);
Server = (
"SimpleHTTP/0.6 Python/3.8.2"
);
} })
nil
And this is the request the server sees, conspicuously missing the Cookie header:
127.0.0.1 - - [24/May/2021 20:38:40] "GET / HTTP/1.1" 200 -
ERROR:root:Host: localhost:8000
Accept: */*
Accept-Language: en-us
Connection: keep-alive
Accept-Encoding: gzip, deflate
User-Agent: URLSession/1 CFNetwork/1220.1 Darwin/20.3.0
One problem may be that Swift playgrounds at least historically didn't get along well with NSURLSession. This might be fixed now, in which case never mind, but if not, then you might try doing this in an actual app.
A second problem is that the isSecure property on the cookie is set, but you're trying to send the cookie over HTTP (not secure), so it won't ever get sent.
Finally, localhost itself may be problematic. See Cookies on localhost with explicit domain. I have no idea how Apple's cookie storage handles that edge case, but you should be aware of it anyway.

Uploading an UIImage to REST API with Swift 5.0

I can send (upload) a picture to REST API with Postman. But I can't send(upload) a picture with Swift code in to REST API.
First of all, I would like to share the raw data working with Postman:
POST http://testwlbyp.abc.com/ebys-servis-rest/ebys
200
583 ms
POST /ebys-servis-rest/ebys HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: PostmanRuntime/7.25.0
Accept: */*
Cache-Control: no-cache
Postman-Token: 888b7a05-e796-447f-9f6f-f3265bc38f3d
Host: testwlbyp.abc.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: JSESSIONID=tZXqTQrm8CHDiALd42cIWIiyVctFPSCJMKh-nxIyfqnVLu4CXChe!-1356762059
Content-Length: 11450
----------------------------postman
Content-Disposition: form-data; name="p_type"
LOAD_DOC
----------------------------postman
Content-Disposition: form-data; name="doc_no"
20200006966
----------------------------postman
Content-Disposition: form-data; name="token"
4268260b012c51fe8b2d41fe4c48e289
----------------------------postman
Content-Disposition: form-data; name="theme_no"
63
----------------------------postman
Content-Disposition: form-data; name="all_parameters"
FILE_NAME='myTest.jpg'
----------------------------postman
Content-Disposition: form-data; name="profile_no"
A-S
----------------------------postman
Content-Disposition: form-data; name="ss_no"
M1997069
----------------------------postman
Content-Disposition: form-data; name="user_agent"
----------------------------postman
Content-Disposition: form-data; name="file"; filename="testpic.jpg"
<myTest.jpg>
----------------------------postman--
Here is the response:
HTTP/1.1 200 OK
Date: Thu, 25 Jun 2020 08:00:15 GMT
Content-Length: 169
Content-Type: application/json
{
"LOGID": 282659,
"RESULTID": "20200006966",
"FILESIZE": -1,
"OPNAME": "LOAD_DOC",
"ERRORCODE": 0,
"ERRORMESSAGE": "success"
}
And here is the Swift Code;
func uploadPicture(paramaterImage: UIImage, parameterDocNo: String, parameterToken: String, parameterSSNo: String)
{
print("LOG 01") // I SEE THIS LOG
let fileName = "testImageName.jpg"
let oall_param : String = "FILE?_NAME='\(fileName+".jpg")'"
let url = URL(string: "http://testwlbyp.abc.com/ebys-servis-rest/ebys")
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = "POST"
print("LOG 02") // I SEE THIS LOG
let image = paramaterImage
let data = image.jpegData(compressionQuality: 0.7)!
let qstr = String(decoding: data, as: UTF8.self)
// 1
urlRequest.addValue("LOAD_DOC", forHTTPHeaderField: "p_type")
// 2
urlRequest.addValue(parameterDocNo, forHTTPHeaderField: "doc_no")
// 3
urlRequest.addValue(parameterToken, forHTTPHeaderField: "token")
// 4
urlRequest.addValue("63123", forHTTPHeaderField: "theme_no")
// 5
urlRequest.addValue(oall_param, forHTTPHeaderField: "all_parameters")
// 6
urlRequest.addValue("A-S", forHTTPHeaderField: "profile_no")
// 7
urlRequest.addValue(parameterSSNo, forHTTPHeaderField: "ss_no")
// 8
urlRequest.addValue("", forHTTPHeaderField: "user_agent")
// 9
urlRequest.addValue(qstr, forHTTPHeaderField: "file")
let boundary = "Boundary-\(UUID().uuidString)"
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
print("LOG 03") // I SEE THIS LOG
//create body
let body = NSMutableData()
//append first line
let line1_boundryPrefix = "--\(boundary)\r\n"
body.append(line1_boundryPrefix.data(
using: String.Encoding.utf8,
allowLossyConversion: false)!)
//append second line
let line2_parameter = "Content-Disposition: form-data; name=\"source\"; filename=\"" + fileName + "\"\r\n"
body.append(line2_parameter.data(
using: String.Encoding.utf8,
allowLossyConversion: false)!)
//append third line (mime type)
let mimeType = "image/jpg"
let line3_contentType = "Content-Type: \(mimeType)\r\n\r\n"
body.append(line3_contentType.data(
using: String.Encoding.utf8,
allowLossyConversion: false)!)
//append image data
//line4
body.append(data)
let line5 = "\r\n"
body.append(line5.data(
using: String.Encoding.utf8,
allowLossyConversion: false)!)
let line6 = "--" + boundary + "--\r\n"
body.append(line6.data(
using: String.Encoding.utf8,
allowLossyConversion: false)!)
urlRequest.httpBody = body as Data
urlRequest.setValue(String(body.length), forHTTPHeaderField: "Content-Length")
URLSession.shared.dataTask(with: urlRequest) { (data, urlResponse, error) in
//handle callback
print("LOG 04, urlResponse: \(urlResponse)") // I DON'T SEE THIS LOG
}
print("LOG END") // I SEE THIS LOG
}
Here is the result from Xcode output window:
LOG 01
LOG 02
LOG 03
LOG END
I don't see LOG04
I am sending UIImage parameter(paramaterImage: UIImage) in to uploadPicture method from below struct:
import UIKit
struct MyMedia {
    let key: String
    let filename: String
    let data: Data?
    let mimeType: String
    
    init?(withImage image: UIImage, forKey key: String) {
        self.key = key
        self.mimeType = "image/jpeg"
        self.filename = "temppicname.jpg"
        self.data = image.jpegData(compressionQuality: 1)
    }
}
You must call resume() on the task, or it won't start:
let task = URLSession.shared.dataTask(with: urlRequest) { (data, urlResponse, error) in
//handle callback
print("LOG 04, urlResponse: \(urlResponse)") // I DON'T SEE THIS LOG
}
task.resume()
After that, you need to make sure you're formatting the request the same way as you are in postman, otherwise the server will reject it.
I'd suggest you use something like Charles proxy to compare what your app is sending to what you're sending in Postman.
At a quick glance it looks like you're sending p_type etc. as request headers, but in your example from Postman, those values are part of the form.

Bad Request While Trying to create transient document in adobe using REST API

I am trying to create a transient document in adobe sign using rest api.Foloowing is my code snippet:
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.in1.echosign.com/api/rest/v6/transientDocuments');
req.setMethod(postMethod);
req.setHeader('Content-Type', 'application/json');
blob testFileContent = blob.toPDF('test string it is');
// req.setBody('{"filename":"testsign","file":'+testFileContent+'}');
req.setBody('{"fileName":"testsign","file":"'+testFileContent+'"}');
String authorizationHeader = 'Bearer ' +acceessToken;
req.setHeader('Authorization', authorizationHeader);
//req.setHeader('Authorization', acceessToken);
try{
HttpResponse res = h.send(req);
However I am getting the bad request error in response.Issue maybe due to the tag mismatch with adobe sign requirement, however after a lot of research and trial and error I am not able to find the accurate tags to put in JSON for this API request. Any suggestion and help ?
Form data
Content-Disposition: form-data; name="File"; filename="<filename>.pdf"
Content-Type: application/pdf
Request params
Content-Type: multipart/form-data;
Accept: application/json, text/javascript, */*;
More details available in the documentation.

can't get web page source from url in Swift

I'm currently using SwiftHTTP for getting source of url address. I am using 'get method' for getting source code of url address which is
do {
let opt = try HTTP.GET(self.my_url_address!)
opt.start { response in
if let err = response.error {
print("error: \(err.localizedDescription)")
return
}
print(response.description)
}
} catch let error {
print("got an error creating the request: \(error)")
}
after this code run I got this output in Xcode output screen
URL: http://myweburl.com/detay/swat-under-siege.html
Status Code: 200
Headers: Content-Type: text/html
Connection: keep-alive
CF-RAY: 38391215a60e2726-FRA
Set-Cookie: ASPSESSIONIDSABBBSDT=HPKKPJGCDLKMDMILNGHPCAGD; path=/
Date: Mon, 24 Jul 2017 18:51:24 GMT
Vary: Accept-Encoding
X-Powered-By: ASP.NET Transfer-Encoding: Identity
Server: cloudflare-nginx
Content-Encoding: gzip
Cache-Control: private
The status code is 200 but the output is not the source code of url. How can I fix this?
Response is correct. I've tried requesting the website (the real one) and it works:
print(response.data.base64EncodedString())
If you decode the BASE64 data, it will render valid HTML code.
The issue seems related to encoding. After checking the website's head tag, it states that the charset is windows-1254
String(data: response.data, encoding: .windowsCP1254) // works. latin1, etc.
Your issue is similar to SWIFT: NSURLSession convert data to String

Getting 500 error (missing ;) from blobstore upload

I would like to skip the part where an upload URL is sent to the client, and upload directly to the blobstore from the backend. I use this to send the multipart request, although I get:
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing/_ah/upload/ahB0NTIzNjU4OTY1ODk1Njg5ciILEhVfX0Jsb2JVcGxvYWRTZXNzaW9uX18YgICAgICAiAs M. Reason:
<pre> Missing ';'</pre></p><h3>Caused by:</h3> <pre>javax.mail.internet.ParseException: Missing ';'
at javax.mail.internet.ParameterList.<init>(ParameterList.java:135)
at javax.mail.internet.ContentType.<init>(ContentType.java:72)
at javax.mail.internet.MimeMultipart.<init>(MimeMultipart.java:98)
at com.google.apphosting.utils.servlet.MultipartMimeUtils.parseMultipartRequest(MultipartMimeUtils.java:41)
at com.google.appengine.api.blobstore.dev.UploadBlobServlet.handleUpload(UploadBlobServlet.java:173)
at com.google.appengine.api.blobstore.dev.UploadBlobServlet.access$000(UploadBlobServlet.java:71)
at com.google.appengine.api.blobstore.dev.UploadBlobServlet$1.run(UploadBlobServlet.java:117)
at java.security.AccessController.doPrivileged(Native Method)
at com.google.appengine.api.blobstore.dev.UploadBlobServlet.doPost(UploadBlobServlet.java:114)
this is the data sent:
--__END_OF_PART__
Content-Type: image/bmp
content-transfer-encoding: binary
content-disposition: form-data; name="file"
[binary string here]
--__END_OF_PART__--
these are the headers:
accept-encoding: gzip,
content-type: multipart/form-data; boundary=__END_OF_PART__
and this is the code:
BlobstoreService service = BlobstoreServiceFactory.getBlobstoreService();
String url = service.createUploadUrl("/upload");
HttpRequestFactory factory = UrlFetchTransport.getDefaultInstance().createRequestFactory();
MultipartFormContent content = new MultipartFormContent();
content.addPart(new MultipartFormContent.Part(
"file",
new InputStreamContent("image/bmp",
new ByteArrayInputStream(Base64.decodeBase64(data)))));
content.writeTo(System.out);
HttpRequest request = factory.buildPostRequest(new GenericUrl(new URL(url)), content);
request.getHeaders().setContentType(content.getMediaType().build());
request.execute();
As noted in the docs, the multipart body has to be multipart/form-data with input type "file", which produces the Content-Disposition header with "filename" section:
Content-Disposition: form-data; name="myFile"; filename="imagename.bmp"
With HttpClient it should be something like this.