Compare commits

...
14 Commits
Author SHA1 Message Date
Hivert Quentin 8f03432a55 chore(release): 5.12.2 2025-06-25 09:34:32 +02:00
Hivert Quentin 9649ae345f i18n(pt): Update Portuguese translations 2025-06-25 08:52:35 +02:00
Hivert Quentin e9c03d3cf7 i18n(tr_TR): Update TurkishTurkey translations 2025-06-25 08:52:35 +02:00
Hivert Quentin 031e117cba i18n(nl): Update Dutch translations 2025-06-25 08:52:34 +02:00
Hivert Quentin dabad1d0cf fix(api): add handler for internal error 2025-06-24 10:20:12 +02:00
Hivert Quentin c1b6388260 chore(packaging): fix rhel packaging 2025-06-24 09:30:28 +02:00
Hivert Quentin bc6b849d66 chore(js/css): update generated files 2025-06-24 01:00:36 -04:00
Hivert Quentin 51f1521dba feat(api): add endpoint for caldav/cardav url 2025-06-23 17:14:39 +02:00
Hivert Quentin 516606baff fix(session): allow password/token to be longer than userkey 2025-06-23 09:27:51 +02:00
Hivert Quentin a782424a30 feat(openid): swicth to libcurl for http request
It was using an internal library before and there were too much errors due to incomplete http's protocol implementation
2025-06-23 09:24:02 +02:00
Hivert Quentin f53d7b73db fix(sieve): add requirements for notify when doing a filter 2025-06-19 09:40:58 +02:00
Hivert Quentin 8766b7c6b3 fix(calendar): properly evalute last occurance freebusy 2025-06-17 11:31:00 +02:00
Hivert Quentin 8a907737de fix(view): don't hide the option mailcomposer in small screen 2025-06-16 11:14:08 +02:00
Hivert Quentin 1a6f2825b6 chore(foc): put a link to the community version 2025-06-11 13:52:36 +02:00
53 changed files with 1597 additions and 242 deletions

No files matched your search

+1
View File
@@ -10,6 +10,7 @@
*/obj/
.scss-lint-config.yml_
ActiveSync/ActiveSync.SOGo
API/API.SOGo
Documentation/*.docbook
Documentation/*.pdf
Documentation/*.html
+28
View File
@@ -0,0 +1,28 @@
# GNUstep makefile
include common.make
BUNDLE_NAME = API
API_PRINCIPAL_CLASS = SOGoAPIProduct
API_OBJC_FILES = \
SOGoAPIProduct.m \
SOGoAPI.m \
SOGoAPIVersion.m \
SOGoAPIUserFolder.m \
SOGoAPIDispatcher.m
API_RESOURCE_FILES += \
product.plist
API_LANGUAGES = $(SOGO_LANGUAGES)
API_LOCALIZED_RESOURCE_FILES = Localizable.strings
ADDITIONAL_INCLUDE_DIRS += -I../SOPE/ -I../SoObjects/
-include GNUmakefile.preamble
include $(GNUSTEP_MAKEFILES)/bundle.make
-include GNUmakefile.postamble
+16
View File
@@ -0,0 +1,16 @@
# compile settings
ADDITIONAL_CPPFLAGS += \
-DSOGO_MAJOR_VERSION=$(MAJOR_VERSION) \
-DSOGO_MINOR_VERSION=$(MINOR_VERSION) \
-DSOGO_PATCH_VERSION=$(SUBMINOR_VERSION) \
-DSOGO_LIBDIR="@\"$(SOGO_LIBDIR)\""
ADDITIONAL_INCLUDE_DIRS += \
-D_GNU_SOURCE -I../SOPE/ -I../SoObjects/
ADDITIONAL_LIB_DIRS += \
-L../SoObjects/SOGo/SOGo.framework/sogo -lSOGo \
-L../SOPE/GDLContentStore/$(GNUSTEP_OBJ_DIR)/ -lGDLContentStore \
-L../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ -lNGCards \
-lEOControl -lNGStreams -lNGMime -lNGExtensions -lNGObjWeb -lWEExtensions
+35
View File
@@ -0,0 +1,35 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOPE.
SOPE is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOPE 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 Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOPE; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/Foundation.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSArray.h>
@class WOContext;
@interface SOGoAPI : NSObject
- (NSArray *) methodAllowed;
- (BOOL) needAuth;
- (NSArray *) paramNeeded;
- (NSDictionary *) action: (WOContext*) ctx withParam: (NSDictionary *) param;
@end
+52
View File
@@ -0,0 +1,52 @@
/*
Copyright (C) todo...
*/
#import <SOGoAPI.h>
@implementation SOGoAPI
- (id) init
{
[super init];
return self;
}
- (void) dealloc
{
[super dealloc];
}
- (NSArray *) methodAllowed
{
NSArray *result;
result = [NSArray arrayWithObjects:@"GET",nil];
return result;
}
- (BOOL) needAuth
{
return YES;
}
- (NSArray *) paramNeeded
{
return nil;
}
- (NSDictionary *) action: (WOContext*) ctx withParam: (NSDictionary *) param
{
NSDictionary* result;
result = [[NSDictionary alloc] initWithObjectsAndKeys:
@"API not defined", @"error",
nil];
[result autorelease];
return result;
}
@end /* SOGoAPI */
View File
Whitespace-only changes.
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright (c) 2014-2015, Inverse inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Inverse inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/NSObject.h>
#import <SOGo/WORequest+SOGo.h>
#import <SOGo/WOResponse+SOGo.h>
@class NSCalendarDate;
@class NSException;
@class NSMutableDictionary;
@class NSURL;
@class NSNumber;
static volatile BOOL apiShouldTerminate = NO;
@interface SOGoAPIDispatcher : NSObject
{
id context;
BOOL debugOn;
}
- (NSException *) dispatchRequest: (WORequest*) theRequest
inResponse: (WOResponse*) theResponse
context: (id) theContext;
@end
+399
View File
@@ -0,0 +1,399 @@
/*
Copyright (c) 2014, Inverse inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Inverse inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "SOGoAPIDispatcher.h"
#import <Foundation/NSAutoreleasePool.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WOCoreApplication.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSString+Encoding.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUser.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/WORequest+SOGo.h>
#import <SOGo/WOResponse+SOGo.h>
#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoOpenIdSession.h>
void handle_api_terminate(int signum)
{
NSLog(@"Forcing termination of API loop.");
apiShouldTerminate = YES;
[[WOCoreApplication application] terminateAfterTimeInterval: 1];
}
@implementation SOGoAPIDispatcher
- (id) init
{
[super init];
debugOn = [[SOGoSystemDefaults sharedSystemDefaults] apiDebugEnabled];
apiShouldTerminate = NO;
signal(SIGTERM, handle_api_terminate);
return self;
}
- (void) dealloc
{
[super dealloc];
}
- (void) _sendAPIErrorResponse: (WOResponse* ) response
withMessage: (NSString *) message
withStatus: (unsigned int) status
{
NSDictionary *msg;
msg = [[NSDictionary alloc] initWithObjectsAndKeys:
message, @"error",
nil];
[response setStatus: status];
[response setContent: [msg jsonRepresentation]];
}
- (NSString *) _getActionFromUri: (NSString *) _uri
{
/*
_uri always start with /SOGo/SOGoAPI
full _uri example /SOGo/SOGoAPI/Action/subaction1?param1&param2
*/
NSString *uriWithoutParams, *action, *prefix;
NSArray *uriSplits;
prefix = @"/SOGo/SOGoAPI";
action = @"";
uriWithoutParams = [_uri urlWithoutParameters];
if(![uriWithoutParams hasPrefix: prefix])
{
[self errorWithFormat: @"Uri for API request does not start with /SOGo/SOGoAPI: %@", uriWithoutParams];
return nil;
}
else
{
uriWithoutParams = [uriWithoutParams substringFromIndex:[prefix length]];
}
//remove first and last '/'' if needed
if([uriWithoutParams hasPrefix: @"/"])
{
uriWithoutParams = [uriWithoutParams substringFromIndex:1];
}
if([uriWithoutParams hasSuffix: @"/"])
{
uriWithoutParams = [uriWithoutParams substringToIndex:([uriWithoutParams length] -1)];
}
if([uriWithoutParams length] == 0)
{
[self warnWithFormat: @"Uri for API request has no action, make Version instead: %@", uriWithoutParams];
return @"SOGoAPIVersion";
}
else
{
uriSplits = [uriWithoutParams componentsSeparatedByString: @"/"];
action = [@"SOGoAPI" stringByAppendingString: [uriSplits objectAtIndex: 0]];
if(debugOn)
[self logWithFormat: @"API request, action made is %@", action];
}
return action;
}
- (NSDictionary *) _authBasicCheck: (NSString *) auth
{
NSDictionary *user;
NSRange rng;
NSString *decodeCred, *domain, *login, *pwd;
SOGoUserManager *lm;
SOGoPasswordPolicyError perr;
int expire, grace;
BOOL rc;
user = nil;
decodeCred = [[auth substringFromIndex:5] stringByTrimmingLeadWhiteSpaces];
decodeCred = [decodeCred stringByDecodingBase64];
rng = [decodeCred rangeOfString:@":"];
login = [decodeCred substringToIndex:rng.location];
pwd = [decodeCred substringFromIndex:(rng.location + rng.length)];
domain = nil;
perr = PolicyNoError;
rc = ([[SOGoUserManager sharedUserManager]
checkLogin: [login stringByReplacingString: @"%40"
withString: @"@"]
password: pwd
domain: &domain
perr: &perr
expire: &expire
grace: &grace
additionalInfo: nil]
&& perr == PolicyNoError);
if(rc)
{
//Fecth user info
lm = [SOGoUserManager sharedUserManager];
user = [lm contactInfosForUserWithUIDorEmail: login];
}
else
user = nil;
return user;
}
- (NSDictionary *) _authOpenId: (NSString *) auth withDomain: (NSString *) domain
{
NSDictionary *user;
NSString *token, *login;
SOGoOpenIdSession *openIdSession;
SOGoUserManager *lm;
user = nil;
token = [[auth substringFromIndex:6] stringByTrimmingLeadWhiteSpaces];
openIdSession = [SOGoOpenIdSession OpenIdSession: domain];
if(![openIdSession sessionIsOk])
{
[self errorWithFormat: @"API - OpenId server not found or has unexpected behavior, contact your admin."];
return nil;
}
[openIdSession setAccessToken: token];
login = [openIdSession login: @""];
if(login && ![login isEqualToString: @"anonymous"])
{
//Fecth user info
lm = [SOGoUserManager sharedUserManager];
user = [lm contactInfosForUserWithUIDorEmail: login];
}
else
user = nil;
return user;
}
- (NSException *) dispatchRequest: (WORequest*) theRequest
inResponse: (WOResponse*) theResponse
context: (id) theContext
{
NSAutoreleasePool *pool;
id activeUser;
NSString *method, *action, *error;
NSDictionary *form;
NSArray *paramNeeded;
NSMutableDictionary *paramInjected, *ret;
NSBundle *bundle;
id classAction;
Class clazz;
pool = [[NSAutoreleasePool alloc] init];
ASSIGN(context, theContext);
//Get the api action, check it
action = [self _getActionFromUri: [theRequest uri]];
if(!action)
{
error = [NSString stringWithFormat: @"No actions found for request to API: %@", [theRequest uri]];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 400];
RELEASE(context);
RELEASE(pool);
return nil;
}
//Get the class for this action, check it
bundle = [NSBundle bundleForClass: NSClassFromString(@"SOGoAPIProduct")];
clazz = [bundle classNamed: action];
if(!clazz)
{
error = [NSString stringWithFormat: @"No backend API found for action: %@", action];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 400];
RELEASE(context);
RELEASE(pool);
return nil;
}
//try to instantiate the class
NS_DURING
{
classAction = [[clazz alloc] init];
}
NS_HANDLER
{
error = [NSString stringWithFormat: @"Can't alloc and init class: %@", classAction];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 500];
RELEASE(context);
RELEASE(pool);
return nil;
}
NS_ENDHANDLER;
//Check method
method = [theRequest method];
if(![[classAction methodAllowed] containsObject: method])
{
error = [NSString stringWithFormat: @"Method %@ not allowed for action %@", method, action];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 400];
RELEASE(context);
RELEASE(pool);
return nil;
}
paramInjected = [NSMutableDictionary dictionary];
//Check parameters
if((paramNeeded = [classAction paramNeeded]) && [paramNeeded count] >= 1)
{
NSDictionary* formAndQuery = [theRequest formValues];
for(NSString *param in paramNeeded)
{
id value;
if((value = [formAndQuery objectForKey: param]))
{
NSString* trueValue;
if ([value isKindOfClass: [NSArray class]])
trueValue = [value lastObject];
else
trueValue = value;
[paramInjected setObject: trueValue forKey: param];
}
else
{
error = [NSString stringWithFormat: @"Missing param %@ for action %@", param, action];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 400];
RELEASE(context);
RELEASE(pool);
return nil;
}
}
}
if([classAction needAuth])
{
if(debugOn)
[self logWithFormat: @"Check auth for action %@", action];
//check auth
NSString *auth = [theRequest headerForKey: @"authorization"];
if(auth)
{
NSDictionary* user;
if([[auth lowercaseString] hasPrefix: @"basic"])
{
//basic auth
user = [self _authBasicCheck: auth];
}
else if([[auth lowercaseString] hasPrefix: @"bearer"])
{
//openid auth, we may need to know the user-domain to know which openid server to fetch
NSString *domain = [theRequest headerForKey: @"user-domain"];
user = [self _authOpenId: auth withDomain: domain];
}
else
{
error = [NSString stringWithFormat: @"Authorization method incorrect: %@ for action %@", auth, action];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 401];
RELEASE(context);
RELEASE(pool);
return nil;
}
//add current user in paramInjected
if(user){
if(debugOn)
[self logWithFormat: @"User authenticated %@", user];
[paramInjected setObject: user forKey: @"user"];
}
else
{
error = [NSString stringWithFormat: @"User wrong login or not found for action %@", action];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 401];
RELEASE(context);
RELEASE(pool);
return nil;
}
}
else
{
error = [NSString stringWithFormat: @"No authorization header found for action %@", action];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 401];
RELEASE(context);
RELEASE(pool);
return nil;
}
}
//Execute action
NS_DURING
{
ret = [classAction action: context withParam: paramInjected];
}
NS_HANDLER
{
error = [NSString stringWithFormat: @"Internal error during: %@", action];
[self errorWithFormat: error];
[self _sendAPIErrorResponse: theResponse withMessage: error withStatus: 500];
RELEASE(context);
RELEASE(pool);
return nil;
}
NS_ENDHANDLER;
//Make the response
[theResponse setContent: [ret jsonRepresentation]];
RELEASE(context);
RELEASE(pool);
return nil;
}
@end
+11
View File
@@ -0,0 +1,11 @@
/*
Copyright (C) todo...
*/
#import <Foundation/NSObject.h>
@interface SOGoAPIProduct : NSObject
@end
@implementation SOGoAPIProduct
@end /* SOGoAPIProduct */
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOPE.
SOPE is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOPE 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 Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOPE; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/Foundation.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSArray.h>
#import <SOGoAPI.h>
@class WOContext;
@interface SOGoAPIUserFolder : SOGoAPI
- (NSArray *) methodAllowed;
- (NSDictionary *) action: (WOContext*) ctx withParam: (NSDictionary *) param;
@end
+110
View File
@@ -0,0 +1,110 @@
/*
Copyright (C) todo...
*/
#import <SOGoAPIUserFolder.h>
#import <GDLContentStore/GCSFolderManager.h>
#import <GDLContentStore/GCSFolder.h>
@implementation SOGoAPIUserFolder
- (id) init
{
[super init];
return self;
}
- (void) dealloc
{
[super dealloc];
}
/**
{
"calendar":[
{
"name":"DidyShared",
"url":"http://127.0.0.1/SOGo/dav/sogo-tests1@example.org/Calendar/12509-67F67D00-1-3105AF40"
},
{
"name":"LocalDidy",
"url":"http://127.0.0.1/SOGo/dav/sogo-tests1@example.org/Calendar/1BC38-67B60000-1-6E4B6880"
},
{
"name":"Personal Calendar",
"url":"http://127.0.0.1/SOGo/dav/sogo-tests1@example.org/Calendar/personal"
}
],
"username":"sogo-tests1@example.org",
"contact":[
{
"name":"Personal Address Book",
"url":"http://127.0.0.1/SOGo/dav/sogo-tests1@example.org/Contacts/personal"
}
]
}
**/
- (NSDictionary *) action: (WOContext*) ctx withParam: (NSDictionary *) param
{
NSDictionary* result;
NSArray *folders;
NSMutableArray *cardavLinks, *caldavLinks;
NSString *serverUrl, *basePath, *c_uid, *url;
GCSFolderManager *fm;
GCSFolder *folder;
int max, i;
//Should be a user
c_uid = [[[param objectForKey: @"user"] objectForKey: @"emails"] objectAtIndex: 0];
//fetch folders
fm = [GCSFolderManager defaultFolderManager];
basePath = [NSString stringWithFormat: @"/Users/%@", c_uid];
folders = [fm listSubFoldersAndNamesAtPath: basePath recursive: YES];
//Generate dav link
max = [folders count];
serverUrl = [[ctx serverURL] absoluteString];
cardavLinks = [NSMutableArray array];
caldavLinks = [NSMutableArray array];
serverUrl = [[ctx serverURL] absoluteString];
for (i = 0; i < max; i++)
{
NSMutableDictionary *folderRet;
folderRet = [NSMutableDictionary dictionary];
folder = [folders objectAtIndex: i];
url = [NSString stringWithFormat: @"%@/SOGo/dav/%@/%@", serverUrl, c_uid, [folder objectForKey: @"path"]];
[folderRet setObject: url forKey: @"url"];
[folderRet setObject: [folder objectForKey: @"name"] forKey: @"name"];
if([url rangeOfString:@"/Calendar/"].location == NSNotFound)
{
//Contacts
[cardavLinks addObject: folderRet];
}
else
{
//Calendar
[caldavLinks addObject: folderRet];
}
}
result = [[NSDictionary alloc] initWithObjectsAndKeys:
c_uid, @"username",
cardavLinks, @"contact",
caldavLinks, @"calendar",
nil];
[result autorelease];
return result;
}
@end /* SOGoAPIVersion */
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright (C) 2004-2005 SKYRIX Software AG
This file is part of SOPE.
SOPE is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOPE 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 Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOPE; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <Foundation/Foundation.h>
#import <Foundation/NSObject.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSArray.h>
#import <SOGoAPI.h>
@class WOContext;
@interface SOGoAPIVersion : SOGoAPI
- (NSArray *) methodAllowed;
- (NSDictionary *) action: (WOContext*) ctx withParam: (NSDictionary *) param;
@end
+42
View File
@@ -0,0 +1,42 @@
/*
Copyright (C) todo...
*/
#import <SOGoAPIVersion.h>
@implementation SOGoAPIVersion
- (id) init
{
[super init];
return self;
}
- (void) dealloc
{
[super dealloc];
}
- (BOOL) needAuth
{
return NO;
}
- (NSDictionary *) action: (WOContext*) ctx withParam: (NSDictionary *) param
{
NSDictionary* result;
result = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:SOGO_MAJOR_VERSION], @"major",
[NSNumber numberWithInt:SOGO_MINOR_VERSION], @"minor",
[NSNumber numberWithInt:SOGO_PATCH_VERSION], @"patch",
nil];
[result autorelease];
return result;
}
@end /* SOGoAPIVersion */
+34
View File
@@ -0,0 +1,34 @@
include ../config.make
include $(GNUSTEP_MAKEFILES)/common.make
include ../Version
NEEDS_GUI=no
BUNDLE_EXTENSION = .SOGo
BUNDLE_INSTALL_DIR = $(SOGO_LIBDIR)
WOBUNDLE_EXTENSION = $(BUNDLE_EXTENSION)
WOBUNDLE_INSTALL_DIR = $(BUNDLE_INSTALL_DIR)
# SYSTEM_LIB_DIR += -L/usr/local/lib -L/usr/lib
ADDITIONAL_INCLUDE_DIRS += \
-I.. \
-I../.. \
-I../../SOPE
ADDITIONAL_LIB_DIRS += \
-L../SoObjects/SOGo/SOGo.framework/Versions/Current/sogo \
-L../SoObjects/SOGo/$(GNUSTEP_OBJ_DIR)/ \
-L../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \
-L/usr/local/lib \
-Wl,-rpath,$(SOGO_SYSLIBDIR)/sogo
BUNDLE_LIBS += \
-lSOGo \
-lGDLContentStore \
-lGDLAccess \
-lNGObjWeb \
-lNGCards -lNGMime -lNGLdap \
-lNGStreams -lNGExtensions -lEOControl \
-lDOM -lSaxObjC -lSBJson
ADDITIONAL_BUNDLE_LIBS += $(BUNDLE_LIBS)
+31
View File
@@ -0,0 +1,31 @@
{
requires = ( MAIN, Appointments, Contacts, Mailer );
publicResources = ();
factories = {};
classes = {
SOGoAPI = {
protectedBy = "<public>";
defaultRoles = {
"View" = ( "Authenticated", "PublicUser" );
};
};
};
categories = {
SOGoAPI = {
slots = {
};
methods = {
Version = {
protectedBy = "View";
pageName = "SOGoAPI";
actionName = "sogoVersion";
};
};
};
};
}
+28
View File
@@ -2,6 +2,34 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [5.12.2](https://github.com/Alinto/sogo/compare/SOGo-5.12.1...SOGo-5.12.2) (2025-06-25)
### Features
* **api:** add endpoint for caldav/cardav url ([51f1521](https://github.com/Alinto/sogo/commit/51f1521dba2d0ca8bdbadffadb16db26b975f668))
* **openid:** swicth to libcurl for http request ([a782424](https://github.com/Alinto/sogo/commit/a782424a30cfe8e9c6f2769a45bcdd3498679237))
### Bug Fixes
* **api:** add handler for internal error ([dabad1d](https://github.com/Alinto/sogo/commit/dabad1d0cf46263d78558d79d2af9fff528a9a89))
* **calendar:** properly evalute last occurance freebusy ([8766b7c](https://github.com/Alinto/sogo/commit/8766b7c6b32aedf37d7f8f350e461931b253a4fb))
* **openid:** add state in connection flow ([085fc4a](https://github.com/Alinto/sogo/commit/085fc4a9eb7d1e2a0f7b48baa1f09a8ba0d515e1))
* **openid:** allow expires_in param to be null ([9954c36](https://github.com/Alinto/sogo/commit/9954c3607bfda55424f5ac532a1075407235f345))
* **openid:** make end_session_endpoint optional ([c5fb348](https://github.com/Alinto/sogo/commit/c5fb3482e22f1bfc935213e8ed7208becd9bd1f4))
* **password:** put correct regex for special char ([e36d0d2](https://github.com/Alinto/sogo/commit/e36d0d219baed8c7d57af0069fadb8d1bf7df072))
* **session:** allow password/token to be longer than userkey ([516606b](https://github.com/Alinto/sogo/commit/516606baff5674c25da1e76f532ca2a9d7818ec0))
* **sieve:** add requirements for notify when doing a filter ([f53d7b7](https://github.com/Alinto/sogo/commit/f53d7b73db3d87a17a253bf98ee09a80052b3fd8))
* **view:** don't hide the option mailcomposer in small screen ([8a90773](https://github.com/Alinto/sogo/commit/8a907737de336f873047ecdc7d5c8ac07ae9e45b))
### Localization
* **nl:** Update Dutch translations ([031e117](https://github.com/Alinto/sogo/commit/031e117cbacf9d41caff43be19e639dee81b84dc))
* **pt:** Update Portuguese translations ([9649ae3](https://github.com/Alinto/sogo/commit/9649ae345f334d390860b0db70db8c3b621f513e))
* **tr_TR:** Update TurkishTurkey translations ([e9c03d3](https://github.com/Alinto/sogo/commit/e9c03d3cf7e668866ac757f72d87cdc4709c9d33))
## [5.12.1](https://github.com/Alinto/sogo/compare/SOGo-5.12.0...SOGo-5.12.1) (2025-04-30)
@@ -193,6 +193,9 @@ should apply to all supported operating systems.
Software Downloads
~~~~~~~~~~~~~~~~~~
**In order to access the community build, go to https://www.sogo.nu/download.html[SOGo website]
and select "Development (nightly builds)" at the bottom of the page.**
[NOTE]
In order to access the production builds, you need a proper support contract
from https://sogo.nu/support/index_new.html#support-plans[Alinto]. Continue
+1
View File
@@ -9,6 +9,7 @@ SUBPROJECTS = \
SoObjects \
Main \
UI \
API \
Tools \
Tests/Unit \
+8 -2
View File
@@ -417,7 +417,7 @@ static BOOL debugLeaks;
{
id obj;
WORequest *request;
BOOL isDAVRequest;
BOOL isDAVRequest, isAPIRequest;
SOGoSystemDefaults *sd;
/* put locale info into the context in case it's not there */
@@ -425,8 +425,14 @@ static BOOL debugLeaks;
sd = [SOGoSystemDefaults sharedSystemDefaults];
request = [_ctx request];
isAPIRequest = [[request requestHandlerKey] isEqualToString:@"SOGoAPI"];
isDAVRequest = [[request requestHandlerKey] isEqualToString:@"dav"];
if (isDAVRequest || [sd isWebAccessEnabled])
if(isAPIRequest && ![_key isEqualToString:@"SOGo"] && ![_key isEqualToString:@"SOGoAPI"])
{
//The request will be handle by the API Dispatcher
obj = nil;
}
else if (isDAVRequest || [sd isWebAccessEnabled])
{
if (isDAVRequest)
{
+1
View File
@@ -76,6 +76,7 @@
- (BOOL)folderExistsAtPath:(NSString *)_path;
- (NSArray *)listSubFoldersAtPath:(NSString *)_path recursive:(BOOL)_flag;
- (NSArray *)listSubFoldersAndNamesAtPath:(NSString *)_path recursive:(BOOL)_recursive;
- (NSDictionary *) recordAtPath: (NSString *) _path;
- (GCSFolder *)folderAtPath:(NSString *)_path;
+87
View File
@@ -598,6 +598,27 @@ static BOOL _singleStoreMode = NO;
return sql;
}
- (NSString *)generateSQLPathAndNameFetchForInternalNames:(NSArray *)_names
exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
{
/* fetches the 'path' subset for a given quick-names */
NSMutableString *sql;
NSString *ws;
ws = [self generateSQLWhereForInternalNames:_names
exactMatch:_beExact orDirectSubfolderMatch:_directSubs];
if ([ws length] == 0)
return nil;
sql = [NSMutableString stringWithCapacity:256];
[sql appendString:@"SELECT c_path, c_foldername FROM "];
[sql appendString:[self folderInfoTableName]];
[sql appendString:@" WHERE "];
[sql appendString:ws];
if (debugSQLGen) [self logWithFormat:@"PathFetch-SQL: %@", sql];
return sql;
}
/* handling folder names */
- (BOOL)_isStandardizedPath:(NSString *)_path {
@@ -806,6 +827,72 @@ static BOOL _singleStoreMode = NO;
return result;
}
- (NSArray *)listSubFoldersAndNamesAtPath:(NSString *)_path recursive:(BOOL)_recursive{
NSMutableArray *result;
NSString *fname;
NSArray *fnames, *records;
NSString *sql;
unsigned i, count;
if ((fnames = [self internalNamesFromPath:_path]) == nil) {
[self debugWithFormat:@"got no internal names for path: '%@'", _path];
return nil;
}
sql = [self generateSQLPathAndNameFetchForInternalNames:fnames
exactMatch:NO orDirectSubfolderMatch:(_recursive ? NO : YES)];
if ([sql length] == 0) {
[self debugWithFormat:@"got no SQL for names: %@", fnames];
return nil;
}
if ((records = [self performSQL:sql]) == nil) {
[self logWithFormat:@"ERROR(%s): executing SQL failed: '%@'",
__PRETTY_FUNCTION__, sql];
return nil;
}
if ((count = [records count]) == 0)
return emptyArray;
result = [NSMutableArray arrayWithCapacity:(count > 128 ? 128 : count)];
fname = [self internalNameFromPath:_path];
fname = [fname stringByAppendingString:@"/"]; /* add slash */
for (i = 0; i < count; i++) {
NSDictionary *record, *folderInfo;
NSString *sname, *spath, *foldername;
record = [records objectAtIndex:i];
sname = [record objectForKey:GCSPathRecordName];
if (![sname hasPrefix:fname]) /* does not match at all ... */
continue;
/* strip prefix and following slash */
sname = [sname substringFromIndex:[fname length]];
spath = [self pathPartFromInternalName:sname];
if (_recursive) {
if ([spath length] > 0){
foldername = [record objectForKey: @"c_foldername"];
folderInfo = [NSDictionary dictionaryWithObjectsAndKeys: spath, @"path", foldername, @"name", nil];
[result addObject:folderInfo];
}
}
else {
/* direct children only, so exclude everything with a slash */
if ([sname rangeOfString:@"/"].length == 0 && [spath length] > 0)
{
foldername = [record objectForKey: @"c_foldername"];
folderInfo = [NSDictionary dictionaryWithObjectsAndKeys: spath, @"path", foldername, @"name", nil];
[result addObject:folderInfo];
}
}
}
return result;
}
- (NSDictionary *) recordAtPath: (NSString *) _path
{
NSMutableString *sql;
+1 -1
View File
@@ -303,8 +303,8 @@ static NSString *openIdFolderURLString = nil;
newRecord = [NSDictionary dictionaryWithObjectsAndKeys: _user_session, @"c_user_session",
_old_session, @"c_old_session",
[NSNumber numberWithInt:now], @"c_session_started",
_refresh_token, @"c_refresh_token",
[NSNumber numberWithInt:nowExpire] , @"c_access_token_expires_in",
_refresh_token, @"c_refresh_token",
[NSNumber numberWithInt:nowRefreshExpire] , @"c_refresh_token_expires_in",
nil];
record = [self recordForSession: _user_session useOldSession: NO];
+14 -9
View File
@@ -55,7 +55,7 @@
- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
NSMutableArray *ranges;
NSCalendarDate *firStart, *startDate, *endDate, *currentStartDate, *currentEndDate;
NSCalendarDate *firStart, *firEnd, *startDate, *endDate, *currentStartDate, *currentEndDate;
iCalByDayMask *dayMask;
long i, count, repeatCount;
unsigned interval;
@@ -63,6 +63,7 @@
//[self logWithFormat: @"Recurrence rule is %@", rrule];
firStart = [firstRange startDate];
firEnd = [firstRange endDate];
startDate = [_r startDate];
endDate = [_r endDate];
dayMask = nil;
@@ -84,13 +85,14 @@
// If rule is bound, check the bounds
if (![rrule isInfinite])
{
NSCalendarDate *until, *lastDate;
NSCalendarDate *until, *lastStartDate, *lastEndDate;
lastDate = nil;
lastStartDate = nil;
lastEndDate = nil;
until = [rrule untilDate];
if (until)
{
lastDate = until;
lastStartDate = until;
}
else
{
@@ -98,20 +100,23 @@
if (dayMask == nil)
// If there's no day mask, we can compute the date of the last
// occurrence of the recurrent rule.
lastDate = [firStart dateByAddingYears: 0 months: 0
lastStartDate = [firStart dateByAddingYears: 0 months: 0
days: (interval
* (repeatCount - 1))];
lastEndDate = [firEnd dateByAddingYears: 0 months: 0
days: (interval
* (repeatCount - 1))];
}
if (lastDate != nil)
if (lastStartDate != nil && lastEndDate != nil)
{
if ([lastDate compare: startDate] == NSOrderedAscending)
if ([lastEndDate compare: startDate] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([lastDate compare: endDate] == NSOrderedAscending)
if ([lastStartDate compare: endDate] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
endDate = lastDate;
endDate = lastStartDate;
}
}
+14 -9
View File
@@ -185,7 +185,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
// TODO: check whether this is OK for multiday-events!
NSMutableArray *ranges;
NSTimeZone *timeZone;
NSCalendarDate *eventStartDate, *rStart, *rEnd, *until, *referenceDate;
NSCalendarDate *eventStartDate, *eventEndDate, *rStart, *rEnd, *untilStart, *untilEnd, *referenceDate;
int eventDayOfMonth;
unsigned monthIdxInRange, numberOfMonthsInRange, interval, repeatCount;
int diff, count;
@@ -199,12 +199,14 @@ static inline unsigned iCalDoWForNSDoW (int dow)
iCalByDayMask *byDayMask;
eventStartDate = [firstRange startDate];
eventEndDate = [firstRange endDate];
eventDayOfMonth = [eventStartDate dayOfMonth];
timeZone = [eventStartDate timeZone];
rStart = [_r startDate];
rEnd = [_r endDate];
interval = [rrule repeatInterval];
until = nil;
untilStart = nil;
untilEnd = nil;
repeatCount = [rrule repeatCount];
byMonth = [rrule byMonth];
byMonthDay = [rrule byMonthDay];
@@ -218,24 +220,27 @@ static inline unsigned iCalDoWForNSDoW (int dow)
{
// When there's no BYxxx mask, we can find the date of the last
// occurrence.
until = [eventStartDate dateByAddingYears: 0
untilStart = [eventStartDate dateByAddingYears: 0
months: (interval * (repeatCount - 1))
days: 0];
untilEnd = [eventEndDate dateByAddingYears: 0
months: (interval * (repeatCount - 1))
days: 0];
}
else
{
until = [rrule untilDate];
untilStart = [rrule untilDate];
}
}
if (until != nil)
if (untilStart != nil)
{
if ([until compare: rStart] == NSOrderedAscending)
if ([untilEnd compare: rStart] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([until compare: rEnd] == NSOrderedAscending)
if ([untilStart compare: rEnd] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
rEnd = until;
rEnd = untilStart;
}
if (byMonth && [byMonth count] > 0)
@@ -441,7 +446,7 @@ static inline unsigned iCalDoWForNSDoW (int dow)
start = [cursor dateByAddingYears: 0 months: 0 days: (dom - 1)];
doCont = [self _addInstanceWithStartDate: start
limitDate: until
limitDate: untilStart
limitRange: _r
toArray: ranges];
//NSLog(@"*** MONTHLY [%i/%i] adding %@%@ (count = %i)", dom, numDaysInMonth, start, (doCont?@"":@" .. NOT!"), count);
+14 -9
View File
@@ -64,7 +64,7 @@
- (NSArray *) recurrenceRangesWithinCalendarDateRange: (NGCalendarDateRange *) _r
{
NSMutableArray *ranges;
NSCalendarDate *firStart, *startDate, *endDate, *currentStartDate, *currentEndDate;
NSCalendarDate *firStart, *firEnd, *startDate, *endDate, *currentStartDate, *currentEndDate;
long i, repeatCount, count;
unsigned interval;
iCalByDayMask *dayMask;
@@ -73,6 +73,7 @@
//[self logWithFormat: @"Recurrence rule is %@", rrule];
firStart = [firstRange startDate];
firEnd = [firstRange endDate];
startDate = [_r startDate];
endDate = [_r endDate];
dayMask = nil;
@@ -95,31 +96,35 @@
// If rule is bound, check the bounds
if (![rrule isInfinite])
{
NSCalendarDate *until, *lastDate;
NSCalendarDate *until, *lastStartDate, *lastEndDate;
lastDate = nil;
lastStartDate = nil;
lastEndDate = nil;
until = [rrule untilDate];
if (until)
lastDate = until;
lastStartDate = until;
else
{
repeatCount = [rrule repeatCount];
if (dayMask == nil)
// When there's no BYxxx mask, we can find the date of the last
// occurrence.
lastDate = [firStart dateByAddingYears: 0 months: 0
lastStartDate = [firStart dateByAddingYears: 0 months: 0
days: (interval
* (repeatCount - 1) * 7)];
lastEndDate = [firEnd dateByAddingYears: 0 months: 0
days: (interval
* (repeatCount - 1) * 7)];
}
if (lastDate != nil)
if (lastStartDate != nil && lastEndDate != nil)
{
if ([lastDate compare: startDate] == NSOrderedAscending)
if ([lastEndDate compare: startDate] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([lastDate compare: endDate] == NSOrderedAscending)
if ([lastStartDate compare: endDate] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
endDate = [lastDate addTimeInterval: [firstRange duration]];
endDate = [lastStartDate addTimeInterval: [firstRange duration]];
}
}
+14 -9
View File
@@ -44,13 +44,14 @@
{
NSMutableArray *ranges;
NSArray *byMonth;
NSCalendarDate *firStart, *lastDate, *rStart, *rEnd, *until, *referenceDate, *rTemp;
NSCalendarDate *firStart, *firEnd, *lastStartDate, *lastEndDate, *rStart, *rEnd, *until, *referenceDate, *rTemp;
NSInteger *hoursOfOffset;
iCalMonthlyRecurrenceCalculator *monthlyCalc;
unsigned j, yearIdxInRange, numberOfYearsInRange, count, interval, monthDiff;
int diff, repeatCount, currentMonth, origNbDaysInMonth;
firStart = [firstRange startDate];
firEnd = [firstRange endDate];
rStart = [_r startDate];
rEnd = [_r endDate];
interval = [rrule repeatInterval];
@@ -71,33 +72,37 @@
// If rule is bound, check the bounds
if (![rrule isInfinite])
{
lastDate = nil;
lastStartDate = nil;
lastEndDate = nil;
until = [rrule untilDate];
repeatCount = [rrule repeatCount];
if (until)
{
lastDate = until;
lastStartDate = until;
}
if (repeatCount > 0)
{
if (lastDate == nil && ![rrule hasByMask])
if (lastStartDate == nil && ![rrule hasByMask])
// When there's no BYxxx mask, we can find the date of the last
// occurrence.
lastDate = [firStart dateByAddingYears: (interval * (repeatCount - 1))
lastStartDate = [firStart dateByAddingYears: (interval * (repeatCount - 1))
months: 0
days: 0];
lastEndDate = [firEnd dateByAddingYears: (interval * (repeatCount - 1))
months: 0
days: 0];
referenceDate = firStart;
}
if (lastDate != nil)
if (lastStartDate != nil)
{
if ([lastDate compare: rStart] == NSOrderedAscending)
if ([lastEndDate compare: rStart] == NSOrderedAscending)
// Range starts after last occurrence
return nil;
if ([lastDate compare: rEnd] == NSOrderedAscending)
if ([lastStartDate compare: rEnd] == NSOrderedAscending)
// Range ends after last occurence; adjust end date
rEnd = [lastDate addTimeInterval: [firstRange duration]];
rEnd = [lastStartDate addTimeInterval: [firstRange duration]];
}
}
+1 -1
View File
@@ -231,7 +231,7 @@ endif
ADDITIONAL_TOOL_LIBS += -Lobj -lSOGo$(LIBRARY_NAME_SUFFIX)
ADDITIONAL_INCLUDE_DIRS += -I../../SOPE/
ADDITIONAL_LIB_DIRS += -L../../SOPE/GDLContentStore/obj/
ADDITIONAL_LDFLAGS += -lmemcached -lzip -lytnef
ADDITIONAL_LDFLAGS += -lmemcached -lzip -lytnef -lcurl
-include GNUmakefile.preamble
ifneq ($(FHS_INSTALL_ROOT),)
+22 -2
View File
@@ -33,6 +33,25 @@
@class NSURL;
@class NSJSONSerialization;
size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer);
@interface SimpleOpenIdResponse: NSObject
{
unsigned int status;
NSString *content;
NSString *headers;
}
- (id)initWithResponse: (NSString *)_data andHeaders: (NSString *)_headers andStatus:(unsigned int )_status;
- (void)setStatus:(unsigned int)_status;
- (unsigned int)status;
- (void)setContent:(NSString *)_data;
- (NSString *)contentString;
- (void)setHearders:(NSString *)_data;
- (NSString *)headers;
@end
@interface SOGoOpenIdSession : SOGoObject
{
@@ -47,6 +66,7 @@
NSString *openIdClient;
NSString *openIdClientSecret;
NSString *openIdEmailParam;
NSString *openIdHttpVersion;
BOOL openIdEnableRefreshToken;
BOOL sendDomainInfo;
@@ -80,12 +100,12 @@
- (void) initialize;
- (void) initializeWithConfig: (NSDictionary *) _config;
- (BOOL) sessionIsOK;
- (WOResponse *) _performOpenIdRequest: (NSString *) endpoint
- (SimpleOpenIdResponse *) _performOpenIdRequest: (NSString *) endpoint
method: (NSString *) method
headers: (NSDictionary *) headers
body: (NSData *) body;
- (NSMutableDictionary *) fecthConfiguration: (NSString *) _domain;
- (void) setAccessToken;
- (void) setAccessToken: (NSString* ) token;
- (NSString *) getRefreshToken;
- (NSString *) getToken;
- (NSString *) getCurrentToken;
+233 -52
View File
@@ -18,8 +18,11 @@
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSProcessInfo.h>
#import <curl/curl.h>
#import <NGObjWeb/WOHTTPConnection.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
@@ -27,12 +30,12 @@
#import <SOGo/SOGoUser.h>
#import <GDLContentStore/GCSOpenIdFolder.h>
#import <GDLContentStore/GCSFolderManager.h>
#import "NSDictionary+Utilities.h"
#import "NSString+Utilities.h"
#import <SOGo/NSString+Utilities.h>
#import <SOGo/NSString+Crypto.h>
#import "SOGoCache.h"
#import "SOGoSystemDefaults.h"
@@ -40,6 +43,88 @@
static BOOL SOGoOpenIDDebugEnabled = YES;
size_t curl_body_function(void *ptr, size_t size, size_t nmemb, void *buffer)
{
size_t total;
total = size * nmemb;
[(NSMutableData *)buffer appendBytes: ptr length: total];
return total;
}
@implementation SimpleOpenIdResponse
- (id)init {
if ((self = [super init])) {
}
return self;
}
- (id)initWithResponse: (NSString *)_data andHeaders: (NSString *)_headers andStatus:(unsigned int )_status{
if ((self = [self init])) {
[self setStatus: _status];
[self setContent: _data];
[self setHeaders: _headers];
}
return self;
}
- (void)setStatus:(unsigned int)_status
{
self->status = _status;
}
- (unsigned int)status
{
if(self->status)
return self->status;
return 0;
}
- (void)setContent:(NSString *)_data
{
self->content = _data;
}
- (NSString *)contentString
{
if(self->content)
return self->content;
return nil;
}
- (void)setHeaders:(NSString *)_data
{
self->headers = _data;
}
- (NSString *)headers
{
if(self->headers)
return self->headers;
return nil;
}
- (NSString*)description {
NSString *co, *hd;
unsigned int st;
st = 0;
co = @"nil";
hd = @"nil";
if(self->status)
st = self->status;
if(self->headers)
hd = self->headers;
if(self->content)
co = self->content;
return [NSString stringWithFormat: @"<SimpleOpenIdResponse> <status: %d>, <headers: %@>, <content: %@>", st, hd, co];
}
@end
@implementation SOGoOpenIdSession
/// Check if all required parameters to set up a OpenIdSession are in sogo.conf
@@ -54,6 +139,13 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
return NO;
}
sd = [SOGoSystemDefaults sharedSystemDefaults];
if(![[sd authenticationType] isEqualToString: @"openid"])
{
[self errorWithFormat: @"Sogo SOGoAuthenticationType is not openid"];
return NO;
}
return ([sd openIdConfigUrl] && [sd openIdScope] && [sd openIdClient] && [sd openIdClientSecret]);
}
@@ -72,6 +164,7 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
openIdClient = [_config objectForKey: @"SOGoOpenIdClient"];
openIdClientSecret = [_config objectForKey: @"SOGoOpenIdClientSecret"];
openIdEmailParam = [_config objectForKey: @"SOGoOpenIdEmailParam"];
openIdHttpVersion = [_config objectForKey: @"SOGoOpenIdHttpVersion"];
openIdEnableRefreshToken = NO;
refreshTokenBool = [_config objectForKey: @"SOGoOpenIdEnableRefreshToken"];
@@ -120,7 +213,7 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
}
else
{
[self errorWithFormat: @"Missing parameters from sogo.conf"];
[self errorWithFormat: @"LoginTypebyDOmain - Openid not found or missing parameters for domain", _domain];
}
}
else if ([[self class] checkUserConfig])
@@ -130,6 +223,7 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
openIdClient = [sd openIdClient];
openIdClientSecret = [sd openIdClientSecret];
openIdEmailParam = [sd openIdEmailParam];
openIdHttpVersion = [sd openIdHttpVersion];
openIdEnableRefreshToken = [sd openIdEnableRefreshToken];
userTokenInterval = [sd openIdTokenCheckInterval];
sendDomainInfo = [sd openIdSendDomainInfo];
@@ -149,66 +243,147 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
}
}
- (WOResponse *) _performOpenIdRequest: (NSString *) endpoint
- (SimpleOpenIdResponse *) _performOpenIdRequest: (NSString *) endpoint
method: (NSString *) method
headers: (NSDictionary *) headers
body: (NSData *) body
{
NSURL *url;
NSMutableData *buffer, *buffHeaders;
SimpleOpenIdResponse *response;
CURL *curl;
struct curl_slist *headerlist=NULL;
NSUInteger status;
WORequest *request;
WOResponse *response;
WOHTTPConnection *httpConnection;
url = [NSURL URLWithString: endpoint];
if (url)
{
if(SOGoOpenIDDebugEnabled)
{
NSLog(@"OpenId perform request: %@ %@", method, endpoint);
NSLog(@"OpenId perform request, headers %@", headers);
// if(body)
// NSLog(@"OpenId perform request: content %@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
}
httpConnection = [[WOHTTPConnection alloc] initWithURL: url];
[httpConnection autorelease];
NSString *content, *headerResp;
CURLcode rc;
char error[CURL_ERROR_SIZE];
request = [[WORequest alloc] initWithMethod: method
uri: [endpoint hostlessURL]
httpVersion: @"HTTP/1.1"
headers: headers content: body
userInfo: nil];
[request autorelease];
[httpConnection sendRequest: request];
response = [httpConnection readResponse];
status = [response status];
if(status >= 200 && status <500 && status != 404)
return response;
else if (status == 404)
{
[self errorWithFormat: @"OpenID endpoint not found (404): %@", endpoint];
return nil;
}
else
{
[self errorWithFormat: @"OpenID server internal error during %@: %@", endpoint, response];
return nil;
}
}
else
url = [NSURL URLWithString: endpoint];
if(!url)
{
[self errorWithFormat: @"OpenID can't handle endpoint (not a url): '%@'", endpoint];
return nil;
}
if(SOGoOpenIDDebugEnabled)
{
NSLog(@"OpenId perform request: %@ %@", method, endpoint);
NSLog(@"OpenId perform request, headers %@", headers);
}
if(SOGoOpenIDDebugEnabled)
NSLog(@"OpenId perform request, body raw %@", body);
curl_global_init(CURL_GLOBAL_SSL);
curl = curl_easy_init();
if (curl)
{
error[0] = 0;
curl_easy_setopt(curl, CURLOPT_URL, [endpoint UTF8String]);
//add form
if(body)
{
//Trick because sometimes NSData bytes don't add the \0 at the end and junk character may be added
const char *myBodyTmp = (const char*)[body bytes];
char myBody[[body length]+1];
strncpy(myBody, myBodyTmp, [body length]);
myBody[[body length]]='\0';
if(SOGoOpenIDDebugEnabled)
NSLog(@"OpenId perform request, myBodyTmp %s", myBodyTmp);
if(SOGoOpenIDDebugEnabled)
NSLog(@"OpenId perform request, myBody %s", myBody);
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, myBody);
}
//add headers
if(headers)
{
NSEnumerator *enumerator = [headers keyEnumerator];
NSString *key, *header;
while((key = [enumerator nextObject]))
{
header = [NSString stringWithFormat: @"%@: %@", key, [headers objectForKey: key]];
headerlist = curl_slist_append(headerlist, [header UTF8String]);
}
}
if(headerlist != NULL)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
buffer = [NSMutableData data];
buffHeaders = [NSMutableData data];
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_body_function);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_body_function);
curl_easy_setopt(curl, CURLOPT_HEADERDATA, buffHeaders);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error);
// Perform SOAP request
rc = curl_easy_perform(curl);
if (rc == CURLE_OK)
{
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
response = [SimpleOpenIdResponse alloc];
headerResp = [[NSString alloc] initWithData: buffHeaders
encoding: NSUTF8StringEncoding];
response = [response initWithResponse: nil andHeaders: headerResp andStatus: status];
if(status >= 200 && status <500 && status != 404)
{
content = [[NSString alloc] initWithData: buffer
encoding: NSUTF8StringEncoding];
if (!content)
content = [[NSString alloc] initWithData: buffer
encoding: NSISOLatin1StringEncoding];
[response setContent: content];
if(SOGoOpenIDDebugEnabled)
NSLog(@"OpenId perform request: response is: %@", response);
return [response autorelease];
}
else if (status == 404)
{
[self errorWithFormat: @"OpenID endpoint not found (404): %@", endpoint];
return nil;
}
else
{
[self errorWithFormat: @"OpenID server internal error during %@: %@", endpoint, response];
return nil;
}
}
else
{
[self errorWithFormat: @"CURL error while accessing %@ (%d): %@", endpoint, rc,
[NSString stringWithCString: strlen(error) ? error : curl_easy_strerror(rc)]];
}
curl_easy_cleanup (curl);
}
else
{
[self errorWithFormat: @"OpenID error when setting curl request for: %@", endpoint];
return nil;
}
}
- (NSMutableDictionary *) fecthConfiguration: (NSString*) _domain
{
NSString *content;
WOResponse * response;
SimpleOpenIdResponse *response;
// WOResponse * response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *config, *headers;
@@ -498,7 +673,8 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
- (NSMutableDictionary *) fetchToken: (NSString * ) code redirect: (NSString *) oldLocation
{
NSString *location, *form, *content;
WOResponse *response;
SimpleOpenIdResponse *response;
// WOResponse *response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *headers;
@@ -523,7 +699,10 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
self->forDomain, @"sogo-user-domain", nil];
else
headers = [NSDictionary dictionaryWithObject: @"application/x-www-form-urlencoded" forKey: @"content-type"];
if(SOGoOpenIDDebugEnabled)
NSLog(@"OpenId fetch token, form %@", form);
response = [self _performOpenIdRequest: location
method: @"POST"
headers: headers
@@ -566,7 +745,8 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
- (NSMutableDictionary *) refreshToken: (NSString * ) userRefreshToken
{
NSString *location, *form, *content;
WOResponse *response;
SimpleOpenIdResponse *response;
// WOResponse *response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *headers;
@@ -639,7 +819,8 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
- (NSMutableDictionary *) fetchUserInfo
{
NSString *location, *auth, *content;
WOResponse *response;
SimpleOpenIdResponse *response;
// WOResponse *response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *profile, *headers;
@@ -695,7 +876,7 @@ static BOOL SOGoOpenIDDebugEnabled = YES;
}
else
{
[self logWithFormat: @"Error during fetching the token (status %d), response: %@", status, response];
[self logWithFormat: @"Error fetching userInfo (status %d), response: %@", status, response];
[result setObject: @"http-error" forKey: @"error"];
}
}
+18 -18
View File
@@ -122,28 +122,28 @@ static NSString *productDirectoryName = @"SOGo";
pathes = [[self productSearchPathes] objectEnumerator];
while ((lpath = [pathes nextObject]))
{
productNames = [[fm directoryContentsAtPath: lpath] objectEnumerator];
while ((productName = [productNames nextObject]))
{
productNames = [[fm directoryContentsAtPath: lpath] objectEnumerator];
while ((productName = [productNames nextObject]))
{
if ([[productName pathExtension] isEqualToString: @"SOGo"])
{
bpath = [lpath stringByAppendingPathComponent: productName];
[registry registerProductAtPath: bpath];
[loadedProducts addObject: productName];
}
}
if ([loadedProducts count])
if ([[productName pathExtension] isEqualToString: @"SOGo"])
{
if (verbose)
{
[self logWithFormat: @"SOGo products loaded from '%@':", lpath];
[self logWithFormat: @" %@",
[loadedProducts componentsJoinedByString: @", "]];
}
[loadedProducts removeAllObjects];
bpath = [lpath stringByAppendingPathComponent: productName];
[registry registerProductAtPath: bpath];
[loadedProducts addObject: productName];
}
}
if ([loadedProducts count])
{
if (verbose)
{
[self logWithFormat: @"SOGo products loaded from '%@':", lpath];
[self logWithFormat: @" %@",
[loadedProducts componentsJoinedByString: @", "]];
}
[loadedProducts removeAllObjects];
}
}
if (![registry loadAllProducts] && verbose)
[self warnWithFormat: @"could not load all products !"];
+28 -3
View File
@@ -162,11 +162,23 @@
// Get the key length and its bytes
data = [theKey dataByDecodingBase64];
key = (char *)[data bytes];
klen = [data length];
//value longer than the key, concatenate the key with itself until long enough
if (klen < [theValue length])
[self errorWithFormat: @"Value to be secured is too big (%i > %i) -- secured value will be corrupted", [theValue length], klen, [theKey length]];
{
NSMutableData *concatenatedData = [NSMutableData data];
int j;
int nbDuplication = [theValue length]/klen;
for(j=0; j <= nbDuplication; j++)
[concatenatedData appendData:data];
data = [NSData dataWithData: concatenatedData];
klen = [data length];
}
key = (char *)[data bytes];
// Get the key - padding it with 0 with key length
pass = (char *) calloc(klen, sizeof(char));
@@ -201,7 +213,6 @@
// Get the key length and its bytes
dataKey = [theKey dataByDecodingBase64];
key = (char *)[dataKey bytes];
klen = [dataKey length];
// Get the secured value length and its bytes
@@ -209,6 +220,20 @@
value = (char *)[dataValue bytes];
vlen = [dataValue length];
//If the key is shorer than the value, duplicate it with itself.
if(klen < vlen)
{
NSMutableData *concatenatedData = [NSMutableData data];
int j;
int nbDuplication = [dataValue length]/klen;
for(j=0; j <= nbDuplication; j++)
[concatenatedData appendData:dataKey];
dataKey = [NSData dataWithData: concatenatedData];
klen = [dataKey length];
}
key = (char *)[dataKey bytes];
// Target buffer
buf = (char *) calloc(klen, sizeof(char));
+9 -1
View File
@@ -496,6 +496,7 @@ static NSString *sieveScriptName = @"sogo";
}
- (NSString *) _extractSieveAction: (NSDictionary *) action
withReq: (NSMutableDictionary *) req
delimiter: (NSString *) delimiter
{
NSString *sieveAction, *method, *requirement, *argument, *flag, *mailbox;
@@ -548,7 +549,9 @@ static NSString *sieveScriptName = @"sogo";
{
argument = [NSString stringWithFormat: @"mailto:%@", argument];
sieveAction = [NSString stringWithFormat: @"%@ %@",
method, [argument asSieveQuotedString]];
method, [argument asSieveQuotedString]];
[req addObjectUniquely: @"enotify"];
[req addObjectUniquely: @"variables"];
}
else if ([method isEqualToString: @"reject"])
sieveAction = [NSString stringWithFormat: @"%@ %@",
@@ -575,6 +578,7 @@ static NSString *sieveScriptName = @"sogo";
}
- (NSArray *) _extractSieveActions: (NSArray *) actions
withReq: (NSMutableDictionary *) req
delimiter: (NSString *) delimiter
{
NSMutableArray *sieveActions;
@@ -586,6 +590,7 @@ static NSString *sieveScriptName = @"sogo";
for (count = 0; !scriptError && count < max; count++)
{
sieveAction = [self _extractSieveAction: [actions objectAtIndex: count]
withReq: req
delimiter: delimiter];
if (!scriptError)
[sieveActions addObject: sieveAction];
@@ -595,6 +600,7 @@ static NSString *sieveScriptName = @"sogo";
}
- (NSString *) _convertScriptToSieve: (NSDictionary *) newScript
withReq: (NSMutableDictionary *) req
delimiter: (NSString *) delimiter
{
NSMutableString *sieveText;
@@ -624,6 +630,7 @@ static NSString *sieveScriptName = @"sogo";
scriptError = [NSString stringWithFormat: @"Bad test: %@", match];
}
sieveActions = [self _extractSieveActions: [newScript objectForKey: @"actions"]
withReq: req
delimiter: delimiter];
if ([sieveActions count])
[sieveText appendFormat: @" %@;\r\n",
@@ -660,6 +667,7 @@ static NSString *sieveScriptName = @"sogo";
if ([[currentScript objectForKey: @"active"] boolValue])
{
sieveText = [self _convertScriptToSieve: currentScript
withReq: newRequirements
delimiter: delimiter];
[sieveScript appendString: sieveText];
}
+2
View File
@@ -83,6 +83,7 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
- (BOOL) uixDebugEnabled;
- (BOOL) easDebugEnabled;
- (BOOL) openIdDebugEnabled;
- (BOOL) apiDebugEnabled;
- (BOOL) tnefDecoderDebugEnabled;
- (BOOL) xsrfValidationEnabled;
@@ -106,6 +107,7 @@ NSComparisonResult languageSort(id el1, id el2, void *context);
- (NSString *) openIdClient;
- (NSString *) openIdClientSecret;
- (NSString *) openIdEmailParam;
- (NSString *) openIdHttpVersion;
- (BOOL) openIdEnableRefreshToken;
- (BOOL) openIdLogoutEnabled: (NSString *) _domain;
- (int) openIdTokenCheckInterval;
+14
View File
@@ -654,6 +654,11 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return [self boolForKey: @"SOGoOpenIDDebugEnabled"];
}
- (BOOL) apiDebugEnabled
{
return [self boolForKey: @"SOGoAPIDebugEnabled"];
}
- (BOOL) tnefDecoderDebugEnabled
{
return [self boolForKey: @"SOGoTnefDecoderDebugEnabled"];
@@ -779,6 +784,15 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
return emailParam;
}
- (NSString *) openIdHttpVersion
{
NSString *httpVersion;
httpVersion = [self stringForKey: @"SOGoOpenIdHttpVersion"];
if(!httpVersion)
httpVersion = @"HTTP/1.1";
return httpVersion;
}
- (BOOL) openIdLogoutEnabled: (NSString *) _domain
{
if(_domain && [self doesLoginTypeByDomain])
@@ -25,7 +25,7 @@
"No resource" = "Geen middelen";
"Any Authenticated User" = "Elke geauthenticeerde gebruiker";
"Public Access" = "Publieke toegang";
"Save" = "Save";
"Clear" = "Clear";
"Message of the day" = "Message of the day";
"Message of the day has been saved" = "Message of the day has been saved";
"Save" = "Opslaan";
"Clear" = "Leeg maken";
"Message of the day" = "Bericht van de dag";
"Message of the day has been saved" = "Bericht van de dag is opgeslagen";
+14 -14
View File
@@ -45,7 +45,7 @@
"Reduce" = "Verminderen";
/* tooltip */
"Refresh" = "Refresh";
"Refresh" = "Vernieuwen";
/* generic.js */
"Unable to subscribe to that folder!"
@@ -109,7 +109,7 @@
"Personal" = "Persoonlijk";
"To Do" = "Te doen";
"Later" = "Later";
"Forwarded" = "Forwarded";
"Forwarded" = "Doorgestuurd";
"Return Receipt Sent" = "Ontvangstbewijs verzonden";
"a2_Sunday" = "Zo";
"a2_Monday" = "Ma";
@@ -122,7 +122,7 @@
"Add User" = "Gebruiker toevoegen";
"Loading" = "Laden";
"No such user." = "Geen dergelijke gebruiker";
"You cannot (un)subscribe to a folder that you own!" = "Kan niet aan- of afmelden van eigen map!";
"You cannot (un)subscribe to a folder that you own!" = "U kunt zich niet (af)melden voor een map waarvan u de eigenaar bent!";
/* SMIME Certificate field */
"S/MIME Certificate" = "S/MIME Certificaat";
@@ -227,16 +227,16 @@
"Move forward" = "Ga vooruit";
/* Password Recovery */
"passwordRecovery_Disabled" = "Disabled";
"passwordRecovery_SecretQuestion" = "Secret question";
"passwordRecovery_SecretQuestion1" = "What is the name of your first pet ?";
"passwordRecovery_SecretQuestion2" = "What was your first car ?";
"passwordRecovery_SecretQuestion3" = "What is your favorite movie ?";
"passwordRecovery_SecondaryEmail" = "Secondary E-mail";
"passwordRecovery_Disabled" = "Uitgezet";
"passwordRecovery_SecretQuestion" = "Geheime vraag";
"passwordRecovery_SecretQuestion1" = "Hoe heet je eerste huisdier?";
"passwordRecovery_SecretQuestion2" = "Wat was je eerste auto?";
"passwordRecovery_SecretQuestion3" = "Wat is je favoriete film?";
"passwordRecovery_SecondaryEmail" = "Secundair e-mailadres";
/* Password */
"POLICY_MIN_LOWERCASE_LETTER" = "Minimum of %{0} lowercase letter";
"POLICY_MIN_UPPERCASE_LETTER" = "Minimum of %{0} uppercase letter";
"POLICY_MIN_DIGIT" = "Minimum of %{0} digit";
"POLICY_MIN_SPECIAL_SYMBOLS" = "Minimum of %{0} special symbols";
"POLICY_MIN_LENGTH" = "Minimum length of %{0} characters";
"POLICY_MIN_LOWERCASE_LETTER" = "Minimaal %{0} kleine letter";
"POLICY_MIN_UPPERCASE_LETTER" = "Minimaal %{0} hoofdletter";
"POLICY_MIN_DIGIT" = "Minimaal %{0} cijfer";
"POLICY_MIN_SPECIAL_SYMBOLS" = "Minimaal %{0} speciale symbolen";
"POLICY_MIN_LENGTH" = "Minimale lengte van %{0} tekens";
@@ -235,8 +235,8 @@
"passwordRecovery_SecondaryEmail" = "İkincil e-posta";
/* Password */
"POLICY_MIN_LOWERCASE_LETTER" = "Minimum of %{0} lowercase letter";
"POLICY_MIN_UPPERCASE_LETTER" = "Minimum of %{0} uppercase letter";
"POLICY_MIN_DIGIT" = "Minimum of %{0} digit";
"POLICY_MIN_SPECIAL_SYMBOLS" = "Minimum of %{0} special symbols";
"POLICY_MIN_LENGTH" = "Minimum length of %{0} characters";
"POLICY_MIN_LOWERCASE_LETTER" = "En az %{0} küçük harf";
"POLICY_MIN_UPPERCASE_LETTER" = "En az %{0} büyük harf";
"POLICY_MIN_DIGIT" = "En az %{0} rakam";
"POLICY_MIN_SPECIAL_SYMBOLS" = "En az %{0} özel simge";
"POLICY_MIN_LENGTH" = "En az %{0} karakter uzunluğunda";
+1 -1
View File
@@ -193,7 +193,7 @@
"Work" = "Werk";
"Mobile" = "Mobiel";
"Pager" = "Pieper";
"Download iOS - MacOS configuration profile" = "Download iOS - MacOS configuration profile";
"Download iOS - MacOS configuration profile" = "Download iOS - MacOS configuratieprofiel";
/* categories */
"contacts_category_labels" = "Collega, Concurrent, Klant, Vriend, Familie, Zakenpartner, Leverancier, Pers, VIP";
@@ -47,4 +47,4 @@ reply_info = "Dit is een antwoord op een door u verstuurde uitnodiging.";
"OK" = "OK";
"Comment" = "Commentaar:";
"Description" = "Omschrijving";
"Location" = "Location";
"Location" = "Locatie";
+33 -33
View File
@@ -143,23 +143,23 @@
"This mail is being sent from an unsecure network!" = "Deze e-mail wordt verzonden vanaf een onveilig netwerk!";
"Address Book" = "Adresboek";
"Search For" = "Zoek naar";
"Contains" = "Contains";
"Does not contains" = "Does not contains";
"Advanced search" = "Advanced search";
"Anytime" = "Anytime";
"Last 7 days" = "Last 7 days";
"Last 30 days" = "Last 30 days";
"Last 6 month" = "Last 6 month";
"Before" = "Before";
"After" = "After";
"Between" = "Between";
"and" = "and";
"With attachments" = "With attachments";
"In favorites" = "In favorites";
"Unseen only" = "Unseen only";
"Show more" = "Show more";
"Contains" = "bevat";
"Does not contains" = "bevat niet";
"Advanced search" = "Geavanceerd zoeken";
"Anytime" = "Altijd";
"Last 7 days" = "Laatste 7 dagen";
"Last 30 days" = "Laatste 30 dagen";
"Last 6 month" = "Laatste 6 maanden";
"Before" = "Voor";
"After" = "Na";
"Between" = "Tussen";
"and" = "en";
"With attachments" = "Met bijlage";
"In favorites" = "In favorieten";
"Unseen only" = "Alleen ongelezen ";
"Show more" = "Meer weergeven";
"Reset" = "Reset";
"Message size" = "Message size";
"Message size" = "Berichtgrootte";
/* Popup "show" */
"all" = "alle";
@@ -171,7 +171,7 @@
/* MailListView */
"Sender" = "Afzender";
"Subject or Sender" = "Onderwerp of afzender";
"Subject or Recipient" = "Subject or Recipient";
"Subject or Recipient" = "Onderwerp of ontvanger";
"To or Cc" = "Ontvanger (Aan of Cc)";
"Entire Message" = "Volledig bericht";
"Date" = "Datum";
@@ -257,7 +257,7 @@
"Save As..." = "Opslaan als...";
"Print Preview" = "Afdrukvoorbeeld";
"View Message Source" = "Bron bekijken";
"View raw message" = "View raw message";
"View raw message" = "Bekijk onbewerkt bericht";
/* Message view "more" menu: create an event from message */
"Convert To Event" = "Omzetten naar afspraak";
@@ -464,7 +464,7 @@
"Copy messages" = "Kopieer berichten";
"More messages options" = "Meer berichtopties";
"Flag" = "Met vlag";
"Unflag" = "Unflag";
"Unflag" = "Vlag verwijderen";
"Mark as Unread" = "Als ongelezen markeren";
"Mark as Read" = "Markeren als gelezen";
"Closing Window ..." = "Venster sluiten ...";
@@ -478,7 +478,7 @@
"Your email has been sent" = "Uw email is verzonden";
"Folder compacted" = "Map gecomprimeerd";
"More Options" = "More Options";
"More Options" = "Meer opties";
"New draft" = "Nieuw concept";
"Create new draft with this template" = "Maak een nieuw concept met dit sjabloon";
@@ -517,16 +517,16 @@
"hotkey_forward" = "f";
/* Email deletion */
"Clean folder" = "Clean folder";
"Clean mailbox" = "Clean mailbox";
"Delete permanently (do not use trash)" = "Delete permanently (do not use trash)";
"Apply to subfolders" = "Apply to subfolders";
"Delete e-mails older than" = "Delete e-mails older than";
"3 months" = "3 months";
"6 months" = "6 months";
"9 months" = "9 months";
"1 year" = "1 year";
"Custom" = "Custom";
"You are about to permanently delete some messages. Are you sure you want to proceed with this action?" = "You are about to permanently delete some messages. Are you sure you want to proceed with this action?";
"Apply" = "Apply";
"%{0} message(s) deleted" = "%{0} message(s) deleted";
"Clean folder" = "Map opschonen";
"Clean mailbox" = "Mailbox opschonen";
"Delete permanently (do not use trash)" = "Permanent verwijderen (niet in de prullenbak gebruiken)";
"Apply to subfolders" = "Toepassen op submappen";
"Delete e-mails older than" = "Verwijder e-mails die ouder zijn dan";
"3 months" = "3 maanden";
"6 months" = "6 maanden";
"9 months" = "9 maanden";
"1 year" = "1 jaar";
"Custom" = "Aangepast";
"You are about to permanently delete some messages. Are you sure you want to proceed with this action?" = "U staat op het punt om enkele berichten definitief te verwijderen. Weet u zeker dat u hiermee wilt doorgaan?";
"Apply" = "Toepassen";
"%{0} message(s) deleted" = "%{0} bericht(en) verwijderd";
@@ -171,7 +171,7 @@
/* MailListView */
"Sender" = "Gönderici";
"Subject or Sender" = "Konu ya da Gönderici";
"Subject or Recipient" = "Subject or Recipient";
"Subject or Recipient" = "Konu ya da alıcı";
"To or Cc" = "Alıcı veya Kopya";
"Entire Message" = "Tüm ileti";
"Date" = "Tarih";
@@ -517,16 +517,16 @@
"hotkey_forward" = "f";
/* Email deletion */
"Clean folder" = "Clean folder";
"Clean mailbox" = "Clean mailbox";
"Delete permanently (do not use trash)" = "Delete permanently (do not use trash)";
"Apply to subfolders" = "Apply to subfolders";
"Delete e-mails older than" = "Delete e-mails older than";
"3 months" = "3 months";
"6 months" = "6 months";
"9 months" = "9 months";
"1 year" = "1 year";
"Custom" = "Custom";
"You are about to permanently delete some messages. Are you sure you want to proceed with this action?" = "You are about to permanently delete some messages. Are you sure you want to proceed with this action?";
"Apply" = "Apply";
"%{0} message(s) deleted" = "%{0} message(s) deleted";
"Clean folder" = "Klasörü temizle";
"Clean mailbox" = "E-posta kutusunu temizle";
"Delete permanently (do not use trash)" = "Kalıcı olarak sil (çöpe atmadan)";
"Apply to subfolders" = "Alt klasörlere uygula";
"Delete e-mails older than" = "Şundan eski e-postaları sil";
"3 months" = "3 ay";
"6 months" = "6 ay";
"9 months" = "9 ay";
"1 year" = "1 yıl";
"Custom" = "Özel";
"You are about to permanently delete some messages. Are you sure you want to proceed with this action?" = "Bazı iletileri kalıcı olarak silmek üzeresiniz. Bunu yapmak istediğinize emin misiniz?";
"Apply" = "Uygula";
"%{0} message(s) deleted" = "%{0} ileti silindi";
+16 -16
View File
@@ -77,14 +77,14 @@
"Welsh" = "Cymraeg";
"About" = "Over";
"AboutBox" = "Developed by Inverse and Alinto, SOGo is a fully-featured groupware server with a focus on scalability and simplicity.<br/><br/>\nSOGo provides a rich AJAX-based Web interface and supports multiple native clients through the use of standard protocols such as CalDAV and CardDAV.<br/><br/>\nSOGo is distributed under the <a href=\"http://gnu.org/licenses/gpl.html\">GNU GPL</a> version 2 or later and parts are distributed under the GNU LGPL version 2. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.<br/><br/>\nSee <a href=\"http://www.sogo.nu/en/support/community.html\">this page</a> for various support options.";
"AboutBox" = "SOGO is een volledig uitgeruste groupware server met focus op schaalbaarheid en eenvoud ontwikkeld door Inverse.<br/><br/>SOGO biedt een rijke AJAX-gebaseerde web-interface en ondersteunt meerdere native clients door gebruik van standaard protocollen als CalDAV en CardDAV.<br/><br/>SOGO wordt gedistribueerd onder de <a href=\"http://gnu.org/licenses/gpl.html\">GNU GPL</a> versie 2 of hoger en onderdelen worden verspreid onder de GNU LGPL versie 2. Dit is vrije software: je bent vrij om het te veranderen en verspreiden. Er is GEEN GARANTIE, voor zover toegestaan ​​door de wet.<br/><br/>Zie <a href=\"http://www.sogo.nu/en/support/community.html\">deze pagina</a> voor diverse support opties.";
"Your account was locked due to too many failed attempts." = "Uw account is geblokkeerd wegens te veel mislukte pogingen.";
"Your account was locked due to an expired password." = "Uw account is geblokkeerd wegens een verlopen wachtwoord.";
"Login failed due to unhandled error case" = "Inloggen mislukt wegens onverwerkte fout";
"Change your Password" = "Verander uw wachtwoord";
"The password was changed successfully." = "Het wachtwoord is met succes veranderd.";
"Your password has expired, please enter a new one below" = "Uw wachtwoord is verlopen, vul hieronder een nieuw wachtwoord in";
"Your password is too weak. Please choose a stronger password to enhance your security" = "Your password is too weak. Please choose a stronger password to enhance your security";
"Your password is too weak. Please choose a stronger password to enhance your security" = "Uw wachtwoord is te zwak. Kies een sterker wachtwoord om uw veiligheid te verbeteren.";
"Password must not be empty." = "Wachtwoord mag niet leeg zijn.";
"The passwords do not match. Please try again." = "De wachtwoorden komen niet overeen. Probeer het opnieuw.";
"Password Grace Period" = "Wachtwoord-respijtperiode";
@@ -114,17 +114,17 @@
"Missing type parameter" = "Ontbrekende typeparameter";
/* Password Recovery */
"Secondary e-mail" = "Secondary e-mail";
"Answer" = "Answer";
"Password lost" = "Password lost";
"No password recovery method has been defined for this user" = "No password recovery method has been defined for this user";
"Invalid secret question answer" = "Invalid secret question answer";
"Next" = "Next";
"Back" = "Back";
"Please enter a new password below" = "Please enter a new password below";
"A link will be sent to %{0}" = "A link will be sent to %{0}";
"A password reset link has been sent, please check your recovery e-mail mailbox and click on the link" = "A password reset link has been sent, please check your recovery e-mail mailbox and click on the link";
"Invalid configuration for email password recovery" = "Invalid configuration for email password recovery";
"Password recovery email in error" = "Password recovery email in error";
"Password reset" = "Password reset";
"Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}" = "Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}";
"Secondary e-mail" = "Secundair e-mailadres";
"Answer" = "Beantwoord";
"Password lost" = "Wachtwoord verloren";
"No password recovery method has been defined for this user" = "Er is geen wachtwoordherstelmethode gedefinieerd voor deze gebruiker";
"Invalid secret question answer" = "Ongeldig antwoord op geheime vraag";
"Next" = "Volgende";
"Back" = "Terug";
"Please enter a new password below" = "Voer hieronder een nieuw wachtwoord in";
"A link will be sent to %{0}" = "Er wordt een link verzonden naar %{0}";
"A password reset link has been sent, please check your recovery e-mail mailbox and click on the link" = "Er is een link verzonden om uw wachtwoord opnieuw in te stellen. Controleer uw herstel-e-mailbox en klik op de link.";
"Invalid configuration for email password recovery" = "Ongeldige configuratie voor e-mailwachtwoordherstel";
"Password recovery email in error" = "Wachtwoordherstel-e-mail is foutief verzonden";
"Password reset" = "Wachtwoord opnieuw instellen";
"Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}" = "Hallo %{0},\nEr is een verzoek ingediend om je wachtwoord te wijzigen!\n\nAls je dit verzoek niet hebt ingediend, negeer dan deze e-mail.\n\nKlik anders op deze link om je wachtwoord te wijzigen: %{1}";
+1
View File
@@ -14,6 +14,7 @@ MainUI_OBJC_FILES += \
SOGoUserHomePage.m \
SOGoBrowsersPanel.m \
SOGoMicrosoftActiveSyncActions.m \
SOGoAPIActions.m \
ifeq ($(saml2_config), yes)
MainUI_OBJC_FILES += SOGoSAML2Actions.m
+73
View File
@@ -0,0 +1,73 @@
/*
Copyright (C) 2014-2016 Inverse inc.
This file is part of SOGo.
SOGo is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
SOGo 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 Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public
License along with SOGo; see the file COPYING. If not, write to the
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/
#import <SOGo/SOGoCache.h>
#import <SOGo/NSObject+Utilities.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WODirectAction.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOResponse.h>
#import <API/SOGoAPIDispatcher.h>
@interface SOGoAPIActions : WODirectAction
@end
@implementation SOGoAPIActions
- (WOResponse *) sogoAPIAction
{
WOResponse *response;
WORequest *request;
NSBundle *bundle;
NSException *ex;
id dispatcher;
Class clazz;
request = (WORequest *)[context request];
response = (WOResponse *)[context response];
[response setStatus: 200];
[response setHeader: @"application/json; charset=utf-8" forKey: @"content-type"];
bundle = [NSBundle bundleForClass: NSClassFromString(@"SOGoAPIProduct")];
clazz = [bundle classNamed: @"SOGoAPIDispatcher"];
dispatcher = [[clazz alloc] init];
ex = [dispatcher dispatchRequest: request inResponse: response context: context];
//[[self class] memoryStatistics];
if (ex)
{
return [NSException exceptionWithHTTPStatus: 500];
}
RELEASE(dispatcher);
//[[SOGoCache sharedCache] killCache];
return response;
}
@end
@@ -84,7 +84,7 @@
"Change your Password" = "Parolanızı değiştirin";
"The password was changed successfully." = "Parola değiştirildi.";
"Your password has expired, please enter a new one below" = "Parolanızın süresi dolmuş. Lütfen aşağıya yeni bir parola yazın";
"Your password is too weak. Please choose a stronger password to enhance your security" = "Your password is too weak. Please choose a stronger password to enhance your security";
"Your password is too weak. Please choose a stronger password to enhance your security" = "Parolanız çok zayıf. Lütfen güvenliğinizi artırmak için daha güçlü bir parola seçin";
"Password must not be empty." = "Parola boş olamaz.";
"The passwords do not match. Please try again." = "Parola ile onayı aynı değil. Lütfen yeniden deneyin.";
"Password Grace Period" = "Parola geçerlilik süresi";
+5
View File
@@ -118,6 +118,11 @@
actionClass = "SOGoMicrosoftActiveSyncActions";
actionName = "microsoftServerActiveSync";
};
SOGoAPI = {
protectedBy = "<public>";
actionClass = "SOGoAPIActions";
actionName = "sogoAPI";
};
casProxy = {
protectedBy = "<public>";
pageName = "SOGoRootPage";
@@ -24,7 +24,7 @@
"Vacation" = "Afwezigheid";
"Forward" = "Doorsturen";
"Password" = "Wachtwoord";
"Notification" = "Notification";
"Notification" = "Notificatie";
"Categories" = "Categorieën";
"Appointments invitations" = "Uitnodigingen voor afspraken";
"Name" = "Naam";
@@ -46,10 +46,10 @@
"Days between responses" = "Dagen tussen reacties";
"Do not send responses to mailing lists" = "Geen reacties naar maillinglijsten sturen";
"Activation Constraints" = "Activatiebeperkingen";
"Customize day and hour ranges independently and optionally combine them with specific weekdays to tailor your vacation filters to your preferences" = "Customize day and hour ranges independently and optionally combine them with specific weekdays to tailor your vacation filters to your preferences";
"Customize day and hour ranges independently and optionally combine them with specific weekdays to tailor your vacation filters to your preferences" = "Pas de dag- en uur bereiken onafhankelijk van elkaar aan en combineer ze eventueel met specifieke weekdagen om uw vakantiefilters aan te passen aan uw voorkeuren";
"Enable auto reply on" = "Automatisch beantwoorden inschakelen";
"First day of vacation" = "Eerste vakantiedag";
"Disable auto reply after" = "Disable auto reply after";
"Disable auto reply after" = "Automatisch antwoord uitschakelen na";
"Last day of vacation" = "Laatste vakantiedag";
"Enter date" = "Voer datum in";
/* Enable auto reply at this time of the day */
@@ -82,9 +82,9 @@
"You are not allowed to forward your messages to this domain:" = "U mag uw berichten niet doorsturen naar dit domein:";
/* notification */
"Notify incoming messsages" = "Notify incoming messsages";
"Notification Message Sent" = "Notification message sent";
"Notification Message" = "Notification of new message on %{0}";
"Notify incoming messsages" = "Meld binnenkomende berichten";
"Notification Message Sent" = "Meldingsbericht verzonden";
"Notification Message" = "Melding van nieuw bericht op %{0}";
/* d & t */
"Time Zone" = "Tijd Zone";
@@ -152,7 +152,7 @@
"Contacts Names" = "Contactnamen";
"Duplicate name" = "Dubbele naam";
"Calendar categories must have unique names." = "Kalendercategorieën moeten unieke namen hebben.";
"Disable e-mail notifications on CalDAV" = "Disable e-mail notifications on CalDAV";
"Disable e-mail notifications on CalDAV" = "E-mailmeldingen op CalDAV uitschakelen";
/* Default Calendar */
"Default calendar" = "Standaardagenda";
@@ -202,11 +202,11 @@
"Compose messages in" = "Berichten opstellen in";
"composemessagestype_html" = "HTML";
"composemessagestype_text" = "Platte tekst";
"Insert signature on new message" = "Insert signature on new message";
"Insert signature on reply" = "Insert signature on reply";
"Insert signature on forward" = "Insert signature on forward";
"Show recipients or sender full email in mailboxes" = "Show recipients or sender full email in mailboxes";
"Hide attachements for inline images" = "Hide attachments for inline images";
"Insert signature on new message" = "Handtekening invoegen bij nieuw bericht";
"Insert signature on reply" = "Handtekening invoegen bij antwoord";
"Insert signature on forward" = "Handtekening bij doorsturen invoegen";
"Show recipients or sender full email in mailboxes" = "Toon ontvangers of afzenders volledige e-mail in mailboxen";
"Hide attachements for inline images" = "Bijlagen voor inline-afbeeldingen verbergen";
/* Base font size for messages composed in HTML */
"Default font size" = "Standaard lettergrootte";
@@ -232,11 +232,11 @@
"Mail Account" = "Mailaccount";
"New Mail Account" = "Nieuw mailaccount";
"Server Name" = "Servernaam";
"Outgoing Server Name (SMTP)" = "Outgoing Server Name (SMTP)";
"Smtp Auth" = "Smtp Auth";
"Outgoing Server Name (SMTP)" = "Uitgaande servernaam (SMTP)";
"Smtp Auth" = "Smtp-authenticatie";
"Port" = "Poort";
"Encryption" = "Encryptie";
"Outgoing Encryption" = "Outgoing Encryption";
"Outgoing Encryption" = "Uitgaande encryptie";
"None" = "Geen";
"User Name" = "Gebruikersnaam";
"Full Name" = "Naam";
@@ -263,7 +263,7 @@
"Digitally sign the message by default" = "Standaard digitaal ondertekenen van het bericht";
"Always try to encrypt the message" = "Probeer altijd het bericht te versleutelen";
"An error occurred while importing the certificate. Verify your password." = "Er is een fout opgetreden tijdens het importeren van het certificaat. Controleer je wachtwoord.";
"Data too big. Please contact technical support." = "Data too big. Please contact technical support.";
"Data too big. Please contact technical support." = "Gegevens te groot. Neem contact op met de technische ondersteuning.";
/* Additional Parameters */
"Additional Parameters" = "Extra Parameters";
@@ -418,7 +418,7 @@
"Keep the message" = "Bewaar het bericht";
"Forward the message to" = "Stuur het bericht door naar";
"Notify the message to" = "Notify the message to";
"Notify the message to" = "Geef het bericht door aan";
/* Input field label of "forward" mail filter action */
"Email" = "E-mail";
@@ -499,8 +499,8 @@
"Enable two-factor authentication using a TOTP application" = "Twee-factor authenticatie met TOTP programma inschakelen";
"You must enter this key into your TOTP application." = "U moet deze sleutel in uw TOTP programma invoeren.";
"If you do not and you log out you will not be able to login again." = "Als u dit niet doet en uitlogt, zal u niet meer terug kunnen inloggen.";
"Enter TOTP verification code :" = "Enter TOTP verification code :";
"Invalid TOTP verification code" = "Invalid TOTP verification code";
"Enter TOTP verification code :" = "Voer de TOTP-verificatiecode in:";
"Invalid TOTP verification code" = "Ongeldige TOTP-verificatiecode";
/* External Sieve scripts */
"An external Sieve script is active" = "Een extern Sieve script is actief";
@@ -508,9 +508,9 @@
"Let SOGo handle everything" = "Laat SOGo alles beheren";
/* Password Recovery */
"Password recovery mode" = "Password recovery mode";
"Question" = "Question";
"Secondary e-mail" = "Secondary e-mail";
"Answer" = "Answer";
"Password recovery" = "Password recovery";
"Password change" = "Password change";
"Password recovery mode" = "Wachtwoordherstelmodus";
"Question" = "Vraag";
"Secondary e-mail" = "Secundair e-mailadres";
"Answer" = "Beantwoord";
"Password recovery" = "Wachtwoordherstelmodus";
"Password change" = "Wachtwoord wijzigen mislukt";
+7 -7
View File
@@ -65,7 +65,7 @@
"A total of %{0} events were imported in the calendar." = "Een totaal van %{0} afspraken is geïmporteerd naar de kalender.";
"Compose E-Mail to All Attendees" = "E-mail aan alle deelnemers opstellen";
"Compose E-Mail to Undecided Attendees" = "E-mail opstellen aan deelnemers die nog niet hebben gereageerd";
"Download iOS - MacOS configuration profile" = "Download iOS - MacOS configuration profile";
"Download iOS - MacOS configuration profile" = "Download iOS - MacOS configuratieprofiel";
/* Relative dates */
"Yesterday" = "Gisteren";
@@ -117,7 +117,7 @@
"Save and Close" = "Opslaan en sluiten";
"Close" = "Sluiten";
"Invite Attendees" = "Deelnemers uitnodigen";
"Create Jitsi Meeting" = "Create Jitsi Meeting";
"Create Jitsi Meeting" = "Jitsi-vergadering aanmaken";
"Attach" = "Koppelen";
"Update" = "Opslaan";
"Cancel" = "Annuleren";
@@ -378,7 +378,7 @@
"Show an Alert" = "Toon een waarschuwing";
"Send an E-mail" = "Stuur een E-mail";
"Email Organizer" = "E-mail organisator";
"Email Attendees (internal users)" = "Email Attendees (internal users)";
"Email Attendees (internal users)" = "E-mail deelnemers (interne gebruikers)";
"zoom_400" = "400%";
"zoom_200" = "200%";
"zoom_100" = "100%";
@@ -403,8 +403,8 @@
"To" = "Tot";
/* dates */
"DateFrom" = "From";
"DateTo" = "To";
"DateFrom" = "Van";
"DateTo" = "Aan";
/* validation errors */
validate_notitle = "U heeft geen titel opgegeven. Wilt u doorgaan?";
@@ -567,7 +567,7 @@ vtodo_class2 = "(Vertrouwelijke taak)";
"CalDAV URL" = "CalDAV-URL";
"WebDAV ICS URL" = "WebDAV ICS URL";
"WebDAV XML URL" = "WebDAV XML URL";
"Copy to clipboard" = "Copy to clipboard";
"Copy to clipboard" = "Kopiëren naar klembord";
/* Error messages */
"dayFieldInvalid" = "Vul een numerieke waarde in het veld Dagen in groter dan of gelijk aan 1.";
@@ -632,7 +632,7 @@ vtodo_class2 = "(Vertrouwelijke taak)";
"Rename" = "Hernoem";
"Import Calendar" = "Agenda Importeren";
"Select an ICS file." = "Selecteer een ICS bestand.";
"You have modified data unsaved. Do you want to close popup and loose data ?" = "You have modified data unsaved. Do you want to close popup and loose data ?";
"You have modified data unsaved. Do you want to close popup and loose data ?" = "U heeft gewijzigde gegevens die niet zijn opgeslagen. Wilt u de pop-up sluiten en uw gegevens verliezen?";
/* Notification when user subscribes to a calendar */
"Successfully subscribed to calendar" = "Abonneren op agenda succesvol";
@@ -117,7 +117,7 @@
"Save and Close" = "Guardar e Fechar";
"Close" = "Fechar";
"Invite Attendees" = "Convidar Participantes";
"Create Jitsi Meeting" = "Create Jitsi Meeting";
"Create Jitsi Meeting" = "Criar reunião no Jitsi";
"Attach" = "Anexar";
"Update" = "Atualizar";
"Cancel" = "Cancelar";
@@ -117,7 +117,7 @@
"Save and Close" = "Kaydet ve Kapat";
"Close" = "Kapat";
"Invite Attendees" = "Katılımcı davet et";
"Create Jitsi Meeting" = "Create Jitsi Meeting";
"Create Jitsi Meeting" = "Jitsi görüşmesi oluştur";
"Attach" = "Ekle";
"Update" = "Güncelle";
"Cancel" = "İptal";
@@ -748,7 +748,7 @@
</div>
</div>
<div class="hide-xs" layout="row" layout-align="start center" flex="50">
<div layout="row" layout-align="start center" flex="50" flex-xs="100">
<md-checkbox
class="md-align-top-left"
ng-model="app.preferences.defaults.SOGoMailComposeWindowEnabled"
@@ -756,7 +756,7 @@
<!-- SOGoMailComposeWindow -->
</md-checkbox>
<md-input-container class="md-block md-flex hide-xs">
<md-input-container class="md-block md-flex">
<label><var:string label:value="Always open mail composer"/></label>
<md-select
ng-disabled="!app.preferences.defaults.SOGoMailComposeWindowEnabled"
+1 -1
View File
@@ -1,3 +1,3 @@
MAJOR_VERSION=5
MINOR_VERSION=12
SUBMINOR_VERSION=1
SUBMINOR_VERSION=2
+1
View File
@@ -220,6 +220,7 @@ rm -fr ${RPM_BUILD_ROOT}
%{_libdir}/sogo/libSOGoUI.so*
%{_libdir}/GNUstep/SOGo/AdministrationUI.SOGo
%{_libdir}/GNUstep/SOGo/Appointments.SOGo
%{_libdir}/GNUstep/SOGo/API.SOGo
%{_libdir}/GNUstep/SOGo/CommonUI.SOGo
%{_libdir}/GNUstep/SOGo/Contacts.SOGo
%{_libdir}/GNUstep/SOGo/ContactsUI.SOGo