123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2015-2020 Threema GmbH
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License, version 3,
- // as published by the Free Software Foundation.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- #import "BlobUploader.h"
- #import "ActivityIndicatorProxy.h"
- #import "Contact.h"
- #import "BlobUtil.h"
- #import "BaseMessage.h"
- #import "ThreemaError.h"
- #import "SSLCAHelper.h"
- #import "NSString+Hex.h"
- #import "BlobUploadDelegate.h"
- #ifdef DEBUG
- static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
- #else
- static const DDLogLevel ddLogLevel = DDLogLevelWarning;
- #endif
- @interface BlobUploader ()
- @property NSMutableData *receivedData;
- @property NSURLConnection *uploadConnection;
- @property id<BlobUploadDelegate> blobUploadDelegate;
- @end
- @implementation BlobUploader
- - (void)startUploadFor:(id<BlobUploadDelegate>)messageProxy {
- _blobUploadDelegate = messageProxy;
-
- [ActivityIndicatorProxy startActivity];
- [_blobUploadDelegate uploadProgress: [NSNumber numberWithFloat:0]];
-
- NSURL *blobUploadUrl = [BlobUtil urlForBlobUpload];
-
- NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:blobUploadUrl cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:kBlobUploadTimeout];
- request.HTTPMethod = @"POST";
-
- NSString *boundary = @"---------------------------Boundary_Line";
- NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
- [request addValue:contentType forHTTPHeaderField: @"Content-Type"];
-
- NSUInteger dataLength = [self totalDataLength];
- NSMutableData *body = [NSMutableData dataWithCapacity: dataLength];
- [body appendData:[[NSString stringWithFormat:@"--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
- [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"blob\"; filename=\"blob.bin\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
- [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
- [body appendData:_data];
- _data = nil; // release memory now
-
- if (_thumbnailData) {
- [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
- [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"blob2\"; filename=\"blob2.bin\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
- [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
- [body appendData:_thumbnailData];
- _thumbnailData = nil; // release memory now
- [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
- } else {
- [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
- }
-
- [request setHTTPBody:body];
- [request addValue:[NSString stringWithFormat:@"%lu", (unsigned long)[body length]] forHTTPHeaderField:@"Content-Length"];
-
- _receivedData = [NSMutableData data];
-
- _uploadConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
- [_uploadConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
- [_uploadConnection start];
- }
- - (NSUInteger)totalDataLength {
- NSUInteger length = _data.length + 1024;
- if (_thumbnailData) {
- length += _thumbnailData.length;
- }
-
- return length;
- }
- #pragma mark - URL connection delegate
- - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
- DDLogVerbose(@"totalBytesWritten: %ld, totalBytesExpectedToWrite: %ld", (long)totalBytesWritten, (long)totalBytesExpectedToWrite);
-
- if ([_blobUploadDelegate uploadShouldCancel]) {
- DDLogWarn(@"Upload cancelled");
-
- [connection cancel];
-
- [_blobUploadDelegate uploadDidCancel];
- return;
- }
-
- [_blobUploadDelegate uploadProgress: [NSNumber numberWithFloat:((float)totalBytesWritten / (float)totalBytesExpectedToWrite)]];
- }
- - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
- DDLogVerbose(@"Request succeeded - received %lu bytes", (unsigned long)[_receivedData length]);
- [ActivityIndicatorProxy stopActivity];
-
- NSString *blobIdHex = [[NSString alloc] initWithData:_receivedData encoding:NSASCIIStringEncoding];
- NSData *blobId = [blobIdHex decodeHex];
-
- if (blobId.length == kBlobIdLen) {
- DDLogVerbose(@"Blob ID: %@", blobId);
- [_blobUploadDelegate uploadSucceededWithBlobIds:@[blobId]];
- } else if (blobId.length == (2*kBlobIdLen)) {
- NSData *blobId1 = [NSData dataWithBytes:blobId.bytes length:kBlobIdLen];
- NSData *blobId2 = [NSData dataWithBytes:(blobId.bytes + kBlobIdLen) length:kBlobIdLen];
-
- DDLogVerbose(@"Blob ID: %@ / %@", blobId1, blobId2);
- [_blobUploadDelegate uploadSucceededWithBlobIds:@[blobId1, blobId2]];
- } else {
- DDLogError(@"Got invalid blob ID from server: %@", blobId);
- [self connection:connection didFailWithError:[ThreemaError threemaError:@"Got invalid blob ID from server"]];
- }
-
- _uploadConnection = nil; // release memory for body etc. now
- }
- - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
-
- DDLogError(@"Connection failed - error %@ %@",
- [error localizedDescription],
- [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
-
- [ActivityIndicatorProxy stopActivity];
-
- if ([_blobUploadDelegate uploadShouldCancel]) {
- DDLogWarn(@"Upload cancelled");
-
- [_blobUploadDelegate uploadDidCancel];
- return;
- }
-
- [_blobUploadDelegate uploadFailed];
-
- _uploadConnection = nil; // release memory for body etc. now
- }
- /* this method ensures that HTTP errors get treated like connection failures etc. as well */
- - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
- [_receivedData setLength:0];
-
- NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
- if (statusCode >= 400)
- {
- [connection cancel]; // stop connecting; no more delegate messages
- NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:
- NSLocalizedString(@"Server returned status code %d",@""),
- statusCode]
- forKey:NSLocalizedDescriptionKey];
- NSError *statusError = [NSError errorWithDomain:NSURLErrorDomain
- code:statusCode
- userInfo:errorInfo];
- [self connection:connection didFailWithError:statusError];
- }
- }
- - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
- [_receivedData appendData:data];
- }
- - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
- return [SSLCAHelper connection:connection canAuthenticateAgainstProtectionSpace:protectionSpace];
- }
- - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
- [SSLCAHelper connection:connection didReceiveAuthenticationChallenge:challenge];
- }
- - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
- return nil;
- }
- @end
|