forked from PerfectlySoft/Perfect
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcURL.swift
325 lines (279 loc) · 9.01 KB
/
cURL.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
//
// cURL.swift
// PerfectLib
//
// Created by Kyle Jessup on 2015-08-10.
// Copyright (C) 2015 PerfectlySoft, Inc.
//
//===----------------------------------------------------------------------===//
//
// This source file is part of the Perfect.org open source project
//
// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors
// Licensed under Apache License v2.0
//
// See http://perfect.org/licensing.html for license information
//
//===----------------------------------------------------------------------===//
//
import cURL
/// This class is a wrapper around the CURL library. It permits network operations to be completed using cURL in a block or non-blocking manner.
public class CURL {
static var sInit:Int = {
curl_global_init(Int(CURL_GLOBAL_SSL | CURL_GLOBAL_WIN32))
return 1
}()
var curl: UnsafeMutablePointer<Void>?
var multi: UnsafeMutablePointer<Void>?
var slists = [UnsafeMutablePointer<curl_slist>]()
var headerBytes = [UInt8]()
var bodyBytes = [UInt8]()
/// The CURLINFO_RESPONSE_CODE for the last operation.
public var responseCode: Int {
return self.getInfo(CURLINFO_RESPONSE_CODE).0
}
/// Get or set the current URL.
public var url: String {
get {
return self.getInfo(CURLINFO_EFFECTIVE_URL).0
}
set {
self.setOption(CURLOPT_URL, s: newValue)
}
}
/// Initialize the CURL request.
public init() {
self.curl = curl_easy_init()
setCurlOpts()
}
/// Initialize the CURL request with a given URL.
public convenience init(url: String) {
self.init()
self.url = url
}
/// Duplicate the given request into a new CURL object.
public init(dupeCurl: CURL) {
if let copyFrom = dupeCurl.curl {
self.curl = curl_easy_duphandle(copyFrom)
} else {
self.curl = curl_easy_init()
}
setCurlOpts() // still set options
}
func setCurlOpts() {
curl_easy_setopt_long(self.curl!, CURLOPT_NOSIGNAL, 1)
let opaqueMe = UnsafeMutablePointer<Void>(Unmanaged.passUnretained(self).toOpaque())
#if Ubuntu_14_04
setOption(CURLOPT_WRITEHEADER, v: opaqueMe)
setOption(CURLOPT_FILE, v: opaqueMe)
setOption(CURLOPT_INFILE, v: opaqueMe)
#else
setOption(CURLOPT_HEADERDATA, v: opaqueMe)
setOption(CURLOPT_WRITEDATA, v: opaqueMe)
setOption(CURLOPT_READDATA, v: opaqueMe)
#endif
let headerReadFunc: curl_func = {
(a: UnsafeMutablePointer<Void>, size: Int, num: Int, p: UnsafeMutablePointer<Void>) -> Int in
let crl = Unmanaged<CURL>.fromOpaque(COpaquePointer(p)).takeUnretainedValue()
let bytes = UnsafeMutablePointer<UInt8>(a)
let fullCount = size*num
for idx in 0..<fullCount {
crl.headerBytes.append(bytes[idx])
}
return fullCount
}
setOption(CURLOPT_HEADERFUNCTION, f: headerReadFunc)
let writeFunc: curl_func = {
(a: UnsafeMutablePointer<Void>, size: Int, num: Int, p: UnsafeMutablePointer<Void>) -> Int in
let crl = Unmanaged<CURL>.fromOpaque(COpaquePointer(p)).takeUnretainedValue()
let bytes = UnsafeMutablePointer<UInt8>(a)
let fullCount = size*num
for idx in 0..<fullCount {
crl.bodyBytes.append(bytes[idx])
}
return fullCount
}
setOption(CURLOPT_WRITEFUNCTION, f: writeFunc)
let readFunc: curl_func = {
(a: UnsafeMutablePointer<Void>, b: Int, c: Int, p: UnsafeMutablePointer<Void>) -> Int in
// !FIX!
// let crl = Unmanaged<CURL>.fromOpaque(COpaquePointer(p)).takeUnretainedValue()
return 0
}
setOption(CURLOPT_READFUNCTION, f: readFunc)
}
/// Clean up and reset the CURL object.
public func reset() {
if self.curl != nil {
if self.multi != nil {
curl_multi_remove_handle(self.multi!, self.curl!)
self.multi = nil
}
while self.slists.count > 0 {
curl_slist_free_all(self.slists.last!)
self.slists.removeLast()
}
curl_easy_reset(self.curl!)
setCurlOpts()
}
}
/// Perform the CURL request in a non-blocking manner. The closure will be called with the resulting code, header and body data.
public func perform(closure: (Int, [UInt8], [UInt8]) -> ()) {
let header = Bytes()
let body = Bytes()
self.multi = curl_multi_init()
curl_multi_add_handle(self.multi!, self.curl!)
performInner(header, body: body, closure: closure)
}
private func performInner(header: Bytes, body: Bytes, closure: (Int, [UInt8], [UInt8]) -> ()) {
let perf = self.perform()
if let h = perf.2 {
header.importBytes(h)
}
if let b = perf.3 {
body.importBytes(b)
}
if perf.0 == false { // done
closure(perf.1, header.data, body.data)
} else {
Threading.dispatchBlock { [weak self] in
self?.performInner(header, body: body, closure: closure)
}
}
}
/// Performs the request, blocking the current thread until it completes.
/// - returns: A tuple consisting of: Int - the result code, [UInt8] - the header bytes if any, [UInt8] - the body bytes if any
public func performFully() -> (Int, [UInt8], [UInt8]) {
let code = curl_easy_perform(self.curl!)
defer {
if self.headerBytes.count > 0 {
self.headerBytes = [UInt8]()
}
if self.bodyBytes.count > 0 {
self.bodyBytes = [UInt8]()
}
self.reset()
}
if code != CURLE_OK {
let str = self.strError(code)
print(str)
}
return (Int(code.rawValue), self.headerBytes, self.bodyBytes)
}
/// Performs a bit of work on the current request.
/// - returns: A tuple consisting of: Bool - should perform() be called again, Int - the result code, [UInt8] - the header bytes if any, [UInt8] - the body bytes if any
public func perform() -> (Bool, Int, [UInt8]?, [UInt8]?) {
if self.multi == nil {
self.multi = curl_multi_init()
curl_multi_add_handle(self.multi!, self.curl!)
}
var one: Int32 = 0
var code = CURLM_OK
repeat {
code = curl_multi_perform(self.multi!, &one)
} while code == CURLM_CALL_MULTI_PERFORM
guard code == CURLM_OK else {
return (false, Int(code.rawValue), nil, nil)
}
var two: Int32 = 0
let msg = curl_multi_info_read(self.multi!, &two)
defer {
if self.headerBytes.count > 0 {
self.headerBytes = [UInt8]()
}
if self.bodyBytes.count > 0 {
self.bodyBytes = [UInt8]()
}
}
if msg != nil {
let msgResult = curl_get_msg_result(msg)
guard msgResult == CURLE_OK else {
return (false, Int(msgResult.rawValue), nil, nil)
}
return (false, Int(msgResult.rawValue),
self.headerBytes.count > 0 ? self.headerBytes : nil,
self.bodyBytes.count > 0 ? self.bodyBytes : nil)
}
return (true, 0,
self.headerBytes.count > 0 ? self.headerBytes : nil,
self.bodyBytes.count > 0 ? self.bodyBytes : nil)
}
// /// Returns the result code for the last
// public func multiResult() -> CURLcode {
// var two: Int32 = 0
// let msg = curl_multi_info_read(self.multi!, &two)
// if msg != nil && msg.memory.msg == CURLMSG_DONE {
// return curl_get_msg_result(msg)
// }
// return CURLE_OK
// }
/// Returns the String message for the given CURL result code.
public func strError(code: CURLcode) -> String {
return String.fromCString(curl_easy_strerror(code))!
}
/// Returns the Int value for the given CURLINFO.
public func getInfo(info: CURLINFO) -> (Int, CURLcode) {
var i = 0
let c = curl_easy_getinfo_long(self.curl!, info, &i)
return (i, c)
}
/// Returns the String value for the given CURLINFO.
public func getInfo(info: CURLINFO) -> (String, CURLcode) {
let i = UnsafeMutablePointer<UnsafePointer<Int8>>.alloc(1)
defer { i.destroy(); i.dealloc(1) }
let code = curl_easy_getinfo_cstr(self.curl!, info, i)
return (code != CURLE_OK ? "" : String.fromCString(i.memory)!, code)
}
/// Sets the Int64 option value.
public func setOption(option: CURLoption, int: Int64) -> CURLcode {
return curl_easy_setopt_int64(self.curl!, option, int)
}
/// Sets the Int option value.
public func setOption(option: CURLoption, int: Int) -> CURLcode {
return curl_easy_setopt_long(self.curl!, option, int)
}
/// Sets the poionter option value.
public func setOption(option: CURLoption, v: UnsafeMutablePointer<Void>) -> CURLcode {
return curl_easy_setopt_void(self.curl!, option, v)
}
/// Sets the callback function option value.
public func setOption(option: CURLoption, f: curl_func) -> CURLcode {
return curl_easy_setopt_func(self.curl!, option, f)
}
/// Sets the String option value.
public func setOption(option: CURLoption, s: String) -> CURLcode {
switch(option.rawValue) {
case CURLOPT_HTTP200ALIASES.rawValue,
CURLOPT_HTTPHEADER.rawValue,
CURLOPT_POSTQUOTE.rawValue,
CURLOPT_PREQUOTE.rawValue,
CURLOPT_QUOTE.rawValue,
CURLOPT_MAIL_FROM.rawValue,
CURLOPT_MAIL_RCPT.rawValue:
let slist = curl_slist_append(nil, s)
self.slists.append(slist)
return curl_easy_setopt_slist(self.curl!, option, slist)
default:
()
}
return curl_easy_setopt_cstr(self.curl!, option, s)
}
/// Cleanup and close the CURL request.
public func close() {
if self.curl != nil {
if self.multi != nil {
curl_multi_cleanup(self.multi!)
self.multi = nil
}
curl_easy_cleanup(self.curl!)
self.curl = nil
while self.slists.count > 0 {
curl_slist_free_all(self.slists.last!)
self.slists.removeLast()
}
}
}
deinit {
self.close()
}
}