react-native-blob-util 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/android/src/main/AndroidManifest.xml +0 -3
- package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilBody.java +30 -6
- package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilReq.java +70 -0
- package/android/src/main/java/com/ReactNativeBlobUtil/ReactNativeBlobUtilUtils.java +5 -17
- package/index.d.ts +1 -1
- package/ios/ReactNativeBlobUtil.xcodeproj/project.pbxproj +0 -2
- package/ios/ReactNativeBlobUtilFS.m +1 -2
- package/ios/ReactNativeBlobUtilReqBuilder.m +1 -2
- package/ios/ReactNativeBlobUtilRequest.m +5 -6
- package/package.json +1 -1
- package/ios/IOS7Polyfill.h +0 -27
package/README.md
CHANGED
|
@@ -908,6 +908,30 @@ ReactNativeBlobUtil.fetch('POST', 'http://example.com/upload', {'Transfer-Encodi
|
|
|
908
908
|
### Self-Signed SSL Server
|
|
909
909
|
|
|
910
910
|
By default, react-native-blob-util does NOT allow connection to unknown certification provider since it's dangerous. To connect a server with self-signed certification, you need to add `trusty` to `config` explicitly. This function is available for version >= `0.5.3`
|
|
911
|
+
In addition since ``0.16.0`` you'll have to define your own trust manager for android.
|
|
912
|
+
````java
|
|
913
|
+
public class MainApplication extends Application implements ReactApplication {
|
|
914
|
+
...
|
|
915
|
+
@Override
|
|
916
|
+
public void onCreate() {
|
|
917
|
+
...
|
|
918
|
+
ReactNativeBlobUtilUtils.sharedTrustManager = final X509TrustManager x509TrustManager = new X509TrustManager() {
|
|
919
|
+
@Override
|
|
920
|
+
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
@Override
|
|
924
|
+
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
@Override
|
|
928
|
+
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
|
929
|
+
return new java.security.cert.X509Certificate[]{};
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
...
|
|
933
|
+
}
|
|
934
|
+
````
|
|
911
935
|
|
|
912
936
|
```js
|
|
913
937
|
ReactNativeBlobUtil.config({
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
2
|
package="com.ReactNativeBlobUtil">
|
|
3
3
|
|
|
4
|
-
<!-- Required to access Google Play Licensing -->
|
|
5
|
-
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
|
|
6
|
-
|
|
7
4
|
<!-- Required to download files from Google Play -->
|
|
8
5
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
9
6
|
|
|
@@ -26,7 +26,6 @@ import okio.BufferedSink;
|
|
|
26
26
|
|
|
27
27
|
class ReactNativeBlobUtilBody extends RequestBody {
|
|
28
28
|
|
|
29
|
-
private InputStream requestStream;
|
|
30
29
|
private long contentLength = 0;
|
|
31
30
|
private ReadableArray form;
|
|
32
31
|
private String mTaskId;
|
|
@@ -71,12 +70,10 @@ class ReactNativeBlobUtilBody extends RequestBody {
|
|
|
71
70
|
try {
|
|
72
71
|
switch (requestType) {
|
|
73
72
|
case SingleFile:
|
|
74
|
-
|
|
75
|
-
contentLength = requestStream.available();
|
|
73
|
+
contentLength = getRequestStream().available();
|
|
76
74
|
break;
|
|
77
75
|
case AsIs:
|
|
78
76
|
contentLength = this.rawBody.getBytes().length;
|
|
79
|
-
requestStream = new ByteArrayInputStream(this.rawBody.getBytes());
|
|
80
77
|
break;
|
|
81
78
|
case Others:
|
|
82
79
|
break;
|
|
@@ -98,7 +95,6 @@ class ReactNativeBlobUtilBody extends RequestBody {
|
|
|
98
95
|
this.form = body;
|
|
99
96
|
try {
|
|
100
97
|
bodyCache = createMultipartBodyCache();
|
|
101
|
-
requestStream = new FileInputStream(bodyCache);
|
|
102
98
|
contentLength = bodyCache.length();
|
|
103
99
|
} catch (Exception ex) {
|
|
104
100
|
ex.printStackTrace();
|
|
@@ -107,6 +103,34 @@ class ReactNativeBlobUtilBody extends RequestBody {
|
|
|
107
103
|
return this;
|
|
108
104
|
}
|
|
109
105
|
|
|
106
|
+
// This organizes the input stream initialization logic into a method. This allows:
|
|
107
|
+
// 1) Initialization to be deferred until it's needed (when we are ready to pipe it into the BufferedSink)
|
|
108
|
+
// 2) The stream to be initialized and used as many times as necessary. When okhttp runs into
|
|
109
|
+
// a connection error, it will retry the request which will require a new stream to write into
|
|
110
|
+
// the sink once again.
|
|
111
|
+
InputStream getInputStreamForRequestBody() {
|
|
112
|
+
try {
|
|
113
|
+
if (this.form != null) {
|
|
114
|
+
return new FileInputStream(bodyCache);
|
|
115
|
+
} else {
|
|
116
|
+
switch (requestType) {
|
|
117
|
+
case SingleFile:
|
|
118
|
+
return getRequestStream();
|
|
119
|
+
case AsIs:
|
|
120
|
+
return new ByteArrayInputStream(this.rawBody.getBytes());
|
|
121
|
+
case Others:
|
|
122
|
+
ReactNativeBlobUtilUtils.emitWarningEvent("ReactNativeBlobUtil could not create input stream for request type others");
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (Exception ex){
|
|
127
|
+
ex.printStackTrace();
|
|
128
|
+
ReactNativeBlobUtilUtils.emitWarningEvent("ReactNativeBlobUtil failed to create input stream for request:" + ex.getLocalizedMessage());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
@Override
|
|
111
135
|
public long contentLength() {
|
|
112
136
|
return chunkedEncoding ? -1 : contentLength;
|
|
@@ -120,7 +144,7 @@ class ReactNativeBlobUtilBody extends RequestBody {
|
|
|
120
144
|
@Override
|
|
121
145
|
public void writeTo(@NonNull BufferedSink sink) {
|
|
122
146
|
try {
|
|
123
|
-
pipeStreamToSink(
|
|
147
|
+
pipeStreamToSink(getInputStreamForRequestBody(), sink);
|
|
124
148
|
} catch (Exception ex) {
|
|
125
149
|
ReactNativeBlobUtilUtils.emitWarningEvent(ex.getLocalizedMessage());
|
|
126
150
|
ex.printStackTrace();
|
|
@@ -12,6 +12,9 @@ import android.net.NetworkCapabilities;
|
|
|
12
12
|
import android.net.NetworkInfo;
|
|
13
13
|
import android.net.Uri;
|
|
14
14
|
import android.os.Build;
|
|
15
|
+
import android.os.Bundle;
|
|
16
|
+
import android.os.Handler;
|
|
17
|
+
import android.os.Message;
|
|
15
18
|
import android.util.Base64;
|
|
16
19
|
import android.webkit.CookieManager;
|
|
17
20
|
|
|
@@ -48,6 +51,9 @@ import java.util.HashMap;
|
|
|
48
51
|
|
|
49
52
|
import java.util.List;
|
|
50
53
|
import java.util.Locale;
|
|
54
|
+
import java.util.concurrent.Executors;
|
|
55
|
+
import java.util.concurrent.Future;
|
|
56
|
+
import java.util.concurrent.ScheduledExecutorService;
|
|
51
57
|
import java.util.concurrent.TimeUnit;
|
|
52
58
|
|
|
53
59
|
import javax.net.ssl.SSLContext;
|
|
@@ -161,6 +167,59 @@ public class ReactNativeBlobUtilReq extends BroadcastReceiver implements Runnabl
|
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
|
|
170
|
+
private final int QUERY = 1314;
|
|
171
|
+
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
|
|
172
|
+
private Future<?> future;
|
|
173
|
+
private Handler mHandler = new Handler(new Handler.Callback() {
|
|
174
|
+
public boolean handleMessage(Message msg) {
|
|
175
|
+
switch (msg.what) {
|
|
176
|
+
|
|
177
|
+
case QUERY:
|
|
178
|
+
|
|
179
|
+
Bundle data = msg.getData();
|
|
180
|
+
long id = data.getLong("downloadManagerId");
|
|
181
|
+
if (id == downloadManagerId) {
|
|
182
|
+
|
|
183
|
+
Context appCtx = ReactNativeBlobUtil.RCTContext.getApplicationContext();
|
|
184
|
+
|
|
185
|
+
DownloadManager downloadManager = (DownloadManager) appCtx.getSystemService(Context.DOWNLOAD_SERVICE);
|
|
186
|
+
|
|
187
|
+
DownloadManager.Query query = new DownloadManager.Query();
|
|
188
|
+
|
|
189
|
+
Cursor cursor = downloadManager.query(query);
|
|
190
|
+
|
|
191
|
+
if (cursor != null && cursor.moveToFirst()) {
|
|
192
|
+
|
|
193
|
+
long written = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
|
|
194
|
+
|
|
195
|
+
long total = cursor.getLong(cursor.getColumnIndex(
|
|
196
|
+
DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
|
|
197
|
+
cursor.close();
|
|
198
|
+
|
|
199
|
+
ReactNativeBlobUtilProgressConfig reportConfig = getReportProgress(taskId);
|
|
200
|
+
float progress = (total != -1) ? written / total : 0;
|
|
201
|
+
|
|
202
|
+
if (reportConfig != null && reportConfig.shouldReport(progress /* progress */)) {
|
|
203
|
+
WritableMap args = Arguments.createMap();
|
|
204
|
+
args.putString("taskId", String.valueOf(taskId));
|
|
205
|
+
args.putString("written", String.valueOf(written));
|
|
206
|
+
args.putString("total", String.valueOf(total));
|
|
207
|
+
args.putString("chunk", "");
|
|
208
|
+
ReactNativeBlobUtil.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
209
|
+
.emit(ReactNativeBlobUtilConst.EVENT_PROGRESS, args);
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (total == written) {
|
|
214
|
+
future.cancel(true);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
164
223
|
@Override
|
|
165
224
|
public void run() {
|
|
166
225
|
|
|
@@ -212,6 +271,17 @@ public class ReactNativeBlobUtilReq extends BroadcastReceiver implements Runnabl
|
|
|
212
271
|
downloadManagerId = dm.enqueue(req);
|
|
213
272
|
androidDownloadManagerTaskTable.put(taskId, Long.valueOf(downloadManagerId));
|
|
214
273
|
appCtx.registerReceiver(this, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
|
274
|
+
future = scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
|
|
275
|
+
@Override
|
|
276
|
+
public void run() {
|
|
277
|
+
Message msg = mHandler.obtainMessage();
|
|
278
|
+
Bundle data = new Bundle();
|
|
279
|
+
data.putLong("downloadManagerId", downloadManagerId);
|
|
280
|
+
msg.setData(data);
|
|
281
|
+
msg.what = QUERY;
|
|
282
|
+
mHandler.sendMessage(msg);
|
|
283
|
+
}
|
|
284
|
+
}, 0, 100, TimeUnit.MILLISECONDS);
|
|
215
285
|
return;
|
|
216
286
|
}
|
|
217
287
|
|
|
@@ -10,7 +10,6 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
|
10
10
|
|
|
11
11
|
import java.nio.charset.Charset;
|
|
12
12
|
import java.security.MessageDigest;
|
|
13
|
-
import java.security.cert.CertificateException;
|
|
14
13
|
import java.util.Locale;
|
|
15
14
|
|
|
16
15
|
import javax.net.ssl.HostnameVerifier;
|
|
@@ -22,9 +21,10 @@ import javax.net.ssl.X509TrustManager;
|
|
|
22
21
|
|
|
23
22
|
import okhttp3.OkHttpClient;
|
|
24
23
|
|
|
25
|
-
|
|
26
24
|
public class ReactNativeBlobUtilUtils {
|
|
27
25
|
|
|
26
|
+
public static X509TrustManager sharedTrustManager;
|
|
27
|
+
|
|
28
28
|
public static String getMD5(String input) {
|
|
29
29
|
String result = null;
|
|
30
30
|
|
|
@@ -61,22 +61,10 @@ public class ReactNativeBlobUtilUtils {
|
|
|
61
61
|
|
|
62
62
|
public static OkHttpClient.Builder getUnsafeOkHttpClient(OkHttpClient client) {
|
|
63
63
|
try {
|
|
64
|
-
// Create a trust manager that does not validate certificate chains
|
|
65
|
-
final X509TrustManager x509TrustManager = new X509TrustManager() {
|
|
66
|
-
@Override
|
|
67
|
-
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
|
68
|
-
}
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
|
72
|
-
}
|
|
65
|
+
if (sharedTrustManager == null) throw new IllegalStateException("Use of own trust manager but none defined");
|
|
73
66
|
|
|
74
|
-
|
|
75
|
-
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
|
76
|
-
return new java.security.cert.X509Certificate[]{};
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
final TrustManager[] trustAllCerts = new TrustManager[]{x509TrustManager};
|
|
67
|
+
final TrustManager[] trustAllCerts = new TrustManager[]{sharedTrustManager};
|
|
80
68
|
|
|
81
69
|
// Install the all-trusting trust manager
|
|
82
70
|
final SSLContext sslContext = SSLContext.getInstance("SSL");
|
|
@@ -85,7 +73,7 @@ public class ReactNativeBlobUtilUtils {
|
|
|
85
73
|
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
|
86
74
|
|
|
87
75
|
OkHttpClient.Builder builder = client.newBuilder();
|
|
88
|
-
builder.sslSocketFactory(sslSocketFactory,
|
|
76
|
+
builder.sslSocketFactory(sslSocketFactory, sharedTrustManager);
|
|
89
77
|
builder.hostnameVerifier(new HostnameVerifier() {
|
|
90
78
|
@Override
|
|
91
79
|
public boolean verify(String hostname, SSLSession session) {
|
package/index.d.ts
CHANGED
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
A19B48241D98102400E6868A /* ReactNativeBlobUtilProgress.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeBlobUtilProgress.m; sourceTree = "<group>"; };
|
|
49
49
|
A1AAE2971D300E3E0051D11C /* ReactNativeBlobUtilReqBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReactNativeBlobUtilReqBuilder.h; sourceTree = "<group>"; };
|
|
50
50
|
A1AAE2981D300E4D0051D11C /* ReactNativeBlobUtilReqBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReactNativeBlobUtilReqBuilder.m; sourceTree = "<group>"; };
|
|
51
|
-
A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IOS7Polyfill.h; sourceTree = "<group>"; };
|
|
52
51
|
/* End PBXFileReference section */
|
|
53
52
|
|
|
54
53
|
/* Begin PBXFrameworksBuildPhase section */
|
|
@@ -76,7 +75,6 @@
|
|
|
76
75
|
9FD8D3C126F1709A00009F35 /* ReactNativeBlobUtilFileTransformer.h */,
|
|
77
76
|
A19B48241D98102400E6868A /* ReactNativeBlobUtilProgress.m */,
|
|
78
77
|
A19B48231D98100800E6868A /* ReactNativeBlobUtilProgress.h */,
|
|
79
|
-
A1F950181D7E9134002A95A6 /* IOS7Polyfill.h */,
|
|
80
78
|
A1AAE2981D300E4D0051D11C /* ReactNativeBlobUtilReqBuilder.m */,
|
|
81
79
|
A1AAE2971D300E3E0051D11C /* ReactNativeBlobUtilReqBuilder.h */,
|
|
82
80
|
A158F42E1D0539CE006FFD38 /* ReactNativeBlobUtilNetwork.h */,
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
#import "ReactNativeBlobUtilFS.h"
|
|
12
12
|
#import "ReactNativeBlobUtilConst.h"
|
|
13
13
|
#import "ReactNativeBlobUtilFileTransformer.h"
|
|
14
|
-
#import "IOS7Polyfill.h"
|
|
15
14
|
@import AssetsLibrary;
|
|
16
15
|
|
|
17
16
|
#import <CommonCrypto/CommonDigest.h>
|
|
@@ -381,7 +380,7 @@ NSMutableDictionary *fileStreams = nil;
|
|
|
381
380
|
|
|
382
381
|
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
|
|
383
382
|
NSData * content = nil;
|
|
384
|
-
if([encoding
|
|
383
|
+
if([encoding containsString:@"base64"]) {
|
|
385
384
|
content = [[NSData alloc] initWithBase64EncodedString:data options:0];
|
|
386
385
|
}
|
|
387
386
|
else if([encoding isEqualToString:@"uri"]) {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
#import "ReactNativeBlobUtilNetwork.h"
|
|
12
12
|
#import "ReactNativeBlobUtilConst.h"
|
|
13
13
|
#import "ReactNativeBlobUtilFS.h"
|
|
14
|
-
#import "IOS7Polyfill.h"
|
|
15
14
|
|
|
16
15
|
#if __has_include(<React/RCTAssert.h>)
|
|
17
16
|
#import <React/RCTLog.h>
|
|
@@ -151,7 +150,7 @@
|
|
|
151
150
|
|
|
152
151
|
__block NSString * cType = [[self class]getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders];
|
|
153
152
|
// when content-type is application/octet* decode body string using BASE64 decoder
|
|
154
|
-
if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString]
|
|
153
|
+
if([[cType lowercaseString] hasPrefix:@"application/octet"] || [[cType lowercaseString] containsString:@";base64"])
|
|
155
154
|
{
|
|
156
155
|
__block NSString * ncType = [[cType stringByReplacingOccurrencesOfString:@";base64" withString:@""]stringByReplacingOccurrencesOfString:@";BASE64" withString:@""];
|
|
157
156
|
if([mheaders valueForKey:@"content-type"] != nil)
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
#import "ReactNativeBlobUtilFileTransformer.h"
|
|
14
14
|
#import "ReactNativeBlobUtilReqBuilder.h"
|
|
15
15
|
|
|
16
|
-
#import "IOS7Polyfill.h"
|
|
17
16
|
#import <CommonCrypto/CommonDigest.h>
|
|
18
17
|
|
|
19
18
|
|
|
@@ -222,20 +221,20 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
|
|
|
222
221
|
|
|
223
222
|
return;
|
|
224
223
|
} else {
|
|
225
|
-
self.isServerPush = [[respCType lowercaseString]
|
|
224
|
+
self.isServerPush = [[respCType lowercaseString] containsString:@"multipart/x-mixed-replace;"];
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
if(respCType)
|
|
229
228
|
{
|
|
230
229
|
NSArray * extraBlobCTypes = [options objectForKey:CONFIG_EXTRA_BLOB_CTYPE];
|
|
231
230
|
|
|
232
|
-
if ([respCType
|
|
231
|
+
if ([respCType containsString:@"text/"]) {
|
|
233
232
|
respType = @"text";
|
|
234
|
-
} else if ([respCType
|
|
233
|
+
} else if ([respCType containsString:@"application/json"]) {
|
|
235
234
|
respType = @"json";
|
|
236
235
|
} else if(extraBlobCTypes) { // If extra blob content type is not empty, check if response type matches
|
|
237
236
|
for (NSString * substr in extraBlobCTypes) {
|
|
238
|
-
if ([respCType
|
|
237
|
+
if ([respCType containsString:[substr lowercaseString]]) {
|
|
239
238
|
respType = @"blob";
|
|
240
239
|
respFile = YES;
|
|
241
240
|
destPath = [ReactNativeBlobUtilFS getTempPath:taskId withExtension:nil];
|
|
@@ -293,7 +292,7 @@ typedef NS_ENUM(NSUInteger, ResponseFormat) {
|
|
|
293
292
|
|
|
294
293
|
// if not set overwrite in options, defaults to TRUE
|
|
295
294
|
BOOL overwrite = [options valueForKey:@"overwrite"] == nil ? YES : [[options valueForKey:@"overwrite"] boolValue];
|
|
296
|
-
BOOL appendToExistingFile = [destPath
|
|
295
|
+
BOOL appendToExistingFile = [destPath containsString:@"?append=true"];
|
|
297
296
|
|
|
298
297
|
appendToExistingFile = !overwrite;
|
|
299
298
|
|
package/package.json
CHANGED
package/ios/IOS7Polyfill.h
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// IOS7Polyfill.h
|
|
3
|
-
// ReactNativeBlobUtil
|
|
4
|
-
//
|
|
5
|
-
// Created by Ben Hsieh on 2016/9/6.
|
|
6
|
-
// Copyright © 2016年 wkh237.github.io. All rights reserved.
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
#ifndef IOS7Polyfill_h
|
|
10
|
-
#define IOS7Polyfill_h
|
|
11
|
-
|
|
12
|
-
@interface NSString (Contains)
|
|
13
|
-
|
|
14
|
-
- (BOOL)RNFBContainsString:(NSString*)other;
|
|
15
|
-
|
|
16
|
-
@end
|
|
17
|
-
|
|
18
|
-
@implementation NSString (Contains)
|
|
19
|
-
|
|
20
|
-
- (BOOL)RNFBContainsString:(NSString*)other {
|
|
21
|
-
NSRange range = [self rangeOfString:other];
|
|
22
|
-
return range.length != 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@end
|
|
27
|
-
#endif /* IOS7Polyfill_h */
|