Download attachments of a message as a zip archive
This commit is contained in:
parent
2a2ebd553e
commit
011fae8a65
3
NEWS
3
NEWS
|
@ -1,6 +1,9 @@
|
||||||
3.2.5 (2016-12-DD)
|
3.2.5 (2016-12-DD)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
New features
|
||||||
|
- [web] download attachments of a message as a zip archive
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
- [web] prevent using localhost on additional IMAP accounts
|
- [web] prevent using localhost on additional IMAP accounts
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
fromIndex: (int) start;
|
fromIndex: (int) start;
|
||||||
- (int) indexOf: (unichar) _c;
|
- (int) indexOf: (unichar) _c;
|
||||||
- (NSString *) decodedHeader;
|
- (NSString *) decodedHeader;
|
||||||
|
- (NSString *) asSafeFilename;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -688,4 +688,26 @@ convertChars (const char *oldString, unsigned int oldLength,
|
||||||
return decodedHeader;
|
return decodedHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *) asSafeFilename
|
||||||
|
{
|
||||||
|
NSRange r;
|
||||||
|
NSMutableString *safeName;
|
||||||
|
|
||||||
|
r = [self rangeOfString: @"\\"
|
||||||
|
options: NSBackwardsSearch];
|
||||||
|
if (r.length > 0)
|
||||||
|
safeName = [NSMutableString stringWithString: [self substringFromIndex: r.location + 1]];
|
||||||
|
else
|
||||||
|
safeName = [NSMutableString stringWithString: self];
|
||||||
|
[safeName replaceString: @"/" withString: @"_"];
|
||||||
|
|
||||||
|
if ([self isEqualToString: @"."])
|
||||||
|
return @"_";
|
||||||
|
|
||||||
|
if ([self isEqualToString: @".."])
|
||||||
|
return @"__";
|
||||||
|
|
||||||
|
return safeName;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -97,7 +97,6 @@
|
||||||
/* attachments */
|
/* attachments */
|
||||||
|
|
||||||
- (NSArray *) fetchAttachmentAttrs;
|
- (NSArray *) fetchAttachmentAttrs;
|
||||||
- (BOOL) isValidAttachmentName: (NSString *) _name;
|
|
||||||
- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name;
|
- (NGMimeBodyPart *) bodyPartForAttachmentWithName: (NSString *) _name;
|
||||||
- (NSString *) pathToAttachmentWithName: (NSString *) _name;
|
- (NSString *) pathToAttachmentWithName: (NSString *) _name;
|
||||||
- (NSException *) saveAttachment: (NSData *) _attach
|
- (NSException *) saveAttachment: (NSData *) _attach
|
||||||
|
|
|
@ -845,59 +845,22 @@ static NSString *userAgent = nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
- (NSArray *) _attachmentBodiesFromPaths: (NSArray *) paths
|
|
||||||
fromResponseFetch: (NSDictionary *) fetch;
|
|
||||||
{
|
|
||||||
NSEnumerator *attachmentKeys;
|
|
||||||
NSMutableArray *bodies;
|
|
||||||
NSString *currentKey;
|
|
||||||
NSDictionary *body;
|
|
||||||
|
|
||||||
bodies = [NSMutableArray array];
|
|
||||||
|
|
||||||
attachmentKeys = [paths objectEnumerator];
|
|
||||||
while ((currentKey = [attachmentKeys nextObject]))
|
|
||||||
{
|
|
||||||
body = [fetch objectForKey: [currentKey lowercaseString]];
|
|
||||||
if (body)
|
|
||||||
[bodies addObject: [body objectForKey: @"data"]];
|
|
||||||
else
|
|
||||||
[bodies addObject: [NSData data]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return bodies;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail
|
- (void) _fetchAttachmentsFromMail: (SOGoMailObject *) sourceMail
|
||||||
{
|
{
|
||||||
unsigned int count, max;
|
unsigned int max, count;
|
||||||
NSArray *parts, *paths, *bodies;
|
NSArray *attachments;
|
||||||
NSData *body;
|
|
||||||
NSDictionary *currentInfo;
|
NSDictionary *currentInfo;
|
||||||
NGHashMap *response;
|
|
||||||
|
|
||||||
parts = [sourceMail fetchFileAttachmentKeys];
|
attachments = [sourceMail fetchFileAttachments];
|
||||||
max = [parts count];
|
max = [attachments count];
|
||||||
if (max > 0)
|
for (count = 0; count < max; count++)
|
||||||
{
|
{
|
||||||
paths = [parts keysWithFormat: @"BODY[%{path}]"];
|
currentInfo = [attachments objectAtIndex: count];
|
||||||
response = [[sourceMail fetchParts: paths] objectForKey: @"RawResponse"];
|
[self saveAttachment: [currentInfo objectForKey: @"body"]
|
||||||
bodies = [self _attachmentBodiesFromPaths: paths
|
withMetadata: currentInfo];
|
||||||
fromResponseFetch: [response objectForKey: @"fetch"]];
|
|
||||||
for (count = 0; count < max; count++)
|
|
||||||
{
|
|
||||||
currentInfo = [parts objectAtIndex: count];
|
|
||||||
body = [[bodies objectAtIndex: count]
|
|
||||||
bodyDataFromEncoding: [currentInfo
|
|
||||||
objectForKey: @"encoding"]];
|
|
||||||
[self saveAttachment: body withMetadata: currentInfo];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1098,12 +1061,6 @@ static NSString *userAgent = nil;
|
||||||
return ma;
|
return ma;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) isValidAttachmentName: (NSString *) filename
|
|
||||||
{
|
|
||||||
return (!([filename rangeOfString: @"/"].length
|
|
||||||
|| [filename isEqualToString: @"."]
|
|
||||||
|| [filename isEqualToString: @".."]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *) pathToAttachmentWithName: (NSString *) _name
|
- (NSString *) pathToAttachmentWithName: (NSString *) _name
|
||||||
{
|
{
|
||||||
|
@ -1113,17 +1070,15 @@ static NSString *userAgent = nil;
|
||||||
return [[self draftFolderPath] stringByAppendingPathComponent:_name];
|
return [[self draftFolderPath] stringByAppendingPathComponent:_name];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSException *) invalidAttachmentNameError: (NSString *) _name
|
|
||||||
{
|
|
||||||
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
|
|
||||||
reason: @"Invalid attachment name!"];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write attachment file to the spool directory of the draft and write a dot
|
||||||
|
* file with its mime type.
|
||||||
|
*/
|
||||||
- (NSException *) saveAttachment: (NSData *) _attach
|
- (NSException *) saveAttachment: (NSData *) _attach
|
||||||
withMetadata: (NSDictionary *) metadata
|
withMetadata: (NSDictionary *) metadata
|
||||||
{
|
{
|
||||||
NSString *p, *pmime, *name, *mimeType;
|
NSString *p, *pmime, *name, *mimeType;
|
||||||
NSRange r;
|
|
||||||
|
|
||||||
if (![_attach isNotNull]) {
|
if (![_attach isNotNull]) {
|
||||||
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
|
return [NSException exceptionWithHTTPStatus:400 /* Bad Request */
|
||||||
|
@ -1135,15 +1090,7 @@ static NSString *userAgent = nil;
|
||||||
reason: @"Could not create folder for draft!"];
|
reason: @"Could not create folder for draft!"];
|
||||||
}
|
}
|
||||||
|
|
||||||
name = [metadata objectForKey: @"filename"];
|
name = [[metadata objectForKey: @"filename"] asSafeFilename];
|
||||||
r = [name rangeOfString: @"\\"
|
|
||||||
options: NSBackwardsSearch];
|
|
||||||
if (r.length > 0)
|
|
||||||
name = [name substringFromIndex: r.location + 1];
|
|
||||||
|
|
||||||
if (![self isValidAttachmentName: name])
|
|
||||||
return [self invalidAttachmentNameError: name];
|
|
||||||
|
|
||||||
p = [self pathToAttachmentWithName: name];
|
p = [self pathToAttachmentWithName: name];
|
||||||
if (![_attach writeToFile: p atomically: YES])
|
if (![_attach writeToFile: p atomically: YES])
|
||||||
{
|
{
|
||||||
|
@ -1173,19 +1120,12 @@ static NSString *userAgent = nil;
|
||||||
NSException *error;
|
NSException *error;
|
||||||
|
|
||||||
error = nil;
|
error = nil;
|
||||||
|
fm = [NSFileManager defaultManager];
|
||||||
if ([self isValidAttachmentName:_name])
|
p = [self pathToAttachmentWithName: [_name asSafeFilename]];
|
||||||
{
|
if ([fm fileExistsAtPath: p])
|
||||||
fm = [NSFileManager defaultManager];
|
if (![fm removeFileAtPath: p handler: nil])
|
||||||
p = [self pathToAttachmentWithName:_name];
|
error = [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
||||||
if ([fm fileExistsAtPath: p])
|
reason: @"Could not delete attachment from draft!"];
|
||||||
if (![fm removeFileAtPath: p handler: nil])
|
|
||||||
error
|
|
||||||
= [NSException exceptionWithHTTPStatus: 500 /* Server Error */
|
|
||||||
reason: @"Could not delete attachment from draft!"];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
error = [self invalidAttachmentNameError:_name];
|
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -1288,8 +1228,7 @@ static NSString *userAgent = nil;
|
||||||
NSString *s, *p;
|
NSString *s, *p;
|
||||||
NSData *mimeData;
|
NSData *mimeData;
|
||||||
|
|
||||||
p = [self pathToAttachmentWithName:
|
p = [self pathToAttachmentWithName: [NSString stringWithFormat: @".%@.mime", _name]];
|
||||||
[NSString stringWithFormat: @".%@.mime", _name]];
|
|
||||||
mimeData = [NSData dataWithContentsOfFile: p];
|
mimeData = [NSData dataWithContentsOfFile: p];
|
||||||
if (mimeData)
|
if (mimeData)
|
||||||
{
|
{
|
||||||
|
@ -1308,20 +1247,18 @@ static NSString *userAgent = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name
|
- (NSString *) contentDispositionForAttachmentWithName: (NSString *) _name
|
||||||
|
andContentType: (NSString *) _type
|
||||||
{
|
{
|
||||||
NSString *type;
|
|
||||||
NSString *cdtype;
|
NSString *cdtype;
|
||||||
NSString *cd;
|
NSString *cd;
|
||||||
SOGoDomainDefaults *dd;
|
SOGoDomainDefaults *dd;
|
||||||
|
|
||||||
type = [self contentTypeForAttachmentWithName:_name];
|
if ([_type hasPrefix: @"text/"])
|
||||||
|
|
||||||
if ([type hasPrefix: @"text/"])
|
|
||||||
{
|
{
|
||||||
dd = [[context activeUser] domainDefaults];
|
dd = [[context activeUser] domainDefaults];
|
||||||
cdtype = [dd mailAttachTextDocumentsInline] ? @"inline" : @"attachment";
|
cdtype = [dd mailAttachTextDocumentsInline] ? @"inline" : @"attachment";
|
||||||
}
|
}
|
||||||
else if ([type hasPrefix: @"image/"] || [type hasPrefix: @"message"])
|
else if ([_type hasPrefix: @"image/"] || [_type hasPrefix: @"message"])
|
||||||
cdtype = @"inline";
|
cdtype = @"inline";
|
||||||
else
|
else
|
||||||
cdtype = @"attachment";
|
cdtype = @"attachment";
|
||||||
|
@ -1369,7 +1306,7 @@ static NSString *userAgent = nil;
|
||||||
else if ([s hasPrefix: @"message/rfc822"])
|
else if ([s hasPrefix: @"message/rfc822"])
|
||||||
attachAsRFC822 = YES;
|
attachAsRFC822 = YES;
|
||||||
}
|
}
|
||||||
if ((s = [self contentDispositionForAttachmentWithName: _name]))
|
if ((s = [self contentDispositionForAttachmentWithName: _name andContentType: s]))
|
||||||
{
|
{
|
||||||
NGMimeContentDispositionHeaderField *o;
|
NGMimeContentDispositionHeaderField *o;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/*
|
/*
|
||||||
|
Copyright (C) 2009-2016 Inverse inc.
|
||||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||||
|
|
||||||
This file is part of OpenGroupware.org.
|
This file is part of OpenGroupware.org.
|
||||||
|
@ -48,6 +49,8 @@
|
||||||
@class NGImap4Envelope;
|
@class NGImap4Envelope;
|
||||||
@class NGImap4EnvelopeAddress;
|
@class NGImap4EnvelopeAddress;
|
||||||
|
|
||||||
|
@class WOResponse;
|
||||||
|
|
||||||
NSArray *SOGoMailCoreInfoKeys;
|
NSArray *SOGoMailCoreInfoKeys;
|
||||||
|
|
||||||
@interface SOGoMailObject : SOGoMailBaseObject
|
@interface SOGoMailObject : SOGoMailBaseObject
|
||||||
|
@ -107,6 +110,8 @@ NSArray *SOGoMailCoreInfoKeys;
|
||||||
- (BOOL) hasAttachment;
|
- (BOOL) hasAttachment;
|
||||||
- (NSDictionary *) fetchFileAttachmentIds;
|
- (NSDictionary *) fetchFileAttachmentIds;
|
||||||
- (NSArray *) fetchFileAttachmentKeys;
|
- (NSArray *) fetchFileAttachmentKeys;
|
||||||
|
- (NSArray *) fetchFileAttachments;
|
||||||
|
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName;
|
||||||
|
|
||||||
/* flags */
|
/* flags */
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright (C) 2007-2014 Inverse inc.
|
Copyright (C) 2007-2016 Inverse inc.
|
||||||
Copyright (C) 2004-2005 SKYRIX Software AG
|
Copyright (C) 2004-2005 SKYRIX Software AG
|
||||||
|
|
||||||
This file is part of SOGo.
|
This file is part of SOGo.
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
02111-1307, USA.
|
02111-1307, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/NSTask.h>
|
||||||
#import <Foundation/NSURL.h>
|
#import <Foundation/NSURL.h>
|
||||||
#import <Foundation/NSValue.h>
|
#import <Foundation/NSValue.h>
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
#import <NGObjWeb/WORequest.h>
|
#import <NGObjWeb/WORequest.h>
|
||||||
#import <NGObjWeb/NSException+HTTP.h>
|
#import <NGObjWeb/NSException+HTTP.h>
|
||||||
#import <NGExtensions/NGHashMap.h>
|
#import <NGExtensions/NGHashMap.h>
|
||||||
|
#import <NGExtensions/NSFileManager+Extensions.h>
|
||||||
#import <NGExtensions/NSNull+misc.h>
|
#import <NGExtensions/NSNull+misc.h>
|
||||||
#import <NGExtensions/NSObject+Logs.h>
|
#import <NGExtensions/NSObject+Logs.h>
|
||||||
#import <NGExtensions/NSString+Encoding.h>
|
#import <NGExtensions/NSString+Encoding.h>
|
||||||
|
@ -38,7 +40,9 @@
|
||||||
|
|
||||||
#import <SOGo/NSArray+Utilities.h>
|
#import <SOGo/NSArray+Utilities.h>
|
||||||
#import <SOGo/NSDictionary+Utilities.h>
|
#import <SOGo/NSDictionary+Utilities.h>
|
||||||
|
#import <SOGo/NSString+Utilities.h>
|
||||||
#import <SOGo/SOGoPermissions.h>
|
#import <SOGo/SOGoPermissions.h>
|
||||||
|
#import <SOGo/SOGoSystemDefaults.h>
|
||||||
#import <SOGo/SOGoUser.h>
|
#import <SOGo/SOGoUser.h>
|
||||||
#import <SOGo/SOGoUserDefaults.h>
|
#import <SOGo/SOGoUserDefaults.h>
|
||||||
#import <SOGo/NSCalendarDate+SOGo.h>
|
#import <SOGo/NSCalendarDate+SOGo.h>
|
||||||
|
@ -913,6 +917,160 @@ static BOOL debugSoParts = NO;
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of dictionaries with the following keys:
|
||||||
|
* - encoding
|
||||||
|
* - filename
|
||||||
|
* - mimetype
|
||||||
|
* - path
|
||||||
|
* - size
|
||||||
|
* - url
|
||||||
|
* - urlAsAttachment
|
||||||
|
* - body (NSData)
|
||||||
|
*/
|
||||||
|
- (NSArray *) fetchFileAttachments
|
||||||
|
{
|
||||||
|
unsigned int count, max;
|
||||||
|
NGHashMap *response;
|
||||||
|
NSArray *parts, *paths; //, *bodies;
|
||||||
|
NSData *body;
|
||||||
|
NSDictionary *fetch, *currentInfo, *currentBody;
|
||||||
|
NSMutableArray *attachments;
|
||||||
|
NSMutableDictionary *currentAttachment;
|
||||||
|
NSString *currentPath;
|
||||||
|
|
||||||
|
parts = [self fetchFileAttachmentKeys];
|
||||||
|
max = [parts count];
|
||||||
|
attachments = [NSMutableArray arrayWithCapacity: max];
|
||||||
|
if (max > 0)
|
||||||
|
{
|
||||||
|
paths = [parts keysWithFormat: @"BODY[%{path}]"];
|
||||||
|
response = [[self fetchParts: paths] objectForKey: @"RawResponse"];
|
||||||
|
fetch = [response objectForKey: @"fetch"];
|
||||||
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
currentInfo = [parts objectAtIndex: count];
|
||||||
|
currentPath = [[paths objectAtIndex: count] lowercaseString];
|
||||||
|
currentBody = [fetch objectForKey: currentPath];
|
||||||
|
|
||||||
|
if (currentBody)
|
||||||
|
{
|
||||||
|
body = [currentBody objectForKey: @"data"];
|
||||||
|
body = [body bodyDataFromEncoding: [currentInfo objectForKey: @"encoding"]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
body = [NSData data];
|
||||||
|
|
||||||
|
currentAttachment = [NSMutableDictionary dictionaryWithDictionary: currentInfo];
|
||||||
|
[currentAttachment setObject: body forKey: @"body"];
|
||||||
|
[attachments addObject: currentAttachment];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (WOResponse *) archiveAllFilesinArchiveNamed: (NSString *) archiveName
|
||||||
|
{
|
||||||
|
NSArray *attachments;
|
||||||
|
NSData *body, *zipContent;
|
||||||
|
NSDictionary *currentAttachment;
|
||||||
|
NSException *error;
|
||||||
|
NSFileManager *fm;
|
||||||
|
NSMutableArray *zipTaskArguments;
|
||||||
|
NSString *spoolPath, *name, *fileName, *baseName, *extension, *zipPath, *qpFileName;
|
||||||
|
NSTask *zipTask;
|
||||||
|
SOGoMailFolder *folder;
|
||||||
|
WOResponse *response;
|
||||||
|
unsigned int max, count;
|
||||||
|
|
||||||
|
if (!archiveName)
|
||||||
|
archiveName = @"attachments.zip";
|
||||||
|
|
||||||
|
folder = [self container];
|
||||||
|
spoolPath = [folder userSpoolFolderPath];
|
||||||
|
|
||||||
|
if (![folder ensureSpoolFolderPath])
|
||||||
|
{
|
||||||
|
[self errorWithFormat: @"spool directory '%@' doesn't exist", spoolPath];
|
||||||
|
error = [NSException exceptionWithHTTPStatus: 500
|
||||||
|
reason: @"spool directory does not exist"];
|
||||||
|
return (WOResponse *)error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare execution of zip
|
||||||
|
zipPath = [[SOGoSystemDefaults sharedSystemDefaults] zipPath];
|
||||||
|
fm = [NSFileManager defaultManager];
|
||||||
|
if (![fm fileExistsAtPath: zipPath])
|
||||||
|
{
|
||||||
|
error = [NSException exceptionWithHTTPStatus: 500
|
||||||
|
reason: @"zip not available"];
|
||||||
|
return (WOResponse *)error;
|
||||||
|
}
|
||||||
|
|
||||||
|
zipTask = [[NSTask alloc] init];
|
||||||
|
[zipTask setCurrentDirectoryPath: spoolPath];
|
||||||
|
[zipTask setLaunchPath: zipPath];
|
||||||
|
|
||||||
|
zipTaskArguments = [NSMutableArray arrayWithObjects: nil];
|
||||||
|
[zipTaskArguments addObject: @"attachments.zip"];
|
||||||
|
|
||||||
|
// Fetch attachments and write them on disk
|
||||||
|
attachments = [self fetchFileAttachments];
|
||||||
|
max = [attachments count];
|
||||||
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
currentAttachment = [attachments objectAtIndex: count];
|
||||||
|
body = [currentAttachment objectForKey: @"body"];
|
||||||
|
name = [[currentAttachment objectForKey: @"filename"] asSafeFilename];
|
||||||
|
|
||||||
|
fileName = [NSString stringWithFormat:@"%@/%@", spoolPath, name];
|
||||||
|
[body writeToFile: fileName atomically: YES];
|
||||||
|
|
||||||
|
[zipTaskArguments addObject: name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zip files
|
||||||
|
[zipTask setArguments: zipTaskArguments];
|
||||||
|
[zipTask launch];
|
||||||
|
[zipTask waitUntilExit];
|
||||||
|
[zipTask release];
|
||||||
|
zipContent = [[NSData alloc] initWithContentsOfFile:
|
||||||
|
[NSString stringWithFormat: @"%@/attachments.zip", spoolPath]];
|
||||||
|
|
||||||
|
// Delete attachments from disk
|
||||||
|
max = [zipTaskArguments count];
|
||||||
|
for (count = 0; count < max; count++)
|
||||||
|
{
|
||||||
|
fileName = [zipTaskArguments objectAtIndex: count];
|
||||||
|
[fm removeFileAtPath:
|
||||||
|
[NSString stringWithFormat: @"%@/%@", spoolPath, fileName] handler: nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare response
|
||||||
|
response = [context response];
|
||||||
|
baseName = [archiveName stringByDeletingPathExtension];
|
||||||
|
extension = [archiveName pathExtension];
|
||||||
|
if ([extension length] > 0)
|
||||||
|
extension = [@"." stringByAppendingString: extension];
|
||||||
|
else
|
||||||
|
extension = @"";
|
||||||
|
|
||||||
|
qpFileName = [NSString stringWithFormat: @"%@%@",
|
||||||
|
[baseName asQPSubjectString: @"utf-8"], extension];
|
||||||
|
[response setHeader: [NSString stringWithFormat: @"application/zip;"
|
||||||
|
@" name=\"%@\"", qpFileName]
|
||||||
|
forKey: @"content-type"];
|
||||||
|
[response setHeader: [NSString stringWithFormat: @"attachment; filename=\"%@\"",
|
||||||
|
qpFileName]
|
||||||
|
forKey: @"Content-Disposition"];
|
||||||
|
[response setContent: zipContent];
|
||||||
|
|
||||||
|
[zipContent release];
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
/* convert parts to strings */
|
/* convert parts to strings */
|
||||||
- (NSString *) stringForData: (NSData *) _data
|
- (NSString *) stringForData: (NSData *) _data
|
||||||
partInfo: (NSDictionary *) _info
|
partInfo: (NSDictionary *) _info
|
||||||
|
|
|
@ -217,6 +217,12 @@
|
||||||
/* Message view "more" menu: create a task from message */
|
/* Message view "more" menu: create a task from message */
|
||||||
"Convert To Task" = "Convert To Task";
|
"Convert To Task" = "Convert To Task";
|
||||||
|
|
||||||
|
/* Message view "more" menu: download all attachments as a zip archive */
|
||||||
|
"Download all attachments" = "Download all attachments";
|
||||||
|
|
||||||
|
/* Filename prefix when downloading all attachments as a zip archive */
|
||||||
|
"attachments" = "attachments";
|
||||||
|
|
||||||
"Print..." = "Print...";
|
"Print..." = "Print...";
|
||||||
"Delete Message" = "Delete Message";
|
"Delete Message" = "Delete Message";
|
||||||
"Delete Selected Messages" = "Delete Selected Messages";
|
"Delete Selected Messages" = "Delete Selected Messages";
|
||||||
|
|
|
@ -312,6 +312,18 @@ static NSString *mailETag = nil;
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (id <WOActionResults>) archiveAttachmentsAction
|
||||||
|
{
|
||||||
|
NSString *name;
|
||||||
|
SOGoMailObject *co;
|
||||||
|
|
||||||
|
co = [self clientObject];
|
||||||
|
name = [NSString stringWithFormat: @"%@-%@.zip",
|
||||||
|
[self labelForKey: @"attachments"], [co nameInContainer]];
|
||||||
|
|
||||||
|
return [co archiveAllFilesinArchiveNamed: name];
|
||||||
|
}
|
||||||
|
|
||||||
/* MDN */
|
/* MDN */
|
||||||
|
|
||||||
- (BOOL) _userHasEMail: (NSString *) email
|
- (BOOL) _userHasEMail: (NSString *) email
|
||||||
|
|
|
@ -244,6 +244,11 @@
|
||||||
pageName = "UIxMailView";
|
pageName = "UIxMailView";
|
||||||
actionName = "sendMDN";
|
actionName = "sendMDN";
|
||||||
};
|
};
|
||||||
|
archiveAttachments = {
|
||||||
|
protectedBy = "View";
|
||||||
|
pageName = "UIxMailView";
|
||||||
|
actionName = "archiveAttachments";
|
||||||
|
};
|
||||||
viewsource = {
|
viewsource = {
|
||||||
protectedBy = "View";
|
protectedBy = "View";
|
||||||
actionClass = "UIxMailSourceView";
|
actionClass = "UIxMailSourceView";
|
||||||
|
|
|
@ -84,10 +84,16 @@
|
||||||
</md-menu-item>
|
</md-menu-item>
|
||||||
<md-menu-item>
|
<md-menu-item>
|
||||||
<md-button label:aria-label="Save As..."
|
<md-button label:aria-label="Save As..."
|
||||||
ng-click="viewer.message.saveMessage()">
|
ng-click="viewer.message.download()">
|
||||||
<var:string label:value="Save As..."/>
|
<var:string label:value="Save As..."/>
|
||||||
</md-button>
|
</md-button>
|
||||||
</md-menu-item>
|
</md-menu-item>
|
||||||
|
<md-menu-item ng-show="::viewer.message.attachmentAttrs.length">
|
||||||
|
<md-button label:aria-label="Download all attachments"
|
||||||
|
ng-click="viewer.message.downloadAttachments()">
|
||||||
|
<var:string label:value="Download all attachments"/>
|
||||||
|
</md-button>
|
||||||
|
</md-menu-item>
|
||||||
<md-menu-item>
|
<md-menu-item>
|
||||||
<md-button label:aria-label="View Message Source"
|
<md-button label:aria-label="View Message Source"
|
||||||
ng-click="viewer.toggleRawSource($event)">
|
ng-click="viewer.toggleRawSource($event)">
|
||||||
|
|
|
@ -192,14 +192,21 @@
|
||||||
},
|
},
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
cache: false,
|
cache: false,
|
||||||
transformResponse: function (data, headers) {
|
transformResponse: function (data, headers, status) {
|
||||||
var fileName, result, blob = null;
|
var fileName, result, blob = null;
|
||||||
|
|
||||||
|
if (status < 200 || status > 299) {
|
||||||
|
throw new Error('Bad gateway');
|
||||||
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
blob = new Blob([data], { type: type });
|
blob = new Blob([data], { type: type });
|
||||||
}
|
}
|
||||||
fileName = getFileNameFromHeader(headers('content-disposition'));
|
if (options && options.filename) {
|
||||||
|
fileName = options.filename;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getFileNameFromHeader(headers('content-disposition'));
|
||||||
|
}
|
||||||
if (!saveAs) {
|
if (!saveAs) {
|
||||||
throw new Error('To use Resource.download, FileSaver.js must be loaded.');
|
throw new Error('To use Resource.download, FileSaver.js must be loaded.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -713,14 +713,19 @@
|
||||||
* @returns a promise of the HTTP operation
|
* @returns a promise of the HTTP operation
|
||||||
*/
|
*/
|
||||||
AddressBook.prototype.exportCards = function(selectedOnly) {
|
AddressBook.prototype.exportCards = function(selectedOnly) {
|
||||||
var selectedUIDs;
|
var data = null, options, selectedCards;
|
||||||
|
|
||||||
|
options = {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
filename: this.name + '.ldif'
|
||||||
|
};
|
||||||
|
|
||||||
if (selectedOnly) {
|
if (selectedOnly) {
|
||||||
var selectedCards = _.filter(this.$cards, function(card) { return card.selected; });
|
selectedCards = _.filter(this.$cards, function(card) { return card.selected; });
|
||||||
selectedUIDs = _.map(selectedCards, 'id');
|
data = { uids: _.map(selectedCards, 'id') };
|
||||||
}
|
}
|
||||||
|
|
||||||
return AddressBook.$$resource.download(this.id, 'export', (angular.isDefined(selectedUIDs) ? {uids: selectedUIDs} : null), {type: 'application/octet-stream'});
|
return AddressBook.$$resource.download(this.id, 'export', data, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -269,11 +269,15 @@
|
||||||
* @returns a promise of the HTTP operation
|
* @returns a promise of the HTTP operation
|
||||||
*/
|
*/
|
||||||
Card.prototype.export = function() {
|
Card.prototype.export = function() {
|
||||||
var selectedIDs;
|
var data, options;
|
||||||
|
|
||||||
selectedIDs = [ this.id ];
|
data = { uids: [ this.id ] };
|
||||||
|
options = {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
filename: this.$$fullname + '.ldif'
|
||||||
|
};
|
||||||
|
|
||||||
return Card.$$resource.download(this.pid, 'export', {uids: selectedIDs}, {type: 'application/octet-stream'});
|
return Card.$$resource.download(this.pid, 'export', data, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
Card.prototype.$fullname = function(options) {
|
Card.prototype.$fullname = function(options) {
|
||||||
|
|
|
@ -598,10 +598,12 @@
|
||||||
* @returns a promise of the HTTP operation
|
* @returns a promise of the HTTP operation
|
||||||
*/
|
*/
|
||||||
Mailbox.prototype.saveSelectedMessages = function() {
|
Mailbox.prototype.saveSelectedMessages = function() {
|
||||||
var selectedMessages, selectedUIDs;
|
var data, options, selectedMessages, selectedUIDs;
|
||||||
|
|
||||||
selectedMessages = _.filter(this.$messages, function(message) { return message.selected; });
|
selectedMessages = _.filter(this.$messages, function(message) { return message.selected; });
|
||||||
selectedUIDs = _.map(selectedMessages, 'uid');
|
selectedUIDs = _.map(selectedMessages, 'uid');
|
||||||
|
data = { uids: selectedUIDs };
|
||||||
|
options = { filename: l('Saved Messages.zip') };
|
||||||
|
|
||||||
return Mailbox.$$resource.download(this.id, 'saveMessages', {uids: selectedUIDs});
|
return Mailbox.$$resource.download(this.id, 'saveMessages', {uids: selectedUIDs});
|
||||||
};
|
};
|
||||||
|
@ -613,7 +615,11 @@
|
||||||
* @returns a promise of the HTTP operation
|
* @returns a promise of the HTTP operation
|
||||||
*/
|
*/
|
||||||
Mailbox.prototype.exportFolder = function() {
|
Mailbox.prototype.exportFolder = function() {
|
||||||
return Mailbox.$$resource.download(this.id, 'exportFolder');
|
var options;
|
||||||
|
|
||||||
|
options = { filename: this.name + '.zip' };
|
||||||
|
|
||||||
|
return Mailbox.$$resource.download(this.id, 'exportFolder', null, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -708,17 +708,32 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @function saveMessage
|
* @function download
|
||||||
* @memberof Message.prototype
|
* @memberof Message.prototype
|
||||||
* @desc Download the current message
|
* @desc Download the current message
|
||||||
* @returns a promise of the HTTP operation
|
* @returns a promise of the HTTP operation
|
||||||
*/
|
*/
|
||||||
Message.prototype.saveMessage = function() {
|
Message.prototype.download = function() {
|
||||||
var selectedUIDs;
|
var data, options;
|
||||||
|
|
||||||
selectedUIDs = [ this.uid ];
|
data = { uids: [this.uid] };
|
||||||
|
options = { filename: this.subject + '.zip' };
|
||||||
|
|
||||||
return Message.$$resource.download(this.$mailbox.id, 'saveMessages', {uids: selectedUIDs});
|
return Message.$$resource.download(this.$mailbox.id, 'saveMessages', data, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function downloadAttachments
|
||||||
|
* @memberof Message.prototype
|
||||||
|
* @desc Download an archive of all attachments
|
||||||
|
* @returns a promise of the HTTP operation
|
||||||
|
*/
|
||||||
|
Message.prototype.downloadAttachments = function() {
|
||||||
|
var options;
|
||||||
|
|
||||||
|
options = { filename: l('attachments') + "-" + this.uid + ".zip" };
|
||||||
|
|
||||||
|
return Message.$$resource.download(this.$absolutePath(), 'archiveAttachments', null, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -530,7 +530,14 @@
|
||||||
* @returns a promise of the HTTP operation
|
* @returns a promise of the HTTP operation
|
||||||
*/
|
*/
|
||||||
Calendar.prototype.export = function() {
|
Calendar.prototype.export = function() {
|
||||||
return Calendar.$$resource.download(this.id + '.ics', 'export', null, {type: 'application/octet-stream'});
|
var options;
|
||||||
|
|
||||||
|
options = {
|
||||||
|
type: 'application/octet-stream',
|
||||||
|
filename: this.name + '.ics'
|
||||||
|
};
|
||||||
|
|
||||||
|
return Calendar.$$resource.download(this.id + '.ics', 'export', null, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue